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

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

相关推荐
阿 才10 分钟前
跟文件系统(busybox)的构建
大数据·hadoop·分布式
老纪1 小时前
Redis分布式锁进第九零篇
数据库·redis·分布式
Amy187021118231 小时前
分布式光伏防孤岛保护:技术逻辑、标准演进与工程实践全解析
分布式
ACP广源盛139246256732 小时前
IX7008 PCIe 交换芯片@ACP#RTX Spark 经济型 8 口扩展芯片(对比 ASM1806)
大数据·人工智能·分布式·嵌入式硬件·gpt·spark·电脑
ACP广源盛139246256732 小时前
IX6012 PCIe 交换芯片@ACP#RTX Spark 入门级 12 口存储外设扩展方案(对比 ASM1812)
大数据·人工智能·分布式·嵌入式硬件·gpt·spark·电脑
分布式存储与RustFS4 小时前
对标MinIO!RustFS新一代AI分布式对象存储开源能力前瞻
人工智能·分布式·开源·分布式对象存储·rustfs·minio平替·s3 table
cxr8286 小时前
蜂群智能系统中“非必要不添加“原则的有效性再审视:基于分布式决策与通信复杂度的理论推导
人工智能·分布式·智能体
bIo7lyA8v6 小时前
算法工程中的可扩展性与分布式实现方案的技术8
分布式
我登哥MVP6 小时前
SpringCloud 核心组件解析:分布式配置管理
java·spring boot·分布式·spring·spring cloud·java-ee·maven
IT策士6 小时前
Redis 从入门到精通:分布式锁 —— 从 SETNX 到 Redlock
数据库·redis·分布式