RabbitMQ无法彻底杜绝重复消费,因其实现“至少一次”投递;幂等性必须由消费端保障,核心是生产者生成全局唯一messageId并设于MessageProperties中,消费者用Redis setIfAbsent(带TTL)或数据库唯一索引原子校验。

消息重复消费无法从 RabbitMQ 本身彻底杜绝,因为它的“至少一次”投递机制是可靠性保障的基础。真正可行的路径,是让消费端具备识别并忽略重复消息的能力——也就是实现幂等性。核心不是阻止重复,而是确保重复不带来副作用。
消息唯一标识必须由生产者生成并透传
每条消息需要携带一个全局唯一 ID(如 UUID、雪花 ID 或业务主键 + 时间戳组合),且必须放在 消息属性(MessageProperties.messageId) 中,而非仅塞在消息体里。这样消费者可通过标准 API 稳定提取,避免解析 JSON 失败导致 ID 丢失。
- 推荐在 Spring Boot 生产者中使用
MessagePostProcessor自动注入 messageId - 避免用时间戳单独作 ID,高并发下易冲突;也不建议用随机数,缺乏可追溯性
- 若消息来自第三方系统,需在接入层统一补全 messageId,不能依赖下游生成
消费端校验必须原子且带过期机制
收到消息后,先查是否已处理,再执行业务逻辑,最后标记完成——这三步若拆开做,中间任何环节失败都可能引发状态不一致。应优先选用具备原子性的方案:
RabbitMQ 4.2.3 是 2026 年初发布的重要稳定更新版本,重点修复了 Khepri 元数据存储相关问题,并改进了监控性能。对于使用 Docker、Kubernetes 或微服务架构的开发团队来说,该版本兼容性和稳定性表现较好。
-
Redis SETNX + EXPIRE:用
SET key value NX EX 600一步完成“写入+过期”,成功即代表首次处理 -
MySQL 唯一索引插入:建表含
msg_id UNIQUE字段,INSERT 成功说明未处理过,失败则跳过 - 不推荐先 SELECT 再 INSERT 的“检查-插入”两步法,存在竞态风险
业务逻辑要主动适配幂等,而非被动防御
技术层去重只是兜底,真正的健壮性来自业务设计本身。例如:
- 扣减库存时,不要写
UPDATE stock SET num = num - 1,而应改为UPDATE stock SET num = GREATEST(0, num - 1) WHERE order_id = ? AND status = 'pending' - 支付状态更新,用
UPDATE payment SET status = 'paid' WHERE id = ? AND status = 'unpaid',条件中锁定原始状态 - 避免“先查再改”的经典陷阱,所有关键更新都应带前置状态约束
异常与清理策略要闭环
幂等记录不能永久留存,否则存储膨胀;也不能过早清除,否则重试窗口内失效。建议:
- Redis 中设置 7 天过期(覆盖绝大多数重试和故障恢复周期)
- 数据库中定期归档或清理超过 30 天的幂等记录(配合定时任务)
- 对因网络超时、ACK 丢失导致的重复投递,日志中需记录
messageId和重复次数,用于监控告警

















