RabbitMQ消息丢失

RabbitMQ消息丢失

消息从生产到消费经过三个环节: 生产-->MQ Broker --> 消费 这三个环节都有丢失消息的可能。

一、生产者丢失消息

  原因:网络传输不稳定,从生产者到MQ的传输过程中,MQ未收到消息,而生产者认为任务完成不会重复发送,从而导致消息丢失。有两种方式可以解决该问题:事务机制和confirm通知机制。

方式一:事务机制

  RabbitMQ提供了事务功能,生产者发送消息之前开启事务 channel.txSelect,然后发送消息,如果消息没有成功接收则报异常,此时回滚事务,channel.txRollback,然后重试发送消息,成功则提交事务channel.txCommit。

// 开启事务
 
channel.txSelect;
 
try {
 
  // 这里发送消息
 
} catch (Exception e) {
 
 channel.txRollback
 
 
 // 这里再次重发这条消息
 
}
 
// 提交事务
 
channel.txCommit;

  但是注意,使用事务的方式非常消耗性能,大大降低 RabbitMQ 的效率,慎用!!!

方式二:confirm通知

  RabbitMQ可以在生产者那里将信道设置开启confirm通知模式,每次产出的消息都会分配一个唯一的 id (从1开始),如果消息成功写入 RabbitMQ 投递到匹配队列,则给生产者回传一个包含唯一id的确认 (Basic.Ack) 给生产者。

  如果接收失败生产者则重发消息,或者设置超过一定时间没有得到消息回调也可以重发。

  如果消息和队列是可持久化的,那么确认消息会在消息写入磁盘之后发出。

  RabbitMQ 回传给生产者的确认消息中的 deliveryTag 包含了确认消息的序号,此外 RabbitMQ 也可以设置 channel.basicAck 方法中的 multiple 参数,表示到这个序号之前的所有消息都已经得到了处理,注意辨别这里的确认和消费时候的确认之间的异同。

注意:

  • 事务机制和 publisher confirm 机制两者是互斥的,不能共存。

  • 事务机制和 publisher confirm 机制确保的是消息能够正确地发送至 RabbitMQ,这里的“发送至 RabbitMQ”的含义是指消息被正确地发往至 RabbitMQ 的交换器,如果此交换器没有匹配的队列,那么消息也会丢失。

二、RabbitMQ丢失消息

  原因:RabbitMQ 接收到消息之后,当时是存在内存中的还未消费,此时 RabbitMQ 挂了,再次启动时,内存中的消息丢失。

开启RabbitMQ持久化

  当RabbitMQ接收到消息后,将消息持久化到磁盘,此时哪怕 RabbitMQ 挂了,重启之后消息也是还在的。

  也可以联合之前设置的confirm通知,只有当 RabbitMQ 将消息持久化到磁盘才会回调返回 ack 消息,此时就算如果没持久化到磁盘就丢失了,生产者也会重发消息。

配置持久化:

  一、创建queue的时候将其设置为持久化,这样就可以保证RabbitMQ 持久化queue的元数据,但不会保存queue里面的数据。

  二、将发送消息的 deliveryMode 设置为2,把消息设置为持久化,此时 RabbitMQ 就会把消息持久化到磁盘。

  并且注意要同时设置这两个持久化才会起作用!!

三、消费者丢失消息

  原因:RabbitMQ 的 ack 自动消息确认机制,当 autoAck = true 的时候,RabbitMQ 会自动把发送出去的消息设置为确认,然后从内存或磁盘中删除,而不管消费者是否真正的接收到了消息,此时消费者宕机,所以导致消息丢失。

配置autoAck = false

  将autoAck设置为false之后,消费者有足够时间处理消息,不用担心消费者未处理完成就宕机的问题,RabbitMQ 会一直等待消费者显示回调 Basic.Ack 命令。

  并且可以通过 RabbitMQ 的WEB平台上查看消息参数,来监控查看消息的消费情况。

防止重复消费

  正常情况下,当消费者处理完消息之后会给消息队列发送确认通知到队列,队列收到后将消息从队列删除。但有时候网络传输故障,导致队列不知道消息已被处理,这个时候队列会将消息再次重复发送到另一个消费者,这个时候就出现消息重复消费的情况了。

解决:保证消息的作用的唯一性

  1. 做redis的set的操作,不用解决,因为无论set几次结果都是一样的,set操作本来就算幂等操作。
  2. 做数据库的insert操作,可以给这个消息做一个唯一主键,那么就算出现重复消费的情况,就会导致主键冲突,避免数据库出现脏数据。
  3. 最终情况:可以准备一个第三方介质,来做消费记录。以redis为例,给消息分配一个全局id,只要消费过该消息,将<id,message>以K-V形式写入redis。那消费者开始消费前,先去redis中查询有没消费记录即可。
posted @ 2022-09-19 15:07  _杨先生  阅读(569)  评论(0)    收藏  举报