消息何去何从
mandatory 和 immediate 是 channel . basicPublish 方法中的两个参数,它们都有 当消息传递过程中不可达目的地时将消息返回给生产者的功能。
当 mandatory 参数设为 true 时,交换器无法根据自身的类型和路由键找到一个符合条件 的队列,那么 RabbitMQ 会调用 Basic.Return 命令将消息返回给生产者。当 mandatory 参 数设置为 false 时,出现上述情形,则消息直接被丢弃。
生产者通过调用 channel . addReturnListener 来添加 ReturnListener 监昕器实现。
当 imrnediate 参数设为 true 时,如果交换器在将消息路由到队列时发现队列上并不存在 任何消费者,那么这条消息将不会存入队列中。当与路由键匹配的所有队列都没有消费者时, 该消息会通过 Basic . Return 返回至生产者。imrnediate 参数会影响镜像队列的性能, 增加了代码复杂性, 建议采用 TTL 和 DLX 的方法替代。
备份交换器,英文名称为 Altemate Exchange, 生产者在发送消息的时候如果不设置 mandatory 参数, 那么消息在未被路由的情况下将会丢失: 如果设置了 mandatory 参数,那么需要添加 ReturnListener 的编程逻辑,生产者的代码将变得复杂。如果既不想复杂化生产者的编程逻辑,又不想消息丢失,那么可以使用备份交换器, 这样可以将未被路由的消息存储在 RabbitMQ 中,再在需要的时候去处理这些消息。
可以通过在声明交换器(调用 channel.exchangeDeclare 方法)的时候添加 alternate-exchange 参数来实现,也可以通过策略 (Policy)的方式实现。 如果两者同时使用,则前者的优先级更高,会覆盖掉 Policy 的设置。
如果备份交换器和 mandatory 参数一起使用,那么 mandatory 参数无效。
过期时间 (TTL)
设置消息的 TTL
两种方法可以设置消息的 TTL, 第一种方法是通过队列属性设置,队列中所有消息都有相同的过期时间。第二种方法是对消息本身进行单独设置,每条消息的 TTL 可以不同。如 果两种方法一起使用,则消息的 TTL 以两者之间较小的那个数值为准。消息在队列中的生存时间一旦超过设置的 TTL 值时,就会变成"死信" (Dead Message)
1.通过队列属性设置
2.对消息本身进行单独设置
第一种一旦消息过期,就会从队列中抹去,第二种,即使消息过期,也不会马上从队列中抹去,因为每条消息是否过期是在即将投递到消费者之前判定的。
为什么这两种方法处理的方式不一样?因为第一种方法里,队列中己过期的消息肯定在队 列头部, RabbitMQ 只要定期从队头开始扫描是否有过期的消息即可。而第二种方法里,每条消息的过期时间不同,如果要删除所有过期消息势必要扫描整个队列,所以不如等到此消息即将 被消费时再判定是否过期, 如果过期再进行删除即可。
设置队列的 TTL
通过 channel . queueDeclare 方法中的 x-expires 参数可以控制队列被自动删除前处 于未使用状态的时间。未使用的意思是队列上没有任何的消费者,队列也没有被重新声明,并
且在过期时间段内也未调用过 Basic . Get 命令。
RabbitMQ 会确保在过期时间到达后将队列删除,但是不保障删除的动作有多及时 。在 RabbitMQ 重启后, 持久化的队列的过期时间会被重新计算。
Map<String, Object> args =new HashMap<String, Object>() ;
args . put( "x-expires" , 1800000);
channel . queueDeclare("myqueue" , false, false, false, args) ;
死信队列( Dead-Letter-Exchange)
当消息在一个队列中变成死信 (dead message) 之后,它能被重新被发送到另一个交换器中,这个交换器就是 DLX,绑定 DLX 的队列就称之为死信队列。
消息变成死信一般是由于以下几种情况:
- 消息被拒绝 (Basic.Reject/Basic .Nack),井且设置 requeue 参数为 false;
- 消息过期;
- 队列达到最大长度。
对于 RabbitMQ 来说, DLX 是一个非常有用的特性。 它可以处理异常情况下,消息不能够被消费者正确消费(消费者调用了 Basic.Nack 或者 Basic.Reject) 而被置入死信队列中
的情况,后续分析程序可以通过消费这个死信队列中的内容来分析当时所遇到的异常情况,进而可以改善和优化系统
延迟队列
一个应用中需要将每条消息都设置为 10 秒的延迟, 生产者通过 exchange.normal 这个交换器将发送的消息存储在 queue.normal 这个队列中。消费者 订阅的并非是 queue.normal 这个队列,而是 queue.dlx 这个队列。当消息从 queue.normal 这个队列中过期之后被存入 queue.dlx 这个队列中,消费者就恰巧消费到了延迟 10 秒的这条消息。
在真实应用中,对于延迟队列可以根据延迟时间的长短分为多个等级,一般分为 5 秒、 10秒、 30 秒、 1 分钟、 5 分钟、 10 分钟、 30 分钟、 1 小时这几个维度.
持久化
RabbitMQ的持久化分为三个部分:交换器的持久化、队列的持久化和消息的持久化。
交换器和队列的持久化是通过在声明时是将 durable 参数置为 true 实现的,消息的持久化是通过将消息的投递模式 (BasicProperties 中的 deliveryMode 属性)设置为 2 即可实现消息的持久化。
带来的问题:
1.影响性能,建议对可靠性不是很高的消息不持久化
2.设置了持久化,并不能保证数据百分之百不丢失,
情况一,autoAck为true, 消费者接收消息后宕机了,数据会丢失,那么应该设置autoAck为false,手动确认。
情况二,持久化消息存入磁盘需要一定的时间,在这段时间内服务器宕机了,数据丢失,那么可以引入 RabbitMQ 的镜像队列机,相当于副本,提高高可用。
生产者确认
两种解决方式:
- 通过事务机制实现:
- 通过发送方确认 (publisher confirm) 机制实现。
事务机制
事务确实能够解决消息发送方和 RabbitMQ 之间消息确认的问题,只有消息成功被 RabbitMQ 接收,事务才能提交成功,否则便可在捕获异常之后进行事务回滚,与此同时可以进 行消息重发。但是使用事务机制会"吸干" RabbitMQ 的性能。
发送方确认机制
生产者将信道设置成 confmn (确认)模式,一旦信道进入 confmn 模式,所有在该信道上面发布的消息都会被指派一个唯一的 ID(从 l 开始),一旦消息被投递到所有匹配的队列之后, RabbitMQ 就会发送一个确认 (Basic.Ack) 给生产者(包含消息的唯一 ID),这就使得生产 者知晓消息已经正确到达了目的地了。如果消息和队列是可持久化的,那么确认消息会在消息写入磁盘之后发出。 RabbitMQ 回传给生产者的确认消息中的 deliveryTag 包含了确认消息的序号,此外 RabbitMQ 也可以设置 channel . basicAck 方法中的 multiple 参数,表示到 这个序号之前的所有消息都己经得到了处理。