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); 
}
相关推荐
阿里云云原生4 天前
阿里云云原生四项 AI 应用工程化实荣获“鼎新杯”优秀案例
rocketmq
阿里云云原生4 天前
Qoder Cloud Agents 手脑分离实践:RocketMQ LiteTopic 如何支撑规模化长程 Agent 任务
rocketmq
阳光不锈@5 天前
三大主流消息队列架构设计思想剖析:从 RabbitMQ 到 RocketMQ 的改造实践
kafka·rabbitmq·rocketmq·rabbit改造rocket·去除rabbitmq·rabbitmq改造
薛之谦_10 天前
Java 整合 RocketMQ 实战:从入门到复杂场景落地
java·rocketmq·java-rocketmq
jyOverQ10 天前
从零开始学 RocketMQ(四):集群部署、消息存储与高可用【保姆级教程】
java·rocketmq·java-rocketmq
Thomas.Sir16 天前
第31课:微服务消息驱动(RabbitMQ/RocketMQ)适配与解耦实战
微服务·rabbitmq·rocketmq
Knight_AL16 天前
本地服务连接 SaaS 测试环境实践:Docker 部署 Redis 与 RocketMQ 完成接口联调
redis·docker·rocketmq
jyOverQ16 天前
从零开始学 RocketMQ(一):基础概念、环境搭建与简单消息收发【保姆级教程】
java·rocketmq·java-rocketmq
做个文艺程序员18 天前
MQ第10篇:RAG与消息队列实战:Java构建AI客服系统的检索增强生成完整架构
人工智能·架构·rocketmq·向量数据库·rag·ai客服·spring ai
做个文艺程序员18 天前
MQ第07篇:Java SaaS多租户消息隔离实战:RabbitMQ VHost+RocketMQ Tag+命名空间架构
spring boot·rabbitmq·rocketmq·saas·多租户