RabbitMQ学习笔记之进阶篇

消息何去何从

mandatory参数

生产者发送消息时设置的参数。为true时,当交换器无法根据自身的类型和路由键找到一个符合条件的队列,RabbitMQ会把该消息退回给生产者;为false时,出现上述情形,消息会直接被丢弃。

immediate参数

生产者发送消息时设置的参数。为true时,如果交换器将消息路由到队列时发现队列上并不存在任何消费者,那么这条消息将不会存入队列中。当与路由键匹配的所有队列都没有消费者时,该消息会返回给生产者。
概括来说,mandatory参数告诉Broker至少将该消息路由到一个队列中,否则将消息返回给生产者。immediate参数告诉Broker,如果该消息关联的队列上有消费者,则立刻投递;如果所有匹配的队列上都没有消费者,则直接把消息返还给生产者。RabbitMQ 3.0版本开始去掉对该参数的支持,官方说法是它影响镜像队列的性能,增加代码复杂性。

备份交换器

英文名Alternate Exchange,简称AE。申明交换器的时候,可以为该交换器设置备份交换器,当发送到该交换器的消息无法路由到任何队列时,就会发送给备份交换器,备份交换器设置为fanout类型比较合适。对于备份交换器,总结以下几种特殊情况:

  1. 如果设置的备份交换器不存在,客户端和RabbitMQ服务端不会有异常出现,此时消息会丢失;
  2. 如果备份交换器没有绑定任何队列,客户端和RabbitMQ服务端不会有异常出现,此时消息会丢失;
  3. 如果消息通过备份交换器没有路由到任何队列,客户端和RabbitMQ服务端不会有异常出现,此时消息会丢失;
  4. 如果备份交换器和mandatory参数一起使用,那么mandatory参数无效。

过期时间(TTL,Time To Live)

有2种方法可以设置消息的TTL,第一种是通过队列的属性设置,队列中所有消息都有相同的过期时间;第二种是对消息本身进行单独设置,每条消息的TTL可以不同。如果两者同时使用,则消息的过期时间以两者之中的较小的那个为准。消息在队列中的生存时间一旦超过TTL值时,就会变成“死信”(Dead Message)。如果不设置TTL,则表示此消息不会过期;如果将TTL设置为0,则表示除非此时可以直接将消息投递给消费者,否则该消息会立即被丢弃。

死信队列

当消息在一个队列中变成死信之后,它能被重新发送到另外一个交换器中,这个交换器叫DLX(Dead Letter Exchange),绑定DLX的队列就称为死信队列,可以监听该队列中的消息以进行相应的处理,这个特性与将消息的TTL设置为0配合使用可以弥补immediate参数的功能。消息变成死信一般有以下几种情况:

  1. 消息被拒绝(Basic.Reject/Basic.Nack),并且设置requeue参数为false;
  2. 消息过期;
  3. 队列达到最大长度。

对于RabbitMQ来说,死信队列是一个非常有用的特性,它可以处理异常情况下消息不能够被消费者正确消费(消费者调用Basic.Reject/Basic.Nack)而置于死信队列中的情况,后续分析程序可以通过消费这个死信队列中的内容来分析当时所遇到的异常情况,进而可以改善和优化系统。

延迟队列

延迟队列存储的是延迟消息,所谓延迟消息,就是不想让消费者立马拿到而是等待特定的时间后才让消费者拿到的消息。RabbitMQ本身没有直接支持延迟队列的功能,但是可以通过前面介绍的DLX和TTL模拟出延迟队列的功能。基本思路就是给某个队列设置一个TTL值,即延迟时间,当该队列的消息过期之后就进入到死信队列,消费者消费死信队列的消息。

RPC实现

RPC是Remote Procedure Call的简称,即远程过程调用。它是一种通过网络从远程计算机上请求服务,而不需要了解底层网络的技术。
通过RabbitMQ实现RPC很简单,客户端发送请求消息,服务端回复响应的消息,为了接收响应消息,我们需要在请求消息头中带上回调队列。通过replyTo参数指定回调队列,通过correlationId关联请求和调用RPC之后的回复。

持久化

持久化可以提高RabbitMQ的性能,以防在异常情况(重启、关闭、宕机)下的数据丢失。RabbitMQ的持久化分为三部分:交换器的持久化、队列的持久化和消息的持久化。
交换器的持久化是通过在声明队列时将durable参数设置为true实现的。如果交换器不设置持久化,那么在RabbitMQ服务重启之后,相关的交换器元数据会丢失,不过消息不会丢失,只是不能再将消息发送到这个交换器中了。
队列的持久化是通过在声明队列时将durable参数设置为true实现的。如果队列不设置持久化,那么在RabbitMQ服务重启之后,相关队列的元数据和消息都会丢失。
队列的持久化能保证它本身的元数据不会因异常情况而丢失,但是不能保证它内部存储的消息不会丢失,要确保消息不会丢失,需要将消息设置为持久化。通过将消息的投递模式(BasicProperties里的deliveryMode属性)设置为2即可实现消息的持久化。设置了队列和消息的持久化,当RabbitMQ服务重启之后,消息依旧存在。可以将所有消息持久化,但是这样会严重影响RabbitMQ的性能。
将交换器、队列和消息都设置为持久化就能百分之一百保证消息不丢失吗?答案是否定的,具体原因这里就不讨论了。可以引入RabbitMQ的镜像队列机制来进一步保证消息不丢失,相当于配置了副本,主节点挂掉自动切换到从节点。

生产者确认

生产者将消息发出去之后,怎么知道消息到底有没有到达RabbitMQ服务器?针对这个问题,RabbitMQ提供了2种解决方案:

  1. 通过事务机制实现;
  2. 通过发送方确认机制实现。

事务机制

正常情况下是这样四个步骤:

  1. 客户端发送Tx.Select,将信道置为事务模式;
  2. Broker回复Tx.Select-Ok,确认已将信道置为事务模式;
  3. 在发送完消息之后,客户端发送Tx.Commit提交事务;
  4. Broker回复Tx.Commit-Ok,确认事务已提交。

异常情况下进行事务回滚,步骤如下:

  1. 客户端发送Tx.Select,将信道置为事务模式;
  2. Broker回复Tx.Select-Ok,确认已将信道置为事务模式;
  3. 在发送完消息之后,客户端出现异常,发送Tx.Rollback回滚事务;
  4. Broker回复Tx.Rollback-Ok,确认事务已回滚。

发送方确认机制

事务机制会降低RabbitMQ的吞吐量,相比之下发送方确认机制比较轻量级。生产者将信道设置成confirm(确认)模式,一旦信道进入confirm模式,所有在该信道上面发布的消息都会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后,RabbitMQ就会发送一个确认(Basic.Ack)给生产者(包含消息的唯一ID)。如果消息和队列是可持久化的,那么确认消息会在消息写入磁盘之后发出。
事务机制在一条消息发送之后会使发送端阻塞,以等待RabbitMQ的回应,之后才能继续发送下一条消息。相比之下,发送方确认机制最大的好处在于它是异步的,一旦发布一条消息,生产者应用程序就可以在等待信道返回确认的同时继续发送下一条消息。

消费端要点介绍

消息分发

当队列拥有多个消费者时,队列收到的消息将以轮询(round-robin)的分发方式发送给消费者。每条消息只会发送给订阅列表里的一个消费者。
假如每个消费者处理消息的能力不同,比如有的消费者机器配置比较好,有的差,那么轮询就不是那么优雅。RabbitMQ可以限制信道上的消费者所能保持的最大未确认消息的数量,每发送一条信息都会为对应的消费者计数,如果达到了所设定的上限,那么RabbitMQ就不会向这个消费者再发送任何消息,直到消费者确认了某条消息之后,RabbitMQ将相应的计数减1,之后消费者可以继续接收消息,直到再次达到计数上限。

消息顺序性

一般消息中间件的消息传输保障分为三个层级:

  1. At most once:最多一次。消息可能会丢失,但绝对不会重复传输;
  2. At least once:最少一次。消息绝对不会丢失,但可能会重复传输;
  3. Exactly once:恰好一次。每条消息肯定会被传输一次且仅传输一次。

RabbitMQ支持其中的“最多一次”和“最少一次”。其中“最少一次”投递实现需要考虑以下这几个方面的内容:

  1. 消息生产者需要开启事务机制或者发送者确认机制,以确保消息可以可靠的传输到RabbitMQ中。
  2. 消息生产者需要配合使用mandatory参数或者备份交换器来确保消息能够从交换器路由到队列中,进而能够保存下来而不被丢弃。
  3. 消息和队列都需要进行持久化处理,以确保RabbitMQ服务器在遇到异常情况时不会造成消息丢失。
  4. 消费者在消费消息的同时需要将autoAck设置为false,然后通过手动确认的方式去确认已经正确消费的消息,以避免在消费端引起不必要的消息丢失。

参考资料

  1. 朱忠华《RabbitMQ实战指南》
发布了8 篇原创文章 · 获赞 15 · 访问量 953

猜你喜欢

转载自blog.csdn.net/qq_35939417/article/details/104127282