消费重复的场景:
-
发送时消息重复
当一条消息已被成功发送到服务端并完成持久化,此时出现了网络闪断或者客户端宕机,导致服务端对客户端应答失败。 如果此时生产者意识到消息发送失败并尝试再次发送消息,消费者后续会收到两条内容相同并且Message ID也相同的消息。
-
投递时消息重复
消息消费的场景下,消息已投递到消费者并完成业务处理,当客户端给服务端反馈应答的时候网络闪断。为了保证消息至少被消费一次,消息队列RocketMQ版的服务端将在网络恢复后再次尝试投递之前已被处理过的消息,消费者后续会收到两条内容相同并且Message ID也相同的消息。
-
负载均衡时消息重复(包括但不限于网络抖动、Broker重启以及消费者应用重启)
当消息队列RocketMQ版的Broker或客户端重启、扩容或缩容时,会触发Rebalance,此时消费者可能会收到重复消息。
以上来源阿里云:https://help.aliyun.com/document_detail/44397.html
这里提供一种使用Redis的解决思路:在发送消息的时候可以生成一个唯一标识号,例如订单号,然后消费成功后将该标识号存入缓存或者插入数据库,然后每次消费的时候都去检查是否有该条记录
示意图:
因为不同的Message ID对应的消息内容可能相同,有可能出现冲突(重复)的情况,所以真正安全的幂等处理,不建议以Message ID作为处理依据
实际上幂等怎么做是需要根据业务来决定的,有一些业务需要入库的,那么可以利用数据库,一些是需要判断状态的等等,但关键还是在于唯一标识。
一些常见解决思路:
- 利用MySQL的duplicate key(注意:并发情况下可能涉及到死锁)
- 利用MySQL的唯一主键或唯一索引,当重复消费就会导致主键冲突
不论怎样,请牢记一个原则:缓存是不可靠的,查询是不可靠的 。
在高并发的场景下,一定要通过持久化存储的唯一索引以及引入锁机制作为共同保障数据准确性和完整性的最后一道防线