消息中间件RabbitMQ(二):API使用详解

RabbitMQ基础

RabbitMQ连接

创建连接时需要给定IP地址、端口号、账号密码等参数。

	ConnectionFactory connectionFactory=new ConnectionFactory();
    connectionFactory.setHost("192.168.1.1");
    //RabbitMQ服务端默认端口号为5672
    connectionFactory.setPort(5672);
    connectionFactory.setUsername("admin");
    connectionFactory.setPassword("admin");
    Connection connection = connectionFactory.newConnection();

也可以使用URI的方式来实现。

	ConnectionFactory connectionFactory=new ConnectionFactory();
    connectionFactory.setUri("amqp://userName:password@ipAddress:port/virtualHost");
    Connection connection=connectionFactory.newConnection();
    Channel channel = connection.createChannel();

Connection可以创建多个Channel实例,但是Channel实例不能在线程间共享,应用程序应该为每一个线程开辟一个Channel,因为多线程间共享Channel实例是非线程安全的。

交换器的声明

交换器和队列在使用之前都需要先声明(declare)。

声明交换器有多个重载方法,这些重载方法都是由下面的这个方法缺少某些参数构成的。

    Exchange.DeclareOk exchangeDeclare(
				String exchange,
                String type,
                boolean durable,
                boolean autoDelete,
                boolean internal,
                Map<String, Object> arguments) throws IOException;
  • 返回值为Exchange.DeclareOk,用来标识成功声明了一个交换器。
  • exchange:交换器的名称
  • type:交换器的类型,例如direct、fanout等
  • durable:设置是否持久化。true表示持久化。持久化可以将交换器存储到硬盘,服务器重启时不会丢失相关信息。
  • autoDelete:设置是否自动删除。true表示自动删除。自动删除的前提是至少有一个队列或者交换器与这个交换器绑定,之后所有与这个交换器绑定的队列或者交换器都与此解绑。
  • internal:设置是否是内置。true表示是内置的交换器,客户端无法直接发送消息到这个交换器,只能通过交换器路由到该交换器的这种方式。
  • arguments:其他一些结构化参数。

与exchangeDeclare类似的还有一个exchangeDeclareNoWait方法。这个方法比exchangeDeclare多设置了一个nowait参数,意思是不需要服务器返回,所以这个方法返回值为void。这个方法并不推荐使用,因为在声明以后,当实际服务器还未完成交换器的创建时,此时立即使用必然会出现异常。

void exchangeDeclareNoWait(
				String exchange,
                String type,
                boolean durable,
                boolean autoDelete,
                boolean internal,
                Map<String, Object> arguments) throws IOException;

exchangeDeclarePassive方法主要用来检测相应的交换器是否存在,如果存在返回Exchange.DeclareOk,如果不存在抛出404 channel异常,同时Channel会被关闭。

Exchange.DeclareOk exchangeDeclarePassive(String name) throws IOException;

删除交换器。exchange为交换器名称,ifUnused用来设置是否在交换器没有被使用的情况下删除。如果为true,则只有该交换器在没有被使用的情况下才会被删除,如果为false,则无论如何都进行删除。

Exchange.DeleteOk exchangeDelete(String exchange, boolean ifUnused) throws IOException;

队列的声明

声明队列没有那么多的重载方法,只有两个。

Queue.DeclareOk queueDeclare() throws IOException;

Queue.DeclareOk queueDeclare(
					String queue, 
					boolean durable, 
					boolean exclusive, 
					boolean autoDelete,
                    Map<String, Object> arguments) throws IOException;

不带任何参数的queueDeclare方法会默认创建一个由RabbitMQ自动命名的、排他的、自动删除的、非持久化的队列。

  • queue:队列的名称
  • durable:设置是否持久化。
  • exclusive:设置是否排他。true则设置队列为排他的。如果一个队列被声明为排他队列,则该队列仅对首次声明它的连接可见,并在连接断开时自动删除。排他队列是基于Connection可见的,同一个Connection的不同Channel是可以同时访问该连接创建的排他队列的。"首次"是指如果一个连接已经声明了一个排他队列,其他连接是不允许建立同名的排他队列的,即使该队列是持久化的,一旦连接关闭,该排他队列都会被自动删除。
  • autoDelete:设置是否自动删除。true则设置自动删除。自动删除的前提是:至少有一个消费者连接到这个队列,之后所有与这个队列连接的消费者都断开时,才会自动删除。
  • arguments:设置队列的一些其他参数。

queueDeclareNoWait和交换器的一样,返回值为void,表示不需要服务端的任何返回。

void queueDeclareNoWait(
					String queue, 
					boolean durable, 
					boolean exclusive, 
					boolean autoDelete,
                    Map<String, Object> arguments) throws IOException;

queueDeclarePassive用来检测相应的队列是否存在,如果存在返回Queue.DeclareOk,不存在则抛出异常。

Queue.DeclareOk queueDeclarePassive(String queue) throws IOException;

queueDelete为删除队列的方法,queue为队列名,ifUnused和交换器一样,ifEmpty设置为true表示在队列为空(队列里面没有任何堆积消息)的情况下才能删除。

Queue.DeleteOk queueDelete(String queue, boolean ifUnused, boolean ifEmpty) throws IOException;

queuePurge该方法用来清空队列中的内容,不删除队列本身。

Queue.PurgeOk queuePurge(String queue) throws IOException;

接下来我们简单的做一个小Demo,熟悉一下方法。

我们设置一个生产者自动删除,然后运行 

public class RabbitProducer {
    public static void main(String[] args) throws NoSuchAlgorithmException, KeyManagementException, URISyntaxException, IOException, TimeoutException {
        ConnectionFactory connectionFactory=new ConnectionFactory();
        connectionFactory.setUri("amqp://admin:admin@127.0.0.1:5672");
        Connection connection=connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare("exchange100","direct",false,true,null);
        channel.queueDeclare("queue100",false,false,true,null);
        channel.queueBind("queue100","exchange100","testKey");
        channel.basicPublish("exchange100","testKey", MessageProperties.PERSISTENT_TEXT_PLAIN,"helloworld".getBytes());
        connection.close();
    }
}

在这里插入图片描述
在这里插入图片描述我们发现交换器和队列并没有自动删除,这也验证了自动删除需要前提
这时候我们满足自动删除的前提,即解绑交换器和队列,消费消息并与队列断开

public static void main(String[] args) throws NoSuchAlgorithmException, KeyManagementException, URISyntaxException, IOException, TimeoutException {
        ConnectionFactory connectionFactory=new ConnectionFactory();
        connectionFactory.setUri("amqp://admin:admin@127.0.0.1:5672");
        Connection connection=connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare("exchange200","direct",false,true,null);
        channel.queueDeclare("queue200",false,false,true,null);
        channel.queueBind("queue200","exchange200","testKey");
        channel.basicPublish("exchange200","testKey", MessageProperties.PERSISTENT_TEXT_PLAIN,"helloworld".getBytes());
        channel.queueUnbind("queue200","exchange200","testKey");
        connection.close();
    }
public static void main(String[] args) throws Exception{
        ConnectionFactory connectionFactory=new ConnectionFactory();
        connectionFactory.setUri("amqp://admin:admin@10.50.1.17:5672");
        Connection connection=connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        channel.basicConsume("queue200",false,new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag,
                                       Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body)
                    throws IOException
            {
                System.out.println("消费消息"+new String(body));
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        });
        TimeUnit.SECONDS.sleep(50);
        connection.close();
    }

我们发现执行完以后交换器和队列就已经不存在了,自动删除了。
在这里插入图片描述在这里插入图片描述

交换器和队列的绑定

  • queue:队列名称
  • exchange:交换器的名称
  • routingKey:用来绑定队列和交换器的路由键
  • arguments:设置绑定的一些其他参数。
Queue.BindOk queueBind(
						String queue, 
						String exchange, 
						String routingKey, 
						Map<String, Object> arguments) throws IOException;

将已经被绑定的队列和交换器解绑。

Queue.UnbindOk queueUnbind(
						String queue, 
						String exchange, 
						String routingKey, 
						Map<String, Object> arguments) throws IOException;

我们不仅可以将交换器与队列绑定,也可以将交换器和交换器绑定。绑定之后,消息从source交换器转发到destination交换器。

Exchange.BindOk exchangeBind(
						String destination, 
						String source, 
						String routingKey, 
						Map<String, Object> arguments) throws IOException;

发送消息

basicPublish方法为发送消息的方法。有三个重载方法。

void basicPublish(
				String exchange, 
				String routingKey, 
				BasicProperties props, 
				byte[] body) throws IOException;
				
void basicPublish(
				String exchange, 
				String routingKey, 
				boolean mandatory, 
				BasicProperties props, 
				byte[] body) throws IOException;
				
void basicPublish(
				String exchange, 
				String routingKey, 
				boolean mandatory, 
				boolean immediate, 
				BasicProperties props, byte[] body)throws IOException;
  • exchange:交换器的名称

  • routingKey:路由键

  • props:消息的基本属性集,例如消息头、编码、过期时间等,其包含十四个属性成员。分别有 contentType、contentEncoding、headers (Map<String , Object>) 、deliveryMode、priority、correlationld、replyTo、expiration、messageld、timestamp、type、userld、appld、clusterld。

  • body:消息体。真正发送的内容

  • mandatory和immediate后面会讲到。

下面这段代码发送了一条消息,这条消息的投递模式(deliveryMode)设置为2,即消息会被持久化在服务器中。这条消息的优先级(priority)设置为1,contentType设置为text/plain,并且设置了userId以便让消费者知道是谁发送的消息。

        channel.basicPublish(
        			"exchangeName",
        			"routingKey",
        			new AMQP.BasicProperties().
        			builder().
        			contentType("text/plain").
        			deliveryMode(2).
        			priority(1).
        			userId("admin").
        			build(),
        			"message".getBytes());

我们还可以发送一条带有过期时间的消息。

        channel.basicPublish(
        			"exchangeName",
        			"routingKey",
        			new AMQP.BasicProperties().
        			builder().
        			expiration("60000").
        			build(),
        			"message".getBytes());

只是简单的举例一下用法,后面会慢慢更进一步学习。

消费消息

RabbitMQ的消费模式分为两种:推模式(push)和拉模式(pull)。

推模式中,可以通过持续订阅的方式来消费消息,接收消息一般通过实现Consumer接口或者继承DefaultConsumer类来实现。
我们通过Channel类的basicConsume来进行消费,这个方法有很多的重载形式。

    String basicConsume(
    			String queue, 
    			boolean autoAck, 
    			String consumerTag, 
    			boolean noLocal, 
    			boolean exclusive, 
    			Map<String, Object> arguments, 
    			DeliverCallback deliverCallback, 
    			CancelCallback cancelCallback, 
    			ConsumerShutdownSignalCallback shutdownSignalCallback) throws IOException;

  • queue:队列的名称
  • autoAck:设置是否自动确认。一般设置为false,即不自动确认。
  • consumerTag:消费者标签,用来区分多个消费者。
  • noLocal:设置为true,则表示不能将同一个Connection中生产者发送的消息传送给这个Connection中的消费者。
  • exclusive:设置是否排他。
  • arguments:设置消费者的其他参数
  • deliverCallback:处理推送过来的消息的回调函数。比如DefaultConsumer,重写其中的方法实现我们对消息的处理逻辑。
  • cancelCallback:当消费者取消订阅时的回调函数
  • shutdownSignalCallback:当Connection/Channel关闭时的回调函数

消费者客户端同样需要考虑线程安全的问题。消费者客户端额这些callback函数会被分配到与Channel不同的线程池上,这意味着消费者客户端可以安全地调用这些阻塞方法,比如channel.queueDeclare,channel.basicCancle等。
每个Channel都拥有自己独立的线程。最常用的做法是一个channel对应一个消费者,也就是消费者之间没有任何关联。如果一个channel中维护多个消费者,要注意如果一个消费者一直在运行,其他的消费者callback会被耽搁。

拉模式通过channel.basicGet方法来单条获取消息,返回值是GetResponse。
它没有其他重载函数。

 GetResponse basicGet(String queue, boolean autoAck) throws IOException;
  • queue:队列名称
  • autoAck:设置是否自动确认。一般设置为false,即不自动确认。

推模式会将channel置为接受模式,直到取消队列的订阅,在接收消息期间,RabbitMQ会不断地推送消息给消费者,当然推送消息地个数会受到Basic.Qos的限制。如果想获取单条消息,就要使用拉模式。但是不能用循环拉模式来代替推模式,这样会严重影响性能。

消息的确认和拒绝

为了保证消息从队列可靠地达到消费者,RabbitMQ提供了消息确认机制。消费者在订阅队列时可以设置autoAck参数,当autoAck设置为false,即不自动确认时,RabbitMQ会等待消费者手动回复确认收到消息后才从内存中移除消息(实际上是先打上删除标记,之后再删除)。当autoAck为true时,RabbitMQ会自动把发送出去的消息置为确认,然后删除,而不管消费者是否真的消费到了消息。

手动确认消息的方法basicAck。

void basicAck(long deliveryTag, boolean multiple) throws IOException;
  • deliveryTag:用来标识信道中投递的消息,它是 一个 64 位的长整型值,最大值是9223372036854775807。RabbitMQ 推送消息给 Consumer 时,会附带一个 Delivery Tag,以便 Consumer 可以在消息确认时告诉 RabbitMQ 到底是哪条消息被确认了。可以从消费消息方法的Envelope对象中获取。
  • multiple:如果false,表示通知 RabbitMQ该deliveryTag消息被确认。如果为true,则将小于deliveryTag之前的所有消息全部确认。

当autoAck设置为false时,就不用担心处理消息过程中消费者进程挂掉后消息丢失的问题。,因为RabbitMQ会一直等待持有消息直到消费者显式调用basicAck。

当autoAck设置为false时,对于RabbitMQ服务端而言,队列中的消息被分为了两个部分,一部分是等待投递给消费者的消息,一部分是已经投递给消费者的消息,但是还没有收到消费者的确认信号。如果RabbitMQ一直没有收到确认信号,并且消费者断开了连接,则RabbitMQ会安排此消息重新进入队列,等待投递给下一个消费者。
RabbitMQ不会为未确认的消息设置过期时间,它判断此消息是否需要重新投递的给消费者的唯一依据就是消费该消息的消费者是否断开连接。

RabbtiMQ Web 管理平台上可以看到当前队列中的 “Ready” 状态和"Unacknowledged" 状态的消息数,分别对应上文中的等待投递给消费者的消息数和己经投递给消费者但是未收到确认信号的消息数。

如果消费者接收到消息后,想明确拒绝当前消息呢?

void basicReject(long deliveryTag, boolean requeue) throws IOException;
  • deliveryTag:用来标识信道中投递的消息
  • requeue:requeue 参数设置为true,则 RabbitMQ 会重新将这条消息存入队列,以便可以发送给下个订阅的消费者;如果 requeue 参数设置为 false,则 RabbitMQ立即会把消息从队列中移除,而不会把它发送给新的消费者。

Basic.Reject 命令一次只能拒绝一条消息 ,如果想要批量拒绝消息 ,则可以使用Basic.Nack 这个命令

void basicNack(long deliveryTag, boolean multiple, boolean requeue) throws IOException; 
  • deliveryTag:用来标识信道中投递的消息
  • multiple: 如果false,表示通知 RabbitMQ 该deliveryTag消息被拒绝。如果为true,则将小于deliveryTag之前的所有消息全部拒绝。
  • requeue:requeue 参数设置为true,则 RabbitMQ 会重新将这条消息存入队列,以便可以发送给下个订阅的消费者;如果 requeue 参数设置为 false,则 RabbitMQ立即会把消息从队列中移除,而不会把它发送给新的消费者。

basicRecover用来请求RabbitMQ重新发送还未被确认的消息。具备可重入队列的特性。
如果requeue为true,则未被确认的消息会被重新加入到队列中,这样对于同一条消息来说,可能会被分配给与之前不同的消费者。如果为false,那么同一条消息会被分配给之前相同的消费者。不设置该参数,默认为true。

Basic.RecoverOk basicRecover() throws IOException;

Basic.RecoverOk basicRecover(boolean requeue) throws IOException;

关闭连接

显式关闭Channel是个好习惯,但是不是必须的,因为Connection关闭的时候,Channel也会自动关闭。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值