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

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

相关推荐
敖正炀14 小时前
高并发系统的降级预案与容错策略
分布式·架构
敖正炀14 小时前
稳定性监控与告警体系:SLI/SLO/SLA 实践
分布式·架构
敖正炀14 小时前
故障演练与混沌工程:ChaosBlade 到 Litmus
分布式·架构
敖正炀14 小时前
全链路压测与容量规划方法论
分布式·架构
敖正炀15 小时前
限流算法深度与 Guava/Sentinel 源码:从单机令牌桶到分布式滑动窗口的流量防护体系
分布式·架构
山屿落星辰18 小时前
hixl - 让分布式训练“零拷贝“通信
分布式
逍遥德21 小时前
SpringBoot自带TaskScheduler 接口使用详解:(02)微服务多实例模式下,爆发任务重复执行问题
spring boot·分布式·后端·微服务·中间件
Solis程序员1 天前
基于 Outbox 事务表 + Canal 监听+kafka+多级缓存:高并发社交关注系统全链路架构设计
分布式·kafka·linq
phltxy1 天前
Redis集群:分布式高可用存储方案
数据库·redis·分布式
二宝哥1 天前
大数据之安装zookeeper
大数据·分布式·zookeeper