RabbitMQ如何防止消息丢失及重复消费
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
RabbitMQ如何防⽌消息丢失及重复消费
⼀、RabbitMQ出现消息丢失的情况及其解决办法
如图所⽰,RabbitMQ丢失消息的情况可以发送在任何⼀个节点。
1.1 ⽣产者没有成功把消息发送到MQ
a、丢失的原因:因为⽹络传输的不稳定性,当⽣产者在向MQ发送消息的过程中,MQ没有成功接收到消息,但是⽣产者却以为MQ成功接收到了消息,不会再次重复发送该消息,从⽽导致消息的丢失。
b、解决办法:有两个解决办法:事务机制和confirm机制,最常⽤的是confirm机制。
事务机制:
RabbitMQ 提供了事务功能,⽣产者发送数据之前开启 RabbitMQ 事务channel.txSelect,然后发送消息,如果消息没有成功被 RabbitMQ 接收到,那么⽣产者会收到异常报错,此时就可以回滚事务channel.txRollback,然后重试发送消息;如果收到了消息,那么可以提交事
务channel.txCommit。
伪代码如下:
1.
// 开启事务
2.
channel.txSelect
3.
try {
4.
// 这⾥发送消息
5.
} catch (Exception e) {
6.
channel.txRollback
7.
8.
// 这⾥再次重发这条消息
9.
}
10.
// 提交事务
11.
channel.txCommit
confirm机制:
RabbitMQ可以开启confirm模式,在⽣产者那⾥设置开启confirm模式之后,⽣产者每次写的消息都会分配⼀个唯⼀的 id,如果消息成功写⼊ RabbitMQ 中,RabbitMQ 会给⽣产者回传⼀个ack消息,告诉你说这个消息 ok 了。
如果 RabbitMQ 没能处理这个消息,会回调你的⼀个nack接⼝,告诉你这个消息接收失败,⽣产者可以发送。
⽽且你可以结合这个机制⾃⼰在内存⾥维护每个消息 id 的状态,如果超过⼀定时间还没接收到这个消息的回调,那么可以重发。
注意:RabbitMQ的事务机制是同步的,很耗型能,会降低RabbitMQ的吞吐量。
confirm机制是异步的,⽣成者发送完⼀个消息之后,不需要
等待RabbitMQ的回调,就可以发送下⼀个消息,当RabbitMQ成功接收到消息之后会⾃动异步的回调⽣产者的⼀个接⼝返回成功与否的消息。
2 RabbitMQ接收到消息之后丢失了消息
a、丢失的原因:RabbitMQ接收到⽣产者发送过来的消息,是存在内存中的,如果没有被消费完,此时RabbitMQ宕机了,那么再次启动的时候,原来内存中的那些消息都丢失了。
b、解决办法:开启RabbitMQ的持久化。
当⽣产者把消息成功写⼊RabbitMQ之后,RabbitMQ就把消息持久化到磁盘。
结合上⾯的说到的confirm机制,只有当消息成功持久化磁盘之后,才会回调⽣产者的接⼝返回ack消息,否则都算失败,⽣产者会重新发送。
存⼊磁盘的消息不会丢失,就算RabbitMQ挂掉了,重启之后,他会读取磁盘中的消息,不会导致消息的丢失。
c、持久化的配置:
第⼀点是创建 queue 的时候将其设置为持久化,这样就可以保证 RabbitMQ 持久化 queue 的元数据,但是它是不会持久化 queue ⾥的数据的。
第⼆个是发送消息的时候将消息的deliveryMode设置为 2,就是将消息设置为持久化的,此时 RabbitMQ 就会将消息持久化到磁盘上去。
注意:持久化要起作⽤必须同时设置这两个持久化才⾏,RabbitMQ 哪怕是挂了,再次重启,也会从磁盘上重启恢复 queue,恢复这个queue ⾥的数据。
3 消费者弄丢了消息
a、丢失的原因:如果RabbitMQ成功的把消息发送给了消费者,那么RabbitMQ的ack机制会⾃动的返回成功,表明发送消息成功,下次就不会发送这个消息。
但如果就在此时,消费者还没处理完该消息,然后宕机了,那么这个消息就丢失了。
b、解决的办法:简单来说,就是必须关闭 RabbitMQ 的⾃动ack,可以通过⼀个 api 来调⽤就⾏,然后每次在⾃⼰代码⾥确保处理完的时候,再在程序⾥ack⼀把。
这样的话,如果你还没处理完,不就没有ack了?那 RabbitMQ 就认为你还没处理完,这个时候 RabbitMQ 会把这个消费分配给别的 consumer 去处理,消息是不会丢的。
⼆、如何防⽌重复消费
先说为什么会重复消费:正常情况下,消费者在消费消息的时候,消费完毕后,会发送⼀个确认消息给消息队列,消息队列就知道该消息被消费了,就会将该消息从消息队列中删除;但是因为⽹络传输等等故障,确认信息没有传送到消息队列,导致消息队列不知道⾃⼰已经消费过该消息了,再次将消息分发给其他的消费者。
解决思路是:保证消息的唯⼀性,就算是多次传输,不要让消息的多次消费带来影响;保证消息等幂性;
在消息⽣产时,MQ内部针对每条⽣产者发送的消息⽣成⼀个inner-msg-id,作为去重和幂等的依据(消息投递失败并重传),避免重复的消息进⼊队列;
在消息消费时,要求消息体中必须要有⼀个bizId(对于同⼀业务全局唯⼀,如⽀付ID、订单ID、帖⼦ID等)作为去重和幂等的依据,避免同⼀条消息被重复消费。
这个问题针对业务场景来答分以下⼏点:
1. 如果消息是做数据库的insert操作,给这个消息做⼀个唯⼀主键,那么就算出现重复消费的情况,就会导致主键冲突,避免数据
库出现脏数据。
2. 如果消息是做redis的set的操作,不⽤解决,因为⽆论set⼏次结果都是⼀样的,set操作本来就算幂等操作。
3. 如果以上两种情况还不⾏,可以准备⼀个第三⽅介质,来做消费记录。
以redis为例,给消息分配⼀个全局id,只要消费过该消息,
将<id,message>以K-V形式写⼊redis。
那消费者开始消费前,先去redis中查询有没消费记录即可。