RabbitMQ 消费端幂等实战(重复消息、去重、重放怎么处理) | 014-blog
Skip to content

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

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