RabbitMq客户端开发

巧了我就是萌 提交于 2020-02-18 19:32:43

目录

连接RabbitMQ

使用交换器和队列

exchangeDeclare方法详解

QueueDeclare方法详解

queueBind方法详解

exchangeBind方法详解

何时创建

发送消息

消费消息

推模式

拉模式

消费端的确认与拒绝

关闭连接


连接RabbitMQ

下面的代码(代码清单)用来在给定的参数(IP地址、端口号、用户名、密码等)下 连接RabbitMQ:

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

Connection可以用来创建多个Channel实例,但是Channel实例不能在线程间共享, 应用程序应该为每一个线程开辟一个Channel。某些情况下Channel的操作可以并发运行,但 是在其他情况下会导致在网络上出现错误的通信帧交错,同时也会影响发送方确认(publisher confmn)机制的运行(详细可以参考4.8节),所以多线程间共享Channel实例是非线程安全的

Channel或者Connection中有个isOpen方法可以用来检测其是否已处于开启状态。但并不推荐在生产环境的代码上使用 isOpen方法,这个方法的返回值依赖于shutdownCause (参考下面的代码)的存在,有可能会产生竞争,代码清单是isOpen方法的源码: 

错误地使用isOpen方法示例代码如代码清单所示。

通常情况下,在调用createXXX或者newXXX方法之后,我们可以简单地认为 Connection或者Channel已经成功地处于开启状态,而并不会在代码中使用isOpen这个 检测方法。如果在使用Channel的时候其已经处于关闭状态,那么程序会抛出一个 com.rabbitmq.client.ShutdownSignalException, 我们只需捕获这个异常即可。当 然同时也要试着捕获IOException或者SocketException, 以防Connection意外关闭。 示例代码如代码消单所示。

使用交换器和队列

交换器和队列是AMQP中high-level层面的构建模块,应用程序需确保在使用它们的时候 就已经存在了,在使用之前需要先声明(declare)它们。

代码清单演示了如何声明一个交换器和队列:

上面创建了一个持久化的、非自动删除的、绑定类型为direct的交换器,同时也创建了一 个非持久化的、排他的、自动删除的队列(此队列的名称由RabbitMQ自动生成)。这里的交换 器和队列也都没有设置特殊的参数。

上面的代码也展示了如何使用路由键将队列和交换器绑定起来。上面声明的队列具备如下 特性:只对当前应用中同一个Connection层面可用,同一个Connection的不同Channel 可共用,并且也会在应用连接断开时自动删除。

如果要在应用中共享一个队列,可以做如下声明,如代码清单所示。

这里的队列被声明为持久化的、非排他的、非自动删除的,而且也被分配另一个确定的已 知的名称(由客户端分配而非RabbitMQ自动生成)。

注意: Channel的API方法都是可以重载的,比如exchangeDeclare、queue Declare。

根据参数不同,可以有不同的重载形式,根据自身的需要进行调用。

生产者和消费者都可以声明一个交换器或者队列。如果尝试声明一个已经存在的交换器或 者队列,只要声明的参数完全匹配现存的交换器或者队列,RabbitMQ就可以什么都不做,并成 功返回。如果声明的参数不匹配则会抛出异常。

exchangeDeclare方法详解

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

这个方法的返回值是Exchange.DeclareOK, 用来标识成功声明了一个交换器。 各个参数详细说明如下所述。

exchange: 交换器的名称。

type: 交换器的类型,常见的如fanout、direct、topic。

durable: 设置是否持久化。durable设置为true表示持久化,反之是非持久化。持 久化可以将交换器存盘,在服务器重启的时候不会丢失相关信息。

autoDelete: 设置是否自动删除。autoDelete设置为true则表示自动删除。自动 删除的前提是至少有一个队列或者交换器与这个交换器绑定,之后所有与这个交换器绑 定的队列或者交换器都与此解绑。注意不能错误地把这个参数理解为:“当与此交换器 连接的客户端都断开时,RabbitMQ会自动删除本交换器”。

internal: 设置是否是内置的。如果设置为true, 则表示是内置的交换器,客户端程 序无法直接发送消息到这个交换器中,只能通过交换器路由到交换器这种方式。

argument: 其他一些结构化参数,比如alternate-exchange。

exchangeDeclare的其他重载方法如下:

(1) Exchange.DeclareOk exchangeDeclare(String exchange, String type) throws IOException;

(2) Exchange.DeclareOk exchangeDeclare(String exchange, String type, boolean durable) throws IOException;

(3) Exchange.DeclareOk exchangeDeclare(String exchange, String type, boolean durable, boolean autoDelete, Map<String, Object> arguments) throws IOException;

与此对应的,将第二个参数String type换成BuiltinExchangeType type对应的 几个重载方法(不常用):

(1) Exchange.DeclareOk exchangeDeclare(String exchange, BuiltinExchangeType type) throws IOException;

(2) Exchange.DeclareOk exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable) throws IOException;

(3) Exchange.DeclareOk exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete, Map<String, Object> arguments) throws IOException;

(4) Exchange.DeclareOk exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete, boolean internal, Map<String, Object> arguments) throws IOException;

与exchange Declare师出同门的还有几个方法,比如exchangeDeclareNoWait方法, 具体定义如下(当然也有BuiltExchangeType版的,这里就不展开了):

这个exchangeDeclareNoWait比exchangel)eclare多设置了一个nowait参数,这个nowait参数指的是AMQP中:Exchange.Declare命令的参数,意思是不需要服务器返 回,注意这个方法的返回值是VOID,而普通的exchangeDeclare方法的返回值是 :Exchange. DeclareOk, 意思是在客户端声明了一个交换器之后,需要等待服务器的返回(服 务器会返回:Exchange.Declare-Ok这个AMQP命令)。

针对"exchangeDeclareNoWait不需要服务器任何返回值”这一点,考虑这样一种情况, 在声明完一个交换器之后(实际服务器还并未完成交换器的创建),那么此时客户端紧接着使用 这个交换器,必然会发生异常。如果没有特殊的缘由和应用场景,并不建议使用这个方法。

这里还有师出同门的另一个方法exchangeDeclarePassive, 这个方法的定义如下:

Exchange.DeclareOk exchangeDeclarePassive(String name) throws IOException;

这个方法在实际应用过程中还是非常有用的,它主要用来检测相应的交换器是否存在。如果 存在则正常返回;如果不存在则抛出异常: 404 channel exception, 同时Channel也会被关闭。

有声明创建交换器的方法,当然也有删除交换器的方法。相应的方法如下:

(1) Exchange.DeleteOk exchangeDelete(String exchange)throws IOException;

(2) void exchangeDeleteNoWait(String exchange, boolean ifUnused) throws IOException;

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

其中exchange表示交换器的名称,而ifUnused用来设置是否在交换器没有被使用的情 况下删除。如果isUnused设置为true, 则只有在此交换器没有被使用的情况下才会被删除; 如果设置false, 则无论如何这个交换器都要被删除。

QueueDeclare方法详解

QueueDeclare相对于exchangeDeclare方法而言,重载方法的个数就少很多,它只 有两个重载方法:

(1) Queue.DeclareOk queueDeclare() throws IOException;

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

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

方法的参数详细说明如下所述。

queue: 队列的名称。

durable: 设置是否持久化。为true则设置队列为持久化。待久化的队列会存盘,在 服务器重启的时候可以保证不丢失相关信息。

exclusive: 设置是否排他。为true则设置队列为排他的。如果一个队列被声明为排 他队列,该队列仅对首次声明它的连接可见,并在连接断开时自动删除。这里需要注意 三点:排他队列是基于连接(Connection)可见的,同一个连接的不同信道(Channel) 是可以同时访问同一连接创建的排他队列;“首次“是指如果一个连接已经声明了一个 排他队列,其他连接是不允许建立同名的排他队列的,这个与普通队列不同;即使该队 列是持久化的,一旦连接关闭或者客户端退出,该排他队列都会被自动删除,这种队列 适用于一个客户端同时发送和读取消息的应用场景。

autoDelete: 设置是否自动删除。为true则设置队列为自动删除。自动删除的前提是: 至少有一个消费者连接到这个队列,之后所有与这个队列连接的消费者都断开时,才会 自动删除。不能把这个参数错误地理解为:“当连接到此队列的所有客户端断开时,这 个队列自动删除“,因为生产者客户端创建这个队列,或者没有消费者客户端与这个队 列连接时,都不会自动删除这个队列。

arguments: 设置队列的其他一些参数,如x-message-ttl、x-expires、 x-max-length、x-max-length-bytes、x-dead-letter-exchange、x-dead­letter-routing-key、x-max-priority等。

注意要点:

生产者和消费者都能够使用queueDeclare来声明一个队列,但是如果消费者在同一个 信道上订阅了另一个队列,就无法再声明队列了。必须先取消订阅,然后将信道置为“传输"模式,之后才能声明队列。

对应于exchangeDeclareNoWait方法,这里也有一个queueDeclareNoWait方法:

void queueDeclareNoWait(String queue, boolean durable, boolean exclusive,

boolean autoDelete, Map<String, Object> arguments) throws IOException;

方法的返回值也是VOID,表示不需要服务端的任何返回。同样也需要注意,在调用完 queueDeclareNoWait方法之后,紧接着使用声明的队列时有可能会发生异常情况。

同样这里还有一个queueDeclarePassive的方法,也比较常用。这个方法用来检测相 应的队列是否存在。如果存在则正常返回,如果不存在则抛出异常: 404 channel exception, 同 时Channel也会被关闭。方法定义如下:

Queue.DeclareOk queueDeclarePassive(String queue) throws IOException;

与交换器对应,关于队列也有删除的相应方法:

(1) Queue.DeleteOk queueDelete(String queue) throws IOException;

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

(3) void queueDeleteNoWait(String queue, boolean ifUnused, boolean ifEmpty) throws IOException;

其中queue表示队列的名称,ifUnused可以参考上一小节的交换器。ifEmpty设置为 true表示在队列为空(队列里面没有任何消息堆积)的清况下才能够删除。

与队列相关的还有一个有意思的方法 queuePurge, 区别于queueDelete, 这个方 法用来清空队列中的内容,而不删除队列本身,具体定义如下:

Queue.PurgeOk queuePurge(String queue) throws IOException;

queueBind方法详解

将队列和交换器绑定的方法如下,可以与前两节中的方法定义进行类比。

(1) Queue.BindOk queueBind(String queue, String exchange, String routingKey) throws IOE:xception;

(2) Queue.BindOk queueBind(String queue, String exchange, String routingKey, Map<String, Object> arguments) throws IOException;

(3) void queueBindNoWait(String queue, String exchange, String routingKey, Map<String, Object> arguments) throws IOException;

方法中涉及的参数详解。

queue: 队列名称;

exchange: 交换器的名称;

routingKey: 用来绑定队列和交换器的路由键;

argument: 定义绑定的一些参数。

不仅可以将队列和交换器绑定起来,也可以将已经被绑定的队列和交换器进行解绑。具体 方法可以参考如下(具体的参数解释可以参考前面的内容,这里不再赘述):

(1) Queue. UnbindOk queue Unbind (String queue, String exchange, String routingKey) throws IOException;

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

exchangeBind方法详解

我们不仅可以将交换器与队列绑定,也可以将交换器与交换器绑定,后者和前者的用法如 出一辙,相应的方法如下:

(1) Exchange.BindOk exchangeBind(String destination, String source, String routingKey) throws IOException;

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

(3) void exchangeBindNoWait(String destination, String source, String routingKey, t>lap<String, Object> arguments) throws IOException;

方法中的参数可以参考的exchangeDeclare方法。绑定之后,消息从source交 换器转发到destination交换器,某种程度上来说destination交换器可以看作一个队列。示例代 码如代码清单所示。

生产者发送消息至交换器source中,交换器source根据路由键找到与其匹配的另一个交换 器destination, 并把消息转发到destination中,进而存储在destination绑定的队列queue中,可 参考图

何时创建

RabbitMQ的消息存储在队列中,交换器的使用并不真正耗费服务器的性能,而队列会。如果要衡量RabbitMQ当前的QPS只需看队列的即可。在实际业务应用中,需要对所创建的队列 的流量、内存占用及网卡占用有一个清晰的认知,预估其平均值和峰值,以便在固定硬件资源 的情况下能够进行合理有效的分配。

按照RabbitMQ官方建议,生产者和消费者都应该尝试创建(这里指声明操作)队列。这是 一个很好的建议,但不适用于所有的情况。如果业务本身在架构设计之初已经充分地预估了队列 的使用情况,完全可以在业务程序上线之前在服务器上创建好(比如通过页面管理、RabbitMQ 命令或者更好的是从配置中心下发),这样业务程序也可以免去声明的过程,直接使用即可。

预先创建好资源还有一个好处是,可以确保交换器和队列之间正确地绑定匹配。很多时候, 由于人为因素、代码缺陷等,发送消息的交换器并没有绑定任何队列,那么消息将会丢失;或者 交换器绑定了某个队列,但是发送消息时的路由键无法与现存的队列匹配,那么消息也会丢失。 当然可以配合mandatory参数或者备份交换器来提高程序的健壮性。

与此同时,预估好队列的使用情况非常重要,如果在后期运行过程中超过预定的阙值,可 以根据实际情况对当前集群进行扩容或者将相应的队列迁移到其他集群。迁移的过程也可以对 业务程序完全透明。此种方法也更有利于开发和运维分工,便于相应资源的管理。

如果集群资源充足,而即将使用的队列所占用的资源又在可控的范围之内,为了增加业务 程序的灵活性,也完全可以在业务程序中声明队列。

至于是使用预先分配创建资源的静态方式还是动态的创建方式,需要从业务逻辑本身、公 司运维体系和公司硬件资源等方面考虑。

发送消息

如果要发送一个消息,可以使用Channel类的basicPublish方法,比如发送一条内容 为"Hello World!"的消息,参考如下:

为了更好地控制发送,可以使用mandatory这个参数,或者可以发送一些特定属性的信息:

上面这行代码发送了一条消息,这条消息的投递模式(delivery mode)设置为2, 即消息会 被待久化(即存入磁盘)在服务器中。同时这条消息的优先级(priority)设置为1, content-type 为"text/plain"。可以自己设定消息的属性:

以上只是举例,由于篇福关系,这里就不一一列举所有的可能情形了。对于basicPublish 而言,有几个重载方法:

(1) void basicPublish (String exchange, String routingKey, BasicProperties props, byte[] body) throws IOException;

(2) void hasic:Puhlish(String exchange, String routingKey, hoolean mandatory,BasicProperties props, byte[) body) throws IOException;

(3) void basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate, BasicPro:perties :pro:ps, byte[] body) throws IOExce:ption;

对应的具体参数解释如下所述。

exchange: 交换器的名称,指明消息需要发送到哪个交换器中。如果设置为空字符串, 则消息会被发送到RabbitMQ默认的交换器中。

routingKey: 路由键,交换器根据路由键将消息存储到相应的队列之中。

props: 消息的基本属性集,其包含14个属性成员,分别有content Type、 contentEncoding、headers(Map<String,Object:>)、deliveryMod.e、Priority、 correlation Id.、replyTo、expiration、rnessageid、times tamp、type、userid、 appid.、cluster Id.。其中常用的几种都在上面的示例中进行了演示。

byte [ ] body: 消息体(payload), 真正需要发送的消息。

消费消息

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

推模式

在推模式中,可以通过持续订阅的方式来消费消息,使用到的相关类有:

import com.rabbitmq.client.Consumer;

import com.rabbitmq.client.DefaultConsumer;

接收消息一般通过实现Consumer接口或者继承DefaultConsumer类来实现。当调用与Consumer相关的API方法时,不同的订阅采用不同的消费者标签(consumerTag)来区 分彼此,在同一个Channel中的消费者也需要通过唯一的消费者标签以作区分,关键消费代 码如代码清单所示。

注意,上面代码中显式地设置autoAck为false, 然后在接收到消息之后进行显式ack操作(channel .basicAck), 对于消费者来说这个设置是非常必要的,可以防止消息不必要地 丢失。

Channel类中basicConsurne方法有如下几种形式:

(1) String basicConsume(String queue, Consumer callback} throws IOException;

(2) String basicConsume(String queue, boolean autoAck, Consumer callback) throws IOException;

(3) String basicConsume(String queue, boolean autoAck, Map<String, Object> arguments, Consumer callback) throws IOException;

(4) String basicConsume(String queue, boolean autoAck, String consumerTag, Consumer callback) throws IQException;

(5) String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, Map<String, Object> arguments, Consumer callback) throws工OException;

其对应的参数说明如下所述。

queue: 队列的名称;

autoAck: 设置是否自动确认。建议设成false, 即不自动确认; 令consumerTag: 消费者标签,用来区分多个消费者;

noLocal: 设置为true则表示不能将同一个Connection中生产者发送的消息传送给

这个Connection中的消费者;

exclusive: 设置是否排他;

arguments: 设置消费者的其他参数;

callback: 设置消费者的回调函数。用来处理RabbitMQ推送过来的消息,比如 DefaultConsumer, 使用时需要客户端重写(override)其中的方法。

对于消费者客户端来说重写handleDelivery方法是十分方便的。更复杂的消费者客户 端会重写更多的方法,具体如下:

void handleConsumeOk{String consumerTag); void handleCancelOk(String consumerTag);

void handleCancel{String consumerTag) throws IOException;

void handleShutdownSignal{String consumerTag, ShutdownSignalException sig); void handleRecoverOk{String consumerTag);

比如handleShutdownSignal方法当Channel或者Connection关闭的时候会调用。

再者,handleConsumeOk方法会在其他方法之前调用,返回消费者标签。

重写handleCancelOk和handleCancel方法,这样消费端可以在显式地或者隐式地取 消订阅的时候调用。也可以通过channel.basicCancel方法来显式地取消一个消费者的订阅:

channel.basicCancel(consumerTag);

注意上面这行代码会首先触发handleConsumerOk方法,之后触发handleDelivery 方法,最后才触发handleCancelOk方法。

和生产者一样,消费者客户端同样需要考虑线程安全的问题。消费者客户端的这些callback 会被分配到与Channel不同的线程池上,这意味着消费者客户端可以安全地调用这些阻塞方

法,比如channel.queueDeclare、channel.basicCancel等。

每个Channel都拥有自己独立的线程。最常用的做法是一个Channel对应一个消费者, 也就是意味着消费者彼此之间没有任何关联。当然也可以在一个Channel中维待多个消费者, 但是要注意一个问题,如果Channel中的一个消费者一直在运行,那么其他消费者的callback 会被“耽搁"。

拉模式

这里讲一下拉模式的消费方式。通过channel.basicGet方法可以单条地获取消息,其 返回值是GetRespone。Channel类的basicGet方法没有其他重载方法,只有:

GetResponse basicGet(String queue, boolean autoAck) throws IOException;

其中queue代表队列的名称,如果设置autoAck为false, 那么同样需要调用 channel.basicAck来确认消息已被成功接收。

拉模式的关键代码如代码清单所示。

这里采用的拉模式的消费方式如图所示(只 展示消费的部分)。

注意要点:

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

消费端的确认与拒绝

为了保证消息从队列可靠地达到消费者,RabbitMQ提供了消息确认机制(message acknowledgement)。消费者在订阅队列时,可以指定autoAck参数,当autoAck等于false 时,RabbitMQ会等待消费者显式地回复确认信号后才从内存(或者磁盘)中移去消息(实质上 是先打上删除标记,之后再删除)。当autoAck等于true时,RabbitMQ会自动把发送出去的 消息置为确认,然后从内存(或者磁盘)中删除,而不管消费者是否真正地消费到了这些消息。

采用消息确认机制后,只要设置autoAck参数为false, 消费者就有足够的时间处理消息 (任务),不用担心处理消息过程中消费者进程挂掉后消息丢失的问题,因为RabbitMQ会一直 等待持有消息直到消费者显式调用Basic.Ack命令为止。

当autoAck参数置为false, 对于RabbitMQ服务端而言,队列中的消息分成了两个部分:

一部分是等待投递给消费者的消息;

一部分是已经投递给消费者,但是还没有收到消费者确认 信号的消息。如果RabbitMQ一直没有收到消费者的确认信号,并且消费此消息的消费者已经 断开连接,则RabbitMQ会安排该消息重新进入队列,等待投递给下一个消费者,当然也有可 能还是原来的那个消费者。

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

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

在消费者接收到消息后,如果想明确拒绝当前的消息而不是确认,那么应该怎么做呢? RabbitMQ在2.0.0版本开始引入了Basic.Reject这个命令,消费者客户端可以调用与其对 应的channel.basicReject方法来告诉RabbitMQ拒绝这个消息。

Channel类中的basicReject方法定义如下:

void basicReject(long deliveryTag, boolean requeue) throws IOException;

其中deliveryTag可以看作消息的编号,它是一个64位的长整型值,最大值是 9223372036854775807。如果requeue参数设置为true, 则RabbitMQ会重新将这条消息存入 队列,以便可以发送给下一个订阅的消费者;如果requeue参数设置为false, 则RabbitMQ 立即会把消息从队列中移除,而不会把它发送给新的消费者。

Basic.Reject命令一次只能拒绝一条消息,如果想要批量拒绝消息,则可以使用 Basic.Nack这个命令。消费者客户端可以调用channel.basicNack方法来实现,方法定 义如下:

void basicNack(long deliveryTag, boolean mult:iple, boolean requeue) throwsIOException;

其中deliveryTag和requeue的含义可以参考basicReject方法。multiple参数 设置为false则表示拒绝编号为deliveryTag的这一条消息,这时候basicNack和 basicReject方法一样; multiple参数设置为true则表示拒绝deliveryTag编号之前所 有未被当前消费者确认的消息。

注意要点:

将channel.basicReject或者channel.basicNack中的requeue设置为false, 可 以启用“死信队列"的功能。死信队列可以通过检测被拒绝或者未送达的消息来追踪问题。

对于requeue, AMQP中还有一个命令Basic.Recover具备可重入队列的特性。其对 应的客户端方法为:

(1) Basic.RecoverOk basicRecover() throws IOExcertion;

(2) Basic.RecoverOk basicRecover(boolean requeue) throws IOException;

这个channel.basicRecover方法用来请求RabbitMQ重新发送还未被确认的消息。如 果requeue参数设置为true, 则未被确认的消息会被重新加入到队列中,这样对于同一条消息 来说,可能会被分配给与之前不同的消费者。如果requeue参数设置为false, 那么同一条消 息会被分配给与之前相同的消费者。默认情况下,如果不设置requeue这个参数,相当于 channel.basicRecover(true), 即requeue默认为true。

关闭连接

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

channel.close();

conn.close();

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

AMQP协议中的Connection和Channel采用同样的方式来管理网络失败、内部错误和 显式地关闭连接。Connection和Channel所具备的生命周期如下所述。

Open: 开启状态,代表当前对象可以使用。

Closing: 正在关闭状态。当前对象被显式地通知调用关闭方法(shutdown), 这样 就产生了一个关闭请求让其内部对象进行相应的操作,并等待这些关闭操作的完成

Closed: 已经关闭状态。当前对象已经接收到所有的内部对象已完成关闭动作的通知, 并且其也关闭了自身。

Connection和Channel最终都是会成为Closed的状态,不论是程序正常调用的关闭 方法,或者是客户端的异常,再或者是发生了网络异常。

在Connection和Channel中,与关闭相关的方法有addShutdownListener (ShutdownListener listener)和remove ShutdownListener (ShutdownListner tistener)。当Connection或者Channel的状态转变为Closed的时候会调用 ShutdownListener。而且如果将一个ShutdownListener注册到一个已经处于Closed 状态的对象(这里特指Connection和Channel对象)时,会立刻调用ShutdownListener.getCloseReason方法可以让你知道对象关闭的原因;

isOpen方法检测对象当前是否处 于开启状态; close (int closeCode, String closeMessage)方法显式地通知当前对象 执行关闭操作。

有关ShutdownListener的使用可以参考代码清单。

当触发ShutdownListener的时候,就可以获取到ShutdownSignalException, 这个ShutdownSignalException包含了关闭的原因,这里原因也可以通过调用前面所提及的getCloseReason方法获取。

ShutdownSignalException提供了多个方法来分析关闭的原因。isHardError方法 可以知道是Connection的还是Channel的错误; getReason方法可以获取cause相关的 信息,相关示例可以参考代码清单。

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!