RabbitMQ 消费端幂等实战(重复消息、去重、重放怎么处理)

这篇把"幂等"从概念拉回工程现实:重复消息不可避免,但结果可以可控。

先说结论

消费端幂等不是一个技巧,而是三件事组合:

  1. 唯一标识 :每条消息都有可追踪的 messageId
  2. 去重存储:Redis 或数据库记录"是否处理过"
  3. 业务可重放:重复消费不会产生副作用

一、为什么会有重复消息

你只要开启了可靠投递和手动 ACK,就一定会遇到重复消息。常见场景:

  • 消费成功,但 ACK 失败
  • 消费失败后重试
  • 生产者超时重发

这不是异常,是系统设计的正常行为


二、3 种幂等实现方式(按成本从低到高)

1) Redis 去重(推荐起步)

优点:简单、高性能

缺点:Redis 不可用时要兜底

java 复制代码
public void onMessage(OrderMessage msg) {
    String dedupKey = "mq:dedup:" + msg.getMessageId();

    // SETNX + TTL
    Boolean first = redisTemplate.opsForValue()
        .setIfAbsent(dedupKey, "1", 24, TimeUnit.HOURS);

    if (Boolean.FALSE.equals(first)) {
        // 重复消息,直接 ACK
        return;
    }

    // 业务处理
    process(msg);
}

2) 数据库唯一索引(可靠优先)

优点:强一致性

缺点:写入成本更高

sql 复制代码
CREATE TABLE mq_consume_log (
  message_id VARCHAR(64) PRIMARY KEY,
  status TINYINT,
  create_time DATETIME
);
java 复制代码
public void onMessage(OrderMessage msg) {
    try {
        // 先插入去重表
        consumeLogMapper.insert(msg.getMessageId());
    } catch (DuplicateKeyException e) {
        // 重复消息
        return;
    }

    process(msg);
}

3) 业务幂等(最终形态)

比如:支付成功后只更新一次状态,重复消息也只是"幂等更新"。

java 复制代码
public void process(OrderMessage msg) {
    // 只更新一次
    orderMapper.updateStatusIfNotPaid(msg.getOrderId());
}

三、常见业务场景怎么做

场景 1:扣库存

不要直接 stock - 1,而是做"去重后扣减":

java 复制代码
public void reduceStock(String orderNo) {
    if (dedup(orderNo)) {
        stockMapper.decrease(orderNo);
    }
}

场景 2:发优惠券

用"唯一索引"锁住发放记录:

sql 复制代码
ALTER TABLE user_coupon
ADD UNIQUE KEY uk_user_coupon(user_id, coupon_id);

重复发放自然失败,不影响业务。

场景 3:更新订单状态

只允许状态单向流转:

sql 复制代码
UPDATE orders
SET status = 'PAID'
WHERE id = ? AND status = 'UNPAID';

重复消费只会影响 0 行。


四、一个"够用"的组合方案

如果你不想纠结,直接用这个:

  1. messageId 必须有
  2. Redis 去重
  3. 数据库状态更新做幂等条件

这是我在实际项目里用的最平衡方案,成本低、效果稳。


五、常见坑

  1. 去重 key 没有 TTL

    Redis 记录会无限增长,记得设置过期时间。

  2. messageId 不唯一

    不要用订单号当 messageId,最好是 UUID 或雪花 ID。

  3. 只靠 Redis 去重

    Redis 挂了就会穿透,建议加业务幂等作为第二道保险。


最后总结

幂等不是让系统"完全不重复",而是让重复"变得无害"。

你只要把这三件事做好:

  1. 消息有唯一标识
  2. 去重有记录
  3. 业务可重放

重复消息就不再是线上事故,而是可控事件。

相关推荐
万联WANFLOW9 小时前
SD-WAN 控制平面高可用怎么做?SDWAN 控制器挂了,全网会发生什么
运维·网络·分布式·架构
月落归舟18 小时前
从单机到分布式:互联网系统架构演进全流程
分布式·系统架构
搬砖柯20 小时前
系列12-接口压测怎么做成平台能力?自研 httpx 引擎、分布式 Worker 与 Locust 选型对照
分布式·测试工具·性能优化·开源·自动化·httpx
折哥的程序人生 · 物流技术专研20 小时前
Java面试通关⑭:分布式理论与架构全集
分布式·微服务·校招·分布式事务·cap·java面试·最终一致性
格子软件1 天前
2026年GEO贴牌代理:分布式多级分账状态机源码深度解构
java·vue.js·分布式·vue·geo
Evand J2 天前
【论文复现】MATLAB例程,存在测距误差的WSN无锚点分布式自定位,《WSN中存在测距误差的无锚点分布式自定位方法》
开发语言·分布式·matlab·定位·导航·wsn
格子软件2 天前
2026年分布式GEO代理流量调度:源码级状态机防重挂实战
java·vue.js·人工智能·spring boot·分布式·vue
2301_801184752 天前
kafka-zookeeper
分布式·zookeeper·kafka
大明者省2 天前
四大模态大模型训练体系全解析(架构+范式+分布式+算力成本·)
笔记·分布式·架构
格子软件2 天前
2026年分布式GEO代理架构:多租户动态数据源隔离与流控源码解构
java·vue.js·人工智能·分布式·架构·vue·geo