4.1 消息何去何从
这里主要介绍mandatory和immediate两个参数
4.1.1 mandatory参数
当mandatory参数设置为true时,交换器无法根据自身当类型和路由键找到一个符合条件的队列,那么RabbitMQ会调用Basic.Return命令将消息返回给生产者,当mandatory为false时,出现上述情形,消息直接丢弃。
那么生产者如何获取到没有被正确路由到合适队列的消息呢?这时候可以通过调用channel.addReturnListener来添加ReturnListener监听实现。
4.1.2 immediate参数
当immediate参数设置为true时,如果交换器在将信息路由到队列的时候发现队列上并不存在任何消费者,则这条消息不入队。当路由键匹配的所有队列都没有消费者时,该消息会通过Basic.Return返回给生产者,即不用将消息存入队列而等待消费者了。
注意:RabbitMQ 3.0版本开始去掉了对 immediate 参数的支持,对此 RabbitMQ 官方解释是: immediate 参数会影响镜像队列的性能 ,增加了 代码复杂性 , 建议采用 TTL 和 DLX 的方法替代。如果发送带 immediate 参数 (immediate 参数设置为 true) 的 Basic.Publish 客户端会报异常。
4.1.3 备份交换器
备份交换器,应为名称为Alternate Exchange,简称AE;当使用mandtory参数时,需要加入addReturnListener逻辑,代码比较复杂,备份交换器就是了解决这个问题而诞生的,为一个交换器A指定一个备份交换器B,交换器A找不到匹配的队列,就会把该消息发送给交换器B,如果交换器B没有匹配到相应的队列,那么该条消息丢失。当然也可以通过策略的方式实现,当二者同时出现的时候,备份交换器的优先级更高。
- 在整个消息路由的过程中,如果找不到相关的队列和交换器,RabbitMQ的客户端和服务端都不会报任何异常
- 备份交换器和mandtory参数一起使用,则mandtory参数无效
4.2 过期时间TTL(Time To Live)
4.2.1 设置消息的TTL
方法一、通过队列设置,队列中所有的消息都有相同的过期时间,一旦该消息过期,该条消息会立马从队列中抹去 ;
方法二、单独设置某条消息的TTL:basic.publish()中加入expiration参数,当消息过期的时候不会立马从队列中抹去,因为消息是否过期是在投递给消费者的时候判定的;
如果方法一和方法二同时使用,则取较小的时间作为TTL,队列中的消息一旦超过TTL,就会变成死信(deal message),消费者将无法接收到该消息(这点也不是绝对的);
4.2.2 设置队列的TTL
通过 channel.queueDeclare方法中的x-expires参数
4.3 死信队列
DLX,全称为dead letter exchange,可以称之为死信交换器,也有人称之为死信邮箱,当一个消息在队列中变为死信,那么就会被重新发送到一个交换器中,那么这个交换器就称之为DLX,绑定DLX的队列就称为死信队列;
消息变成死信一般是有一下几种情况:
- 消息被拒绝,并且requeue参数为false
- 消息过期
- 队列达到最大长度
4.4 延迟队列
消息发送到队列后,不希望立马被消费者消费,而是希望过一段时间以后再被消费,这就是延迟队列;常见 的使用场景:
- 一个订单三十分钟未支付,然后进入其他处理程序
- 用户设定一个时间远程开启家里的电器
事实上,RabbitMQ并没有直接支持延迟队列,但是我们可以通过TTL+DLX来实现。
4.5 优先级队列
队列可以有优先级,队列中的消息也可以有优先级,队列中消息的优先级不能高于队列的优先级,只有当存在消息堆积当的时候,设置优先级才有意义,因为如果消费速度大于生产速度,其实消息都会被立马消费,设置优先级也就没有什么意义了。
4.6 RPC实现
是为了解决回调队列的问题,具体过程如下:
4.7 持久化
持久化主要分为交换器的持久化,队列的持久化,消息的持久化
注意:
可以将所有的消息都设直为持久化,但是这样会严重影响 RabbitMQ 的性能(随机)。写入磁盘的速度比写入内存的速度慢得不只一点点。对于可靠性不是那么高的消息可以不采用持久 化处理以提高整体的吞吐量。在选择是否要将消息持久化时,需要在可靠性和吐吞量之间做一 个权衡。
4.8 生产者确认
生产者如何知道自己的消息是否发送成功?RabbitMQ提供了两种解决方式:
- 事务机制
channel.txSelect():用于将当前的信道置成事务模式;
channel.txCommit():用于事务提交;
channel.txRollback():用于事务回滚;
事务的方式效率非常底下,可能会吸干RabbitMQ的性能,RabbitMQ提供了一个改进方案,即发送方确认机制: - 发送方确认(publisher confirm)
生产者将信道置成confirm(确认)模式,一旦信道置为confirm模式,所有在该信道上面发布的消息都会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配到队列之后,RabbitMQ就会发送一个确认(Basic.Ack)给生产者(包含消息的唯一ID),这就使得生产者知晓消息已经正确到到达目的地了,如果消息队列至可持久化的,那么确认消息会在消息写入磁盘后发出,此外RabbitMQ也可以设置channel.basicAck方法中的multiple参数,表示到这个序号之前的消息都已经得到了处理。
事务机制在一条消息发送之后会使发送端阻塞,以等待RabbitMQ的回应,之后才能继续 发送下一条消息。相比之下, 发送方确认机制最大的好处在于它是异步的,一旦发布一条消息, 生产者应用程序就可以在等信道返回确认的同时继续发送下一条消息,当消息最终得到确认之 后,生产者应用程序便可以通过回调方法来处理该确认消息,如果 RabbitMQ 因为自身内部错 误导致消息丢失,就会发送一条nack(Basic.Nack) 命令,生产者应用程序同样可以在回调 方法中处理该 nack命令。
publisher confmn 的优势在于并不 一 定需要同步确认 ,这里我们改进了 一 下使用方式,总结有如下两种: - 批量confirm
- 异步confirm
4.9 消费端要点介绍
- 消息分发
主要是解决多个消费者消费某一个队列,有的消费者消费很慢,有的消费者消费很快,导致资源浪费的情况,解决方法大概是使用channel.basicQos来限定消费者端可以持有未确认的最多消息数,可以以使用global这个参数。 - 消息顺序性
RabbitMQ不能保证消息的顺序性,只能在业务方自己想办法处理。 - 弃用QueueingConsumer
这是一个过时的消费者,容易导致内存溢出。
4.10 消息传输保障
消息可靠传输一般是业务系统接入消息中间件时首要考虑的问题,一般消息中间件的消息 传输保障分为三个层级:
- At most once:最多 一次。消息可能会丢失 ,但绝不会重复传输 。
- At least once:最少一次。消息绝不会丢失,但可能会重复传输。
- Exactly once:恰好 一次。每条消息肯定会被传输一次且仅传输一次。
目前可以使用上面介绍的知识实现前两种,第三种RabbitMQ无法实现。
4.11 小结
提升数据可靠性有以下一些途径: 设置 mandatory 参数或者备份交换器 (immediate 参数己被陶汰);设置 publisher confirm机制或者事务机制;设置交换器、队列和消息都为持久 化;设置消费端对应的 autoAck 参数为 false 井在消费完消息之后再进行消息确认。本章不仅 介绍了数据可靠性的一些细节,还展示了 RabbitMQ 的几种己具备或者衍生的高级特性,包括 TTL、死信队列、延迟队列、优先级队列、 RPC 功能等,这些功能在实际使用中可以让相应应 用的实现变得事半功倍 。