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. 业务可重放

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

相关推荐
心念枕惊1 小时前
分布式、服务化的ERP系统架构设计
分布式·系统架构
重庆小透明1 小时前
分布式事务方案对比
分布式
向夏威夷 梦断明暄1 天前
从架构特点到功能缺陷,重新认识分析型分布式数据库
数据库·分布式·架构
AAA@峥1 天前
Ceph 集群配置管理完整指南
运维·数据库·分布式·ceph
心如鉄补1 天前
分布式链路追踪系统之docker-compose安装skywalking
分布式·docker·skywalking
努力努力再努力wz1 天前
【分布式系统与 RPC 框架系列】从单机瓶颈到远程调用:一文理解分布式架构与 RPC 原理
linux·网络·c++·分布式·网络协议·rpc·架构
ACP广源盛139246256731 天前
此芯 P1 AI BOX / AI NAS@ACP#IX8008、IX8024 应用场景与市场机会
大数据·人工智能·分布式·单片机·嵌入式硬件
网络工程小王2 天前
【HCIE-AI】12.deepspeed分布式并行训练进阶版
人工智能·pytorch·分布式·学习·昇腾·deepspeed
ACP广源盛139246256732 天前
国产算力互联IX8024@ACP#Kimi K3 开源后的端侧部署硬件架构分析
大数据·人工智能·分布式·单片机·嵌入式硬件
花生了什么事o2 天前
分布式 ID 生成方案:从数据库自增到雪花算法
数据库·分布式·算法