RabbitMQ中消息的重复消费,就像快递员多次送同一个包裹——明明已经签收了,却又收到一次,可能导致重复收货(比如重复下单、重复扣款)。处理的核心思路是让消费者能认出“这个消息我已经处理过了”,从而忽略重复的消息。
为什么会出现重复消费?
常见原因有:
- 消费者处理完消息后,还没来得及告诉RabbitMQ“我处理完了”(发送确认信号)就崩溃了,MQ以为消息没处理,会重新发给其他消费者;
- 网络波动导致确认信号丢失,MQ没收到确认,会重试发送;
- 生产者因为没收到MQ的接收确认,重复发送了消息。
处理重复消费的核心方案:让消息“可识别”,处理逻辑“可重入”
关键是两点:一是能认出重复的消息,二是重复处理也不会出问题。具体有几种方式:
1. 给消息加“唯一身份证”(最通用)
给每条消息分配一个全局唯一的ID(比如订单号+时间戳、UUID),消费者处理前先查“这个ID是否已经处理过”:
- 第一次处理:ID不存在,正常执行业务,处理完后把ID存入“已处理记录”(可以存在数据库、Redis等);
- 重复处理:ID已存在,直接返回成功,不做任何业务操作。
就像快递单号,每个包裹唯一,签收过的单号在系统里有记录,再收到同单号的包裹就知道“已经送过了”。
2. 让业务逻辑“幂等”(最根本)
“幂等”就是“重复执行多次,结果和执行一次一样”。即使没做ID检查,重复处理也不会出问题:
- 比如更新数据时,不用“增加100”,而用“设置为200”(不管原来多少,结果都是200,重复执行也一样);
- 比如查询操作,本身就不改变数据,重复查多少次都没关系。
这就像查询天气,查一次和查十次,结果都是当前天气,不会有问题。
3. 利用业务状态机(适合有明确状态的场景)
如果业务数据有清晰的状态流转(比如订单从“待支付”→“已支付”→“已发货”),可以通过状态判断是否重复:
- 处理消息时,先检查当前状态。比如处理“订单支付”消息时,看订单是否已经是“已支付”:
- 如果是“待支付”:正常处理(改成“已支付”);
- 如果已经是“已支付”:说明消息重复,直接跳过。
就像电影票检票,票上状态是“未检票”才能进,“已检票”就不让进,天然避免重复。
4. 给处理加“分布式锁”(防止并发重复)
如果多个消费者同时收到重复消息(比如网络延迟导致的同时重试),可以用分布式锁保证“同一时间只有一个消费者能处理”:
- 处理消息前,先尝试获取“该消息ID的锁”,拿到锁才能处理;
- 处理完释放锁,其他消费者再拿到锁时,会发现消息已处理,直接跳过。
就像同一个包裹,快递柜一次只允许一个人打开取件,避免两个人同时取到。
总结
处理重复消费的核心是“让重复的消息产生不了重复的业务影响”。实际中最常用的是“唯一ID+已处理记录”(通用且可靠),配合“业务逻辑幂等”(从根本上解决)。本质上,就是给消息加“辨识度”,给处理逻辑加“安全边界”,让重复消息“进得来,但无效化”。