二、RabbitMQ 入门

一、概念介绍

RabbitMQ整体上是一个生产者与消费者模型,主要负责接收、存储和转发消息。可以把消 息传递的过程想象成:当你将一个包裹送到邮局,邮局会暂存并最终将邮件通过邮递员送到收 件人的手上,RabbitMQ就好比由邮局、邮箱和邮递员组成的一个系统。从计算机术语层面来说, RabbitMQ模型更像是一种交换机模型。

如图

1.1  生产者和消费者

Producer:

生产者,就是投递消息的一方。生产者创建消息,然后发布到RabbitMQ中。

消息一般可以包含2个部分:消息体和标签 (Label)。

消息体也可以称之为payload,在实际应用中,消息体一般是一个带有业务逻辑结构 的数据,比如一个JSON字符串。当然可以进一步对这个消息体进行序列化操作。

标签用来表述这条消息,比如一个交换器的名称和一个路由键。生产者把消息交由RabbitMQ, RabbitMQ之后会根据标签把消息发送给感兴趣的消费者(Consumer)。

Consumer:

消费者,就是接收消息的一方。

消费者连接到RabbitMQ服务器,并订阅到队列上。当消费者消费一条消息时,只是消费 消息的消息体(payload)。在消息路由的过程中,消息的标签会丢弃,存入到队列中的消息只 有消息体,消费者也只会消费到消息体,也就不知道消息的生产者是谁,当然消费者也不需要 知道。

Broken:

消息中间件的服务节点。

对于RabbitMQ来说,一个RabbitMQ Broker可以简单地看作一个RabbitMQ服务节点, 或者RabbitMQ服务实例。大多数情况下也可以将一个RabbitMQ Broker看作台RabbitMQ服务器。

基本流程

首先生产者将业务方数据进行可能的包装,之后封装成消息,发送(AMQP协议里这个动 作对应的命令为Basic. Publish)到Broker中。消费者订阅并接收消息(AMQP协议里这个动作对应的命令为Basic. Consume或者Basic. Get ),经过可能的解包处理得到原始的数据, 之后再进行业务处理逻辑。这个业务处理逻辑并不一定需要和接收消息的逻辑使用同一个线程。 消费者进程可以使用一个线程去接收消息,存入到内存中,比如使用Java中的BlockingQueue。 业务处理逻辑使用另一个线程从内存中读取数据,这样可以将应用进一步解耦,提高整个应用 的处理效率。

 

1.2 队列

Queue:队列,是RabbitMQ的内部对象,用于存储消息

RabbitMQ中消息都只能存储在队列中,这一点和Ka&a这种消息中间件相反。Kafka将消 息存储在topic (主题)这个逻辑层面,而相对应的队列逻辑只是topic实际存储文件中的位移 标识。RabbitMQ的生产者生产消息并最终投递到队列中,消费者可以从队列中获取消息并消费。

多个消费者可以订阅同一个队列,这时队列中的消息会被平均分摊(Round-Robin,即轮询) 给多个消费者进行处理,而不是每个消费者都收到所有的消息并处理

RabbitMQ不支持队列层面的广播消费,如果需要广播消费,需要在其上进行二次开发,处理逻辑会变得异常复杂,同时也不建议这么做。

1.3 交换器、路由键、绑定

Exchange:交换器

在图2-4中我们暂时可以理解成生产者将消息投递到队列中,实际上 这个在RabbitMQ中不会发生。真实情况是,生产者将消息发送到Exchange (交换器,通常也 可以用大写的“X”来表示),由交换器将消息路由到一个或者多个队列中。如果路由不到,或 许会返回给生产者,或许直接丢弃。

RabbitMQ中的交换器有四种类型,不同的类型有着不同的路由策略。

RoutingKey:路由键

生产者将消息发给交换器的时候,一般会指定一个RoutingKey,用 来指定这个消息的路由规则,而这个Routing Key需要与交换器类型和绑定键(BindingKey)联合使用才能最终生效。

在交换器类型和绑定键(BindingKey)固定的情况下,生产者可以在发送消息给交换器时, 通过指定RoutingKey来决定消息流向哪里

Binding:绑定

RabbitMQ中通过绑定将交换器与队列关联起来,在绑定的时候一般会指定一个绑定键(BindingKey),这样RabbitMQ就知道如何正确地将消息路由到队列了

联系

生产者将消息发送给交换器时,需要一个RoutingKey,当BindingKey和RoutingKey相匹配时,消息会被路由到对应的队列中。在绑定多个队列到同一个交换器的时候,这些绑定允许 使用相同的BindingKey。BindingKey并不是在所有的情况下都生效,它依赖于交换器类型,比 如fanout类型的交换器就会无视BindingKey,而是将消息路由到所有绑定到该交换器的队列中

 

1.4交换器类型

RabbitMQ常用的交换器类型有fanout、direct、topic、headers这四种。AMQP协议里还提 到另外两种类型:System和自定义,这里不予描述。对于这四种类型下面一一阐述。

fanout

它会把所有发送到该交换器的消息路由到所有与该交换器绑定的队列中

direct

direct类型的交换器路由规则也很简单,它会把消息路由到那些BindingKey和RoutingKey完全匹配的队列中。

topic

topic类型的交换器在匹配规则上进行了 扩展,它与direct类型的交换器相似,也是将消息路由到BindingKey和RoutingKey相匹配的队 列中,但这里的匹配规则有些不同,它约定:

1.RoutingKey为一个点号”分隔的字符串(被点号”分隔开的每一段独立的字符 串称为个单词),如“com.rabbitmq.client'“java.util.concurrent”、“com.hidden.client”;

2.BindingKey和RoutingKey—样也是点号分隔的字符串

3.BindingKey中可以存在两种特殊字符串“*”和“#”,用于做模糊匹配,其中“*”用于匹配一个单词,“#”用于匹配多规格单词(可以是零个)。

 

headers

headers类型的交换器不依赖于路由键的匹配规则来路由消息,而是根据发送的消息内容中 的headers属性进行匹配。在绑定队列和交换器时制定一组键值对,当发送消息到交换器时, RabbitMQ会获取到该消息的headers (也是一个键值对的形式),对比其中的键值对是否完全 匹配队列和交换器绑定时指定的键值对,如果完全匹配则消息会路由到该队列,否则不会路由 到该队列。headers类型的交换器性能会很差,而且也不实用,基本上不会看到它的存在。

 

1.5 RabbitMQ 运转流程

     生产者发送消息的时候:

  1. 生产者连接到RabbitMQ Broker,建立一个连接(Connection),开启一个信道(Channel)
  2. 生产者声明一个交换器,并设置相关属性,比如交换机类型、是否持久化等
  3. 生产者声明一个队列并设置相关属性,比如是否排他、是否持久化、是否自动删除等 
  4. 生产者通过路由键将交换器和队列绑定起来
  5. 生产者发送消息至RabbitMQ Broker,其中包含路由键、交换器等信息
  6. 相应的交换器根据接收到的路由键查找相匹配的队列。
  7. 如果找到,则将从生产者发送过来的消息存入相应的队列中。
  8. 如果没有找到,则根据生产者配置的属性选择丢弃还是回退给生产者
  9. 关闭信道。
  10. 关闭连接。

消费者接收消息的过程:

  1. 消费者连接到RabbitMQ Broker,建立一个连接(Connection),开启一个信道(Channel)。
  2. 消费者向RabbitMQ Broker请求消费相应队列中的消息,可能会设置相应的回调函数, 以及做一些准备工作(详细内容请参考3.4节)。
  3. 等待RabbitMQ Broker回应并投递相应队列中的消息,消费者接收消息。
  4. 消费者确认(ack)接收到的消息。
  5. RabbitMQ从队列中删除相应已经被确认的消息。
  6. 关闭信道。
  7. 关闭连接

为什么要引入信道呢?

一个应用程序中有很多个线程需要从RabbitMQ中消费消息,或者生产消息,那 么必然需要建立很多个Connection,也就是许多个TCP连接。然而对于操作系统而言,建立和 销毁TCP连接是非常昂贵的开销,如果遇到使用高峰,性能瓶颈也随之显现。RabbitMQ采用 类似NIO (Non-blocking I/O)的做法,选择TCP连接复用,不仅可以减少性能开销,同时也 便于管理。

每个线程把持一个信道,所以信道复用了 Connection的TCP连接。同时RabbitMQ可以确 保每个线程的私密性,就像拥有独立的连接一样。当每个信道的流量不是很大时,复用单一的 Connection可以在产生性能瓶颈的情况下有效地节省TCP连接资源。但是当信道本身的流量很 大时,这时候多个信道复用一个Connection就会产生性能瓶颈,进而使整体的流量被限制了。 此时就需要开辟多个Connection,将这些信道均摊到这些Connection中,至于这些相关的调优 策略需要根据业务自身的实际情况进行调节。

 

二、使用交换机与队列常用方法与属性介绍

2.1 exchangeDeclare 方法详解

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

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

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

exchange:换器的名称。

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

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

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

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

argument:其他一些结构化参数

 

2.2 queueDeclare 方法详解

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

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

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

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

queue:队列的名称

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

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

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

arguments:设置队列的其他些参数,如x-message-ttlx-expiresx-max-lengthx-max-length-bytes、 x-dead-letter-exchangex-dead- letter-routing-keyx-max-priority 

注意要点:

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

 

2.3 queueBind 方法详解

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

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:用来绑定队列和交换器的路由键

argument:定义绑定的些参数

2.4 exchangeBind 方法详解

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

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;

绑定之后,消息从source交 换器转发到destination交换器,某种程度上来说destination交换器可以看作一个队列。

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

 

2.5 何时创建

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

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

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

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

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

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

 

三、发送消息

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

byte[] messageBodyBytes = "Hello, world!".getBytes();

channel.basicPublish(exchangeName, routingKey, props, messageBodyBytes);

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

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

props:消息的基本属性集,其包含14个属性成员,分别有contentType、 contentEncoding、headers(Map<String,〇bject>)、deliveryMode、priority、 correlationld、replyTo、expiration、messageld、timestamp、type、userId、 appld、clusterld。其中常用的几种都在上面的示例中进行了演示。

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

 

四、消费消息

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

4.1 推模式

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

import com.rabbitmq.client.Consumer; import com.rabbitmq.client.DefaultConsumer;

接收消息一般通过实现Consumer接口或者继承DefaultConsumer类来实现。当调用

Consumer相关的API方法时,不同的订阅米用不同的消费者标签(consumerTag)来区 分彼此,在同一个Channel中的消费者也需要通过唯一的消费者标签以作区分,关键消费代 码

boolean autoAck = false;

channel.basicQos(64);

channel.basicConsume(queueName, autoAck, "myConsumerTag", new DefaultConsumer(channel) {

GOverride

public void handleDelivery(String consumerTag,

Envelope envelope,

AMQP.BasicProperties properties, byte[] body)

throws IOException

{

String routingKey = envelope.getRoutingKey();

String contentType = properties.getContentType(); long deliveryTag = envelope.getDeliveryTag();

// (process the message components here ...) channel.basicAck(deliveryTag, false);

}

})

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

String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, Map<String, Objectarguments, 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 会被“耽搁”。

 

4.2 拉模式

通过channel.basicGet方法可以单条地获取消息,其 返回值是GetResponeChannel类的basicGet方法没有其他重载方法,只有: GetResponse basicGet(String queue, boolean autoAck) throws IOException;

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

拉模式的关键代码如代码清单

GetResponse response = channel.basicGet(QUEUE_NAME, false);

System.out.println(new String(response.getBody()));

channel.basicAck(response.getEnvelope().getDeliveryTag(),false);

注意要点:

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

 

五、消费端的确认与拒绝

5.1 autoAck自动确认消息

为了保证消息从队列可靠地达到消费者,RabbitMQ提供了消息确认机制(message acknowledgement)。消费者在订阅队列时,可以指定autoAck参数

当autoAck=false 时,RabbitMQ会等待消费者显式地回复确认信号后才从内存(或者磁盘)中移去消息(实质上 是先打上删除标记,之后再删除)。

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

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

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

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

5.2 basicReject 拒绝消息

在消费者接收到消息后,如果想明确拒绝当前的消息而不是确认,那么应该怎么做呢? 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 multiple, boolean requeue) throwsIOException;

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

注意要点:

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

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

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

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

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

 

6、关闭连接

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

channel.close ();

conn.close ();

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

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

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

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

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

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

Connection 和 Channel 中,与关闭相关的方法有 addShutdownListener (ShutdownListener listener)removeShutdownListener (ShutdownListner listener)。Connection或者Channel的状态转变为Closed的时候会调用 ShutdownListener而且如果将——个ShutdownListener注册到——个己经处于Closed 状态的对象(这里特指ConnectionChannel对象)时,会立刻调用ShutdownListener

getCloseReason法可以让你知道对象关闭的原因;

isOpen方法检测对象当前是否处 于开启状态

close (int closeCode, String closeMessage)方法显式地通知当前对象 执行关闭操作

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值