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); 
}
相关推荐
阿昌喜欢吃黄桃6 天前
RocketMq事务消息原理
java·中间件·消息队列·rocketmq·mq
huisheng_qaq9 天前
【项目篇-01】Vmware虚拟机和环境安装配置
redis·mysql·canal·rocketmq·es·vaware虚拟机
码农飞哥10 天前
RocketMQ消费接口设计实战:为什么HTTP回调接口必须吞掉所有异常,始终返回成功?
网络协议·http·中间件·消息队列·rocketmq
阿维的博客日记10 天前
细说RocketMQ双网卡问题
rocketmq
北城以北888810 天前
RocketMQ简介
java·spring boot·后端·rocketmq
IT界的老黄牛10 天前
RocketMQ 4.x 任意秒数延迟消息工程实战:MQ 粗延迟 + Redis 补精度 + MDC 链路透传
redis·rocketmq·事务消息·延迟消息
至此流年莫相忘11 天前
Windows 环境下 RocketMQ 安装与 NSSM 后台服务化部署指南
windows·rocketmq
折哥的程序人生 · 物流技术专研11 天前
《Java 100 天进阶之路》第95篇:消息队列基础(RocketMQ/Kafka)(2026版)
java·面试·kafka·rocketmq·java-rocketmq·求职招聘
景川呀12 天前
RocketMq知识点
java·rocketmq·java-rocketmq
cfm_291414 天前
RocketMQ源码深度解析(三)消息持久化机制
rocketmq