RocketMQ 如何保证消息不被重复消费

结论

先抛结论:保证不了

为什么会重复消费?

根本原因:ACK 机制

Consumer 消费成功了

但是 ACK 回执网络丢包

Broker 以为没消费

重新投递 → 重复消费

RocketMQ 只能保证消息「至少消费一次」(At Least Once)

不能保证「仅消费一次」

解决方案

唯一解法:消费端做幂等!

同一个消息,消费 1 次和 100 次,结果一样。

3 种最实用的幂等方案

方案 1:唯一消息 ID + 去重表(最常用、最简单)

流程:

每条消息有 唯一业务 ID(订单号、支付流水号)

消费时:

先查 DB 是否存在该 ID

存在 → 直接返回,不处理

不存在 → 执行业务,插入去重表

事务控制:业务逻辑 + 插入去重表 同一个事务

方案 2:利用数据库唯一约束(最简单)

给业务表的 业务 ID 加唯一索引

重复消息插入时

数据库直接报唯一键冲突

捕获异常,直接返回

方案 3:Redis 分布式锁(高并发)

消息 ID 作为 Redis Key

消费前:

SETNX lock:orderId

加锁成功 → 消费

加锁失败 → 重复

其实上面的方案都有问题

方案 1:唯一 ID + 去重表

高并发下绝对有风险!

数据库压力爆炸

每一条消息都要 select + insert,并发 1w+ 直接把库打垮

并发重复

两个相同请求同时 select → 都没查到 → 都执行 → 依旧重复

去重表无限膨胀

数据越来越大,查询越来越慢

方案 2:数据库唯一约束

高并发下大量异常报错

只能用于插入,不能用于更新(比如扣库存)

无法处理复杂业务

方案 3:Redis 分布式锁

锁超时风险

主从切换锁丢失

重入问题复杂

真正生成用法

不靠查询、不靠锁、不靠去重表,

靠【业务唯一 ID + 状态机 + 原子 UPDATE】!**

举2个例子

1.订单状态修改

修改状态无论多少次都是一样的结果

java 复制代码
UPDATE order
SET status = '已取消'
WHERE orderId = #{orderId}
AND status = '待取消'

2.库存扣减

库存没办法像订单表一样修改状态,只能用 去重表+扣减 来幂等

java 复制代码
@Transactional
public void consume(Message message) {

    String orderId = message.getUserProperty("orderId");
    Long goodsId = Long.valueOf(message.getUserProperty("goodsId"));

    // ==========================
    // 1. 幂等去重(唯一键)
    // ==========================
    try {
        stockUniqueLogMapper.insert(orderId);
    } catch (DuplicateKeyException e) {
        log.info("重复消费,直接ACK: {}", orderId);
        return;
    }

    // ==========================
    // 2. 扣库存
    // ==========================
    int rows = stockMapper.deductStock(goodsId);

    // ==========================
    // 3. 扣减成功 → ACK
    // ==========================
    if (rows > 0) {
        log.info("扣减成功,ACK: {}", orderId);
        return;
    }

    // ==========================
    // 4. 库存不足 → ACK + 补偿
    // ==========================
    log.error("库存不足,业务失败,ACK,发送补偿: {}", orderId);
    sendRefundMessage(orderId); 
}
相关推荐
深念Y3 天前
视频平台架构重构:从微服务到可插拔基础设施
后端·微服务·云原生·架构·rabbitmq·音视频·rocketmq
阿里云云原生5 天前
深入解析 RocketMQ LiteTopic:从 Parent Topic 到会话级通道的架构演进
rocketmq
xbgRS5 天前
RocketMQ消费者消息获取
java·rocketmq·java-rocketmq
Arc星语5 天前
Linux服务器RocketMQ和RocketMQ Dashboard安装部署
rocketmq
xbgRS6 天前
RocketMQ中的broker
java·rocketmq
骇客野人6 天前
SpringBoot + RocketMQ 异步解耦与最终一致性解决方案
spring boot·rocketmq·java-rocketmq
晚染烟7 天前
每日八股day33
rocketmq
肠畔码农12 天前
深度解析 RocketMQ 消费端限流与重平衡(Rebalance):分布式队列分配与流控防雪崩本质
分布式·rocketmq
Rain的Java大神之路12 天前
如何避免订单重复提交
java·redis·后端·面试·架构·rabbitmq·rocketmq
阿里云云原生13 天前
不增加副本,只重构接管路径:RocketMQ 单写有状态服务的高可用新设计
apache·rocketmq