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); 
}
相关推荐
qiu_lovejun99815 小时前
linux安装docker和redis和rabbitmq和nginx和rocketmq和kafka
linux·redis·docker·kafka·rabbitmq·rocketmq
豆瓣鸡1 天前
RocketMQ学习-Spring Boot消息实践
java·spring boot·rocketmq
阿里云云原生2 天前
从传统漏桶到分布式矩阵:RocketMQ LiteTopic 如何支撑百炼网关的百万级流量治理?
rocketmq
大E帝国子民15 天前
Docker 部署 RocketMQ 5
docker·容器·rocketmq
孫治AllenSun5 天前
【RocketMQ】Mac安装
macos·rocketmq
佛祖让我来巡山5 天前
消息中间件入门认知:从0开始认识消息队列与RocketMQ
rocketmq·认识rocketmq
霸道流氓气质6 天前
基于阿里云RocketMQ封装库的集成技术示例
阿里云·云计算·rocketmq
阿里云云原生7 天前
乌镇大赛丨5 万奖金已备好!RocketMQ 等你一起打造全新 AI-Native 管控平台
apache·rocketmq
阿昌喜欢吃黄桃1 个月前
RocketMq事务消息原理
java·中间件·消息队列·rocketmq·mq
huisheng_qaq1 个月前
【项目篇-01】Vmware虚拟机和环境安装配置
redis·mysql·canal·rocketmq·es·vaware虚拟机