文章中如果有图看不到,可以点这里去 csdn 看看。从那边导过来的,文章太多,没法一篇篇修改好。

RocketMQ 内容详解【九、RocketMQ 的幂等性保证机制】

RocketMQ 的幂等性保证机制是分层的、非单点实现的体系,需要从消息发送端、Broker 服务端、消费端尤其是业务消费逻辑)三个层面去理解。

下面本文会给出完整的分析体系,从原理 → 源码机制 → 工程实践三个层次详细讲解。


一、首先明确什么是幂等性

在消息队列中,幂等性Idempotence)指:

对同一条消息,消费端即使接收到多次,也保证业务执行结果一致(执行一次或多次结果相同)。

因为网络抖动、Broker 异常、重试策略等原因,RocketMQ 不能保证消息“只被投递一次”(exactly-once),只能做到至少一次(at-least-once)。
所以幂等性的核心目标是:

消费端在可能重复收到消息的情况下,仍能保证业务语义正确。


二、幂等性风险来源分析

RocketMQ 在生产、传输、消费三个阶段,都可能出现重复消息:

阶段可能出现重复的原因
生产端发送超时、网络抖动、Broker 确认丢失导致 Producer 重试发送同一消息
BrokerMaster-Slave 同步不一致、重复恢复、消息重放(重试队列)
消费端消费超时或异常,Consumer 未返回 ACK(或 offset 未提交)导致 Broker 重新投递

→ 因此幂等性保障的核心在消费端,而不是 MQ 内部的“天然特性”。


三、RocketMQ 幂等性设计的三层思路

1️⃣ 发送端层面:去重保障

RocketMQ Producer 发送消息时,如果网络抖动或返回超时,会重试发送。
为了避免重复:

  • 每条消息带有 唯一 Message ID(msgId) 或者 业务 Key(Keys 字段)
  • Broker 在存储时基于 CommitLogOffset + MsgId 识别消息;
  • 但 RocketMQ 默认不做“消息去重”,只要 CommitLog 写入成功,就会保存该消息(不检查重复)。

✅ 所以 生产端幂等性依赖业务方确保消息唯一性
例如在发送时加上:

Message msg = new Message("OrderTopic", "TagA", "orderId-12345", body);

这样消费端就能识别“orderId-12345”是否已处理。


2️⃣ Broker 层面:投递一致性 + 重试语义

Broker 的职责是保证可靠投递,并不负责业务幂等性。
但它提供了若干机制以降低重复风险:

机制说明
CommitLog 顺序写 + ConsumeQueue 索引Broker 不会主动复制消息,但重建时会重放 CommitLog,有可能导致消息再次投递
消息重试队列(%RETRY%topic)消费失败会进入重试队列,按延迟等级重发(默认最多16次)
事务消息(半消息)在事务确认前不投递,保证 “发送成功即执行成功” 语义的近似原子性
去重表(延伸特性)一些公司二次开发会在 Broker 层建立 MsgKey 去重索引(RocketMQ 官方不内置)

✅ 结论:
Broker 不主动保障幂等性,只保证消息不丢、不乱序,重复由上层处理。


3️⃣ 消费端层面:幂等消费的落地关键点

消费端是幂等性的真正落地场所。

(1)消息重复的根源

消费端收到消息后:

  • 若消费逻辑执行异常(抛出异常、返回 RECONSUME_LATER),Broker 会重发;
  • 若 Consumer 崩溃/未提交 offset,Broker 认为未成功消费,也会重新投递。
(2)幂等消费的常见实现方式
实现方式原理优点缺点
唯一业务主键 + 幂等表业务操作写入 DB 前检查 msgKey 是否已执行简单可靠DB 压力大,需索引
Redis 去重表用 Redis Set/Hash 存储已消费 msgKey,TTL 限制窗口高性能,过期控制好存储成本、TTL 需精调
分布式锁 + 状态表业务关键路径加锁或状态控制逻辑明确性能差,复杂度高
业务幂等逻辑本身业务更新幂等(如更新状态、扣减库存)无需外部依赖对业务设计要求高

典型代码示例:

public class OrderConsumer implements MessageListenerConcurrently {

    @Autowired
    private RedisTemplate<String, String> redis;

    @Override
    public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
                                                    ConsumeConcurrentlyContext context) {
        for (MessageExt msg : msgs) {
            String key = msg.getKeys(); // orderId
            // 判断幂等
            if (redis.opsForSet().isMember("consumedKeys", key)) {
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; // 已处理过
            }

            try {
                processBusiness(msg); // 执行业务逻辑
                redis.opsForSet().add("consumedKeys", key);
                redis.expire("consumedKeys", Duration.ofDays(1));
            } catch (Exception e) {
                return ConsumeConcurrentlyStatus.RECONSUME_LATER;
            }
        }
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    }
}

四、结合事务消息实现“发送+执行”幂等(高级用法)

RocketMQ 提供了 事务消息(Transactional Message) 用于解决 “消息发送成功但业务执行失败” 或反向情况。

其执行过程(两阶段):

  1. prepare message(半消息)写入 Broker,但不投递;
  2. 执行本地事务(例如扣款、下单)
  3. 根据事务执行结果 commitrollback
  4. Broker 定时回查事务状态,若 Producer 未确认,触发回查逻辑。

这样保证了发送消息与本地事务的一致性,从而减少“消息丢失/重复”的风险。

虽然事务消息不能直接防止重复消费,但配合消费端幂等控制,可实现端到端的最终一致 + 幂等性保障


五、RocketMQ 幂等性保障的总体闭环

Broker
Producer
Consumer
接收消息
幂等判断(业务key/Redis/DB)
执行业务逻辑
ACK 或 RECONSUME_LATER
持久化 CommitLog
可能重发(重试队列)
发送消息
设置唯一 Keys/MsgId

核心策略:

  1. 生产端防重(唯一 key + 重试机制)
  2. Broker 层重试与事务一致性
  3. 消费端幂等判断与状态标记

这三者形成完整的幂等性闭环


六、实战建议

场景推荐方案
下单、扣款、发货类强一致业务事务消息 + Redis 幂等表
高吞吐日志/埋点不做幂等判断或批量去重
长期重复风险(金融对账)Redis + MySQL 双层校验
高性能服务用 io_uring + 异步消费线程池 + 幂等缓存

七、总结表

层级RocketMQ机制幂等策略责任方
ProducerMessageKey、发送重试唯一业务标识业务
BrokerCommitLog、重试队列、事务消息至少一次投递MQ 系统
ConsumerACK、offset、重试幂等表/Redis 检查业务

✅ 总结

RocketMQ 并不自动保证幂等性,但它通过稳定的“至少一次投递语义 + 明确的消费重试机制 + 事务消息接口”,
为开发者提供了实现端到端幂等的可控基础。

幂等的落点应放在消费端业务逻辑,通过唯一标识与状态存储确保“重复消息不会重复执行效果”。

posted @ 2025-10-08 09:12  NeoLshu  阅读(0)  评论(0)    收藏  举报  来源