RabbitMQ环境有了,核心概念也了解完了,接下来我们有必要了解一下客户端开发api。

1. 交换器

1.1 声明交换器

exchangeDeclare有很多个重载方法,这些重载方法都是有下面这个方法中缺省的某些参数构成。

Exchange.DeclareOk exchangeDeclare(String exchange,
                                              String type,
                                              boolean durable,
                                              boolean autoDelete,
                                              boolean internal,
                                              Map<String, Object> arguments) throws IOException;
  • exchange:交换器的名称。
  • type:交换器类型,常见的如fanoutdirecttopic等。
  • durable:是否持久化。持久化可以将交换器存盘,在服务器重启的时候不会丢失交换器的元信息。
  • autoDelete:是否自动删除。自动删除的提前是至少有一个队列或者交换器与这个交换器绑定,之后所有与这个交换器绑定的队列或者交换器都与之解绑。并不是说当与此交换器连接的客户端都断开时,RabbitMQ会自动删除本交换器。
  • internal:是否是内置的。如果是内置的交换器,客户端程序无法直接发送消息到这个交换器中,只能通过交换器路由到交换器这种方式。
  • arguments:其它一些结构化参数,比如alternate-exchange,后面用到了再具体说。

与此同时,我们通过客户端源码还可以看到如下声明交换器的方法:

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

该方法入参和上述一模一样,只是它的返回值为void。从方法名和返回值可以知道,这个声明交换器不需要等待服务器返回创建结果,考虑这样一种情况,在声明完一个交换器之后(实际服务器并未完成交换器的创建),那么此时客户端紧接着使用这个交换器,必然会发生异常。如果没有特殊的应用场景,并不建议使用这个方法。

1.2 删除交换器

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

不解释,看方法名和参数猜都能猜出来是干嘛的了。

1.3 判断交换器是否存在

Exchange.DeclareOk exchangeDeclarePassive(String name) throws IOException;

2. 队列

2.1 声明队列

queueDeclare只有两个重载方法。

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

不带任何参数的queueDeclare方法默认创建一个由RabbitMQ命名的(名称类似:amq.xxx,这种队列也称之为匿名队列)、排他的、自动删除的、非持久化的队列。

  • queue:队列名称。
  • durable:是否持久化。持久化的队列会存盘,在服务器重启的时候可以保证不丢失队列的元信息。如果该参数设置为false,当服务器重启之后,相关队列的元数据会丢失,此时数据也会丢失。(注意,这里的持久化是指队列的元数据,并不是保证内部所存储的消息也不会丢失。)
  • exclusive:是否排他,如果一个队列被声明为排他队列,那么该队列仅对首次声明它的连接课件,并在连接断开时自动删除。这里需要注意三点:
  • 排他队列是基于连接(Connection)可见的,同一连接的不同信道(Channel)是可以同时访问同一连接创建的排他队列的。
  • “首次”是指如果一个连接已经声明了一个排他队列,其它连接是不允许建立同名的排他队列的。
  • 即使该队列是持久化的,一旦连接关闭或者客户端退出,该排他队列都会被自动删除。
  • autoDelete:是否自动删除。自动删除的前提时:至少有一个消费者连接到这个队列,之后所有与这个队列连接的消费者都断开时,才会自动删除。如果没有消费者客户端与这个队列连接时,不会自动删除这个队列。
  • arguments:设置队列的一些其他参数,比如x-message-ttlx-expires等,后面用到了再具体说。

与交换器类似,声明队列也有一个不需要等待服务端返回的方法。

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

2.2 删除队列

Queue.DeleteOk queueDelete(String queue) throws IOException;
Queue.DeleteOk queueDelete(String queue, boolean ifUnused, boolean ifEmpty) throws IOException;
void queueDeleteNoWait(String queue, boolean ifUnused, boolean ifEmpty) throws IOException;
  • isEmpty:设置为true时,表示队列为空即没有任何消息堆积的情况下才能删除。

2.3 判断队列是否已经存在

Queue.DeclareOk queueDeclarePassive(String queue) throws IOException;

2.4 清空队列

Queue.PurgeOk queuePurge(String queue) throws IOException;

3. 绑定

3.1 交换器与队列绑定

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

3.2 交换器与交换器绑定

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

4. 解绑

4.1 交换器与队列解绑

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

4.2 交换器与交换器解绑

Exchange.UnbindOk exchangeUnbind(String destination, String source, String routingKey) throws IOException;
Exchange.UnbindOk exchangeUnbind(String destination, String source, String routingKey, Map<String, Object> arguments) throws IOException;
void exchangeUnbindNoWait(String destination, String source, String routingKey, Map<String, Object> arguments) throws IOException;

5. 发送消息

void basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate, BasicProperties props, byte[] body)
            throws IOException;
  • exchange:交换器名称,指明消息需要发送到哪个交换器。如果设置为空字符串,则消息会被发送到RabbitMQ默认的交换器中。
  • routingKey:路由键,交换器根据路由键和自身类型将消息存储到对应的队列中。
  • mandatory和immediate:后面再详细说。
  • props:消息的基本属性集,比如deliveryModepriorityexpiration等。
  • body:消息体,真正需要发送的消息。

6. 消费消息

RabbitMQ的消费模式分两种:推和拉模式。推模式采用Basic.Consumer进行消费,而拉模式则是调用Basic.Get进行消费。

6.1 拉模式

拉模式很简单,只有basicGet一个方法,没有其它的重载方法。拉模式通常用于单条地获取消息。

GetResponse basicGet(String queue, boolean autoAck) throws IOException;
  • queue:队列名称。
  • autoAck:是否自动回复。如果设置为false,那么需要在消费完消息后,手动执行channel.basicAck方法来确认消息已被成功接收。

6.2 推模式

在RabbitMQ的java客户端中,推模式有很多的重载方法,方法名basicConsume,下面拿一个方法举例说明:

String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, Map<String, Object> arguments, Consumer callback) throws IOException;
  • queue:队列的名称。
  • autoAck:是否自动确认,建议设成false,即不自动确认。
  • consumerTag:消费者标签,用来区分多个消费者。
  • noLocal:设置为true则表示不能将同一个连接中生产者发送的消息传递给这个连接的消费者。
  • exclusive:是否排他。
  • arguments:消费者的其它参数。
  • callback:设置消费者的回调函数,用来处理RabbitMQ推送过来的消息,比如DefaultConsumer,使用时需要客户端重写其中的方法。

Basic.Consume将信道置为接收模式,直到取消队列的订阅为止。在接收模式期间,RabbitMQ会不断地推送消息给消费者,当然推送消息的个数会受到Basic.Qos的限制。如果只想从队列中获得单条消息而不是持续订阅,建议还是使用Basic.Get进行消费。但是不能将Basic.Get放在一个循环里来代替Basic.Consume,这样会严重影响RabbitMQ的性能。

7. 消息的确认与拒绝

7.1 消息确认

void basicAck(long deliveryTag, boolean multiple) throws IOException;
  • multiple:如果为false,则表示确认编号为deliveryTag的这一条消息。如果为true,则表示确认deliveryTag编号之前所有未被当前消费者确认的消息。测试代码如下:
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    System.out.println("deliveryTag:" + envelope.getDeliveryTag() + ",message:" + new String(body));
    if ("ackOne".equals(new String(body))) {
        channel.basicAck(envelope.getDeliveryTag(), false);
    } else if ("ackAll".equals(new String(body))) {
        channel.basicAck(envelope.getDeliveryTag(), true);
    }
}

为了保证消息从队列可靠地达到消费者,RabbitMQ提供了消息确认机制。消费者在订阅队列时,可以指定autoAck参数,当autoAck等于false时,RabbitMQ会等待消费者显式地回复确认信号后才从内存(或者磁盘)中移除消息(实质上先打上删除标记,之后再删除)。这种情况下,消费者有足够的时间处理消息,不用担心处理消息过程中消费者进程挂掉后消息丢失的问题。

autoAck为true时,RabbitMQ会自动把发送出去的消息置为确认,然后从内存(或者磁盘)中移除消息,而不管消费者是否真正地消费到了这些消息。

autoAck参数置为false,对于RabbitMQ服务端而言,队列中的消息分成两个部分:一部分是等待投递给消费者的消息;一部分是已经投递给消费者,但是还没有收到消费者确认信号的消息。如果RabbitMQ一直没有收到消费者的确认信号,并且消费此消息的消费者已经断开连接,则RabbitMQ会安排该消息重新进入队列,等待投递给下一个消费者,当然也有可能还是原来的那个消费者。

RabbitMQ不会为未确认的消息设置过期时间,它判断此消息是否需要重新投递给消费者的唯一依据是消费该消息的消费者连接是否已经断开,这么设计的原因是RabbitMQ允许消费者消费一条消息的时间可以很久。

7.2 消息拒绝

void basicReject(long deliveryTag, boolean requeue) throws IOException;
  • requeue:是否需要重回队列,如果设置为false,则RabbitMQ会直接将消息从队列中移除,而不会把它发送给其它消费者。

basicReject命令一次只能拒绝一条消息,如果想要批量拒绝消息,则可以使用:

void basicNack(long deliveryTag, boolean multiple, boolean requeue)
            throws IOException;
  • multiple:如果为false,则表示拒绝编号为deliveryTag的这一条消息,此时basicNackbasicReject方法一样。如果为true,则表示拒绝deliveryTag编号之前所有未被当前消费者确认的消息。

8. 关闭连接

在应用程序使用完之后,需要关闭连接,释放资源:

channel.close();
connection.close();

ConnectionChannel所具备的声明周期如下:

  • Open:开启状态,代表当前对象可以使用。
  • Closing:正在关闭状态。
  • Closed:已经关闭状态。ConnectionChannel最终都是会成为该状态,不论是程序正常调用的关闭,或者是客户端的异常,再或者是发生了网络异常。

通道关闭时的回调方法:

channel.addShutdownListener(new ShutdownListener() {
    @Override
    public void shutdownCompleted(ShutdownSignalException cause) {
        // 返回值为true表示Connection异常,false表示Channel异常。
        boolean isConnectionError = cause.isHardError();
        if (isConnectionError) {
            Connection conn = (Connection) cause.getReference();
        } else {
            Channel ch = (Channel) cause.getReference();
        }
        // 获取异常相关信息
        cause.getReason();
    }
});


——End——