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

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

相关推荐
大G的笔记本5 小时前
分布式事务
分布式
weixin_419658315 小时前
RabbitMQ 的高级特性
java·分布式·rabbitmq
_F_y6 小时前
仿RabbitMQ实现消息队列-服务端核心模块实现(1)
分布式·rabbitmq
.柒宇.8 小时前
RabbitMQ入门教程
分布式·rabbitmq
代码漫谈9 小时前
RabbitMQ 单节点部署指南
分布式·消息队列·rabbitmq
aLTttY10 小时前
Spring Boot + Redis 实战分布式锁:从入门到精通
spring boot·redis·分布式
weixin_4196583110 小时前
RabbitMQ 应用问题
java·分布式·中间件·rabbitmq
2301_8152795210 小时前
RabbitMQ - 在微服务架构中的落地实践:消息推送 / 解耦 / 削峰填谷
微服务·架构·rabbitmq
希望永不加班10 小时前
SpringBoot 整合 RabbitMQ 入门
java·spring boot·后端·rabbitmq·java-rabbitmq
爱艺江河10 小时前
HarmonyOS智慧风控:基于分布式架构的安全与创新实践
分布式·架构·harmonyos