RocketMQ 面试题大全(2026最新版)-95 道面试题

声明:本文整理了 RocketMQ 的核心面试题,涵盖基础概念、核心原理、场景应用等全方位内容,帮助大家快速掌握 RocketMQ 面试重点。所有答案均采用通俗易懂的语言讲解,配合实际案例和代码示例。


一、基础概念与架构

1. 什么是 RocketMQ?有哪些核心特点?

答案:

RocketMQ 是阿里巴巴开源的分布式消息中间件,采用 Java 语言开发,支持发布/订阅模式,提供消息可靠传递、事务消息、延时消息等功能。

核心特点:

特点 说明
高吞吐量 支持百万级 TPS,适用于大规模消息场景
低延迟 毫秒级消息传输延迟
高可靠性 支持消息持久化、主从复制,确保消息不丢失
消息堆积能力强 支持海量消息堆积,适合电商秒杀等场景
支持多种消息类型 普通消息、顺序消息、延迟消息、事务消息
灵活的消费模式 支持集群消费和广播消费
金融级可靠性 经过支付宝、淘宝等核心系统验证

2. RocketMQ 的核心组件有哪些?各自的作用是什么?

答案:

组件 作用 类比理解
Producer 消息生产者,负责发送消息到 Broker 寄件人,负责发包裹
Consumer 消息消费者,从 Broker 拉取/推送消息并消费 收件人,负责收包裹
Broker 消息存储节点,接收 Producer 消息并存储,供 Consumer 拉取 快递中转站仓库
NameServer 路由注册中心,管理 Broker 节点信息,提供路由查询服务 快递网点查询系统

详细说明:

Producer(生产者)

  • 负责发送消息到 Broker
  • 支持三种发送模式:
    • 同步发送:等待 Broker 确认返回,可靠性高
    • 异步发送:发送后立即返回,通过回调获取结果
    • 单向发送:只发送不等待确认,性能最高但可靠性最低

Consumer(消费者)

  • 从 Broker 拉取消息并处理业务逻辑
  • 支持两种消费模式:
    • 集群消费(Clustering):同一 Consumer Group 内的消费者共享消息,每条消息只被一个消费者消费
    • 广播消费(Broadcasting):同一 Consumer Group 内的每个消费者都会收到所有消息

Broker(消息代理)

  • 接收并存储消息
  • 向消费者投递消息
  • 支持主从架构:Master 负责读写,Slave 仅同步数据

NameServer(名字服务)

  • 管理 Broker 的路由信息
  • 提供 Topic 路由查询
  • 无状态设计,支持集群部署

3. 什么是 Topic 和 MessageQueue?它们之间是什么关系?

答案:

Topic(主题)

  • 消息的逻辑分类,例如:订单主题(order_topic)、支付主题(pay_topic)
  • 类似于数据库中的表名,用于隔离不同类型的消息

MessageQueue(消息队列)

  • Topic 的物理存储单元,一个 Topic 可以包含多个 MessageQueue
  • 每个 MessageQueue 是一个独立的队列,支持并行读写
  • 队列分布在不同的 Broker 上,实现负载均衡

关系图示:

复制代码
Topic (订单主题)
├── MessageQueue 0 (Broker-A)
├── MessageQueue 1 (Broker-A)
├── MessageQueue 2 (Broker-B)
└── MessageQueue 3 (Broker-B)

为什么需要多队列?

  • 提高并发处理能力:多个队列可并行处理消息
  • 提升吞吐量:生产者可并行发送到多个队列,消费者可并行拉取
  • 水平扩展:队列分布在不同 Broker 上,分散压力

4. NameServer 的工作机制是什么?和 ZooKeeper 有什么区别?

答案:

NameServer 工作机制:

  1. Broker 注册

    • Broker 启动时向所有 NameServer 注册自己的地址信息
    • Broker 每隔 30 秒向 NameServer 发送心跳
  2. 路由管理

    • NameServer 维护 Topic 与 MessageQueue 的映射关系
    • NameServer 120 秒未收到 Broker 心跳则将其移除
  3. 路由查询

    • Producer/Consumer 启动时从 NameServer 拉取路由信息
    • 每 30 秒刷新一次本地路由缓存

与 ZooKeeper 的区别:

对比项 NameServer ZooKeeper
复杂度 轻量级,功能单一 功能强大,支持分布式锁、配置中心等
节点间通信 各节点独立,无数据同步 节点间需要数据同步,有 Leader 选举
扩展性 简单,易于横向扩展 集群复杂,需要维护一致性
性能 性能高,无共识协议开销 受共识协议影响,性能相对较低

为什么 RocketMQ 不用 ZooKeeper?

  • NameServer 设计更简单,满足 RocketMQ 路由需求即可
  • 无状态设计,便于水平扩展
  • 避免 ZooKeeper 的复杂性和性能开销

5. 什么是 ConsumerGroup?有什么作用?

答案:

ConsumerGroup(消费者组)

  • 多个 Consumer 组成的逻辑分组,共享消息消费责任
  • 同一个 ConsumerGroup 内的消费者订阅关系必须一致

作用:

作用 说明
负载均衡 同一 Group 内的消费者分担消息负载
容错能力 某个 Consumer 宕机,其他 Consumer 接管其队列
消费进度管理 每个 Group 维护独立的消费进度(Offset)
广播/集群模式切换 通过 Group 实现不同消费模式

重要规则:

  • 同一 Group 内:一条消息只被一个 Consumer 消费(集群模式)
  • 不同 Group 之间:同一条消息会被各自消费一次

6. RocketMQ 支持哪些消息类型?

答案:

消息类型 特点 适用场景
普通消息 无特殊顺序或延迟要求 通用业务消息
顺序消息 保证同一 Queue 内消息有序 订单状态流转、支付流水
延迟消息 消息延迟指定时间后消费 订单超时取消、定时提醒
事务消息 保证本地事务与消息发送一致性 跨服务分布式事务

7. 什么是消息的 Tag?如何使用?

答案:

Tag(标签)

  • Topic 的二级分类,用于更细粒度的消息过滤
  • 同一个 Topic 下可以有多个 Tag

使用示例:

java 复制代码
// 发送消息时设置 Tag
Message message1 = new Message("OrderTopic", "TagCreate", orderJson.getBytes());
Message message2 = new Message("OrderTopic", "TagPay", payJson.getBytes());

// 订阅时指定 Tag
consumer.subscribe("OrderTopic", "TagCreate || TagPay");  // 订阅创建和支付

最佳实践:

  • 一个应用一个 Topic
  • 消息子类型用 Tag 区分
  • Tag 数量不宜过多(建议 < 100)

8. Broker 的主从架构是怎样的?有什么作用?

答案:

主从架构:

  • Master:负责读写,接收 Producer 消息,响应 Consumer 请求
  • Slave:仅同步 Master 数据,支持 Consumer 读取(可选)

复制模式:

模式 特点 适用场景
SYNC_MASTER Master 等待 Slave 复制完成后再返回确认 高可靠性场景
ASYNC_MASTER Master 立即返回,Slave 异步复制 高性能场景

作用:

  • 高可用:Master 宕机,Slave 可以接管(需手动切换或 DLedger 模式)
  • 读写分离:Slave 支持读请求,减轻 Master 压力
  • 数据冗余:防止数据丢失

9. RocketMQ 如何实现消息的 Pull 和 Push 模式?

答案:

Push 模式(推荐):

  • Consumer 主动拉取,但看起来像 Broker 推送
  • 内部通过长轮询实现
  • 代码简单,无需手动管理拉取
java 复制代码
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group");
consumer.subscribe("TopicTest", "*");
consumer.registerMessageListener(new MessageListenerConcurrently() {
    @Override
    public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
        ConsumeConcurrentlyContext context) {
        // 处理消息
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    }
});

Pull 模式:

  • Consumer 完全手动拉取消息
  • 需要自己管理消费进度、线程池
  • 灵活度高,控制力强
java 复制代码
DefaultMQPullConsumer consumer = new DefaultMQPullConsumer("group");
consumer.start();
Set<MessageQueue> mqs = consumer.fetchSubscribeMessageQueues("TopicTest");

for (MessageQueue mq : mqs) {
    PullResult result = consumer.pull(mq, "*", 0, 32);
    // 处理拉取结果
}

10. 什么是消息的 Keys?有什么作用?

答案:

Keys

  • 消息的业务唯一标识
  • RocketMQ 会为 Keys 建立索引,支持按 Key 查询消息

使用示例:

java 复制代码
Message message = new Message("OrderTopic", "TagA", orderJson.getBytes());
message.setKeys("ORDER_20240101_001");  // 设置订单号作为 Key

作用:

  • 消息查询:通过 Key 快速定位消息
  • 问题排查:出现问题时,根据业务 ID 查询消息轨迹
  • 去重:Keys 可用于业务幂等性判断

最佳实践:

  • 使用订单号、用户 ID、请求 ID 等唯一标识
  • 保证 Key 尽可能唯一,避免 Hash 冲突

11. 什么是消息的 Offset?如何管理?

答案:

Offset(消费位点)

  • Consumer 消费到的消息位置
  • 每个 ConsumerGroup 每个队列都有独立的 Offset

Offset 存储位置:

消费模式 Offset 存储位置
集群消费 Broker 端存储
广播消费 Consumer 本地存储

Offset 提交方式:

java 复制代码
// 自动提交(默认)
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);

// 手动提交
consumer.setConsumeMessageBatchMaxSize(1);
consumer.registerMessageListener(new MessageListenerOrderly() {
    @Override
    public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs,
        ConsumeOrderlyContext context) {
        // 处理消息
        processMessage(msgs);
        
        // 手动提交 Offset
        context.setAckIndex(msgs.size() - 1);
        return ConsumeOrderlyStatus.SUCCESS;
    }
});

12. RocketMQ 如何处理消息的持久化?

答案:

持久化机制:

  1. 写入 CommitLog

    • 所有消息先写入 CommitLog(物理文件)
    • 顺序追加写入,保证高性能
  2. 刷盘策略

    • SYNC_FLUSH:每条消息写入磁盘后才返回确认
    • ASYNC_FLUSH:消息先写入内存,批量刷盘
  3. 文件管理

    • CommitLog 默认 1GB 一个文件
    • 写满后自动创建新文件
    • 过期文件自动删除(默认 72 小时)

持久化流程:

复制代码
Producer 发送消息
    ↓
Broker 接收,写入内存
    ↓
写入 CommitLog(内存映射文件)
    ↓
根据刷盘策略落盘
    ↓
更新 ConsumeQueue 索引

二、核心原理深度解析

13. RocketMQ 的消息存储模型是怎样的?CommitLog 和 ConsumeQueue 是什么?

答案:

RocketMQ 采用** CommitLog + ConsumeQueue** 的混合存储模型。

1)CommitLog(提交日志)

特点:

  • 所有 Topic 的消息统一存储在 CommitLog 中
  • 采用顺序追加写入,最大化磁盘写入性能
  • 文件大小默认为 1GB,写满后自动创建新文件

存储内容:

复制代码
[消息大小][消息体][MagicCode][Crc32][...]

优势:

  • 顺序写盘,避免随机 IO,提高性能
  • 多 Topic 共享一个文件,降低文件管理复杂度
  • 充分利用磁盘顺序读写特性

2)ConsumeQueue(消费队列)

特点:

  • 每个 Topic-Queue 对应一个 ConsumeQueue
  • 作为消息的逻辑索引,记录消息在 CommitLog 中的位置

存储内容:

复制代码
[消息在CommitLog中的偏移量(8字节)][消息大小(4字节)][Tag HashCode(8字节)]

作用:

  • 快速定位消息:Consumer 通过 ConsumeQueue 找到消息在 CommitLog 中的位置
  • 支持按 Tag 过滤:通过 HashCode 快速过滤不需要的消息
  • 减少磁盘读取:只需读取索引信息,无需遍历全部消息

3)协同工作流程

复制代码
1. Producer 发送消息
   ↓
2. Broker 将消息写入 CommitLog(物理存储)
   ↓
3. Broker 同时更新对应的 ConsumeQueue(逻辑索引)
   ↓
4. Consumer 从 ConsumeQueue 获取索引
   ↓
5. Consumer 根据索引从 CommitLog 读取完整消息

4)为什么采用这种设计?

对比单文件存储:

  • ❌ 如果每个 Topic-Queue 独立存储文件,会产生大量小文件,导致随机 IO,性能低下
  • ✅ 采用 CommitLog 统一存储,避免小文件问题,实现顺序写

优势总结:

  • ✅ 高写入性能:顺序追加写入 CommitLog
  • ✅ 高读取性能:ConsumeQueue 提供索引,快速定位
  • ✅ 易于扩展:新增 Topic 不影响存储结构

14. 一条消息从发送到消费的完整流程是什么?

答案:

阶段 1:消息发送

复制代码
1. Producer 启动,连接 NameServer
   ↓
2. Producer 从 NameServer 获取 Topic 的路由信息(包含哪些 Broker、哪些 Queue)
   ↓
3. Producer 根据负载均衡策略选择一个 Queue(轮询、随机、哈希等)
   ↓
4. Producer 向目标 Broker 发送消息(同步/异步/单向)
   ↓
5. Broker 接收消息,写入 CommitLog
   ↓
6. Broker 更新 ConsumeQueue 索引
   ↓
7. Broker 返回发送结果给 Producer

阶段 2:消息消费

复制代码
1. Consumer 启动,连接 NameServer
   ↓
2. Consumer 从 NameServer 获取 Topic 路由信息
   ↓
3. Rebalance(负载均衡):Consumer Group 内分配 Queue
   ↓
4. Consumer 从分配的 Queue 拉取消息
   ↓
5. Consumer 根据 ConsumeQueue 索引从 CommitLog 读取完整消息
   ↓
6. Consumer 执行业务逻辑处理消息
   ↓
7. Consumer 提交消费进度(Offset)给 Broker

代码示例(简化版)

java 复制代码
// Producer 发送消息
DefaultMQProducer producer = new DefaultMQProducer("producer_group");
producer.setNamesrvAddr("localhost:9876");
producer.start();

Message message = new Message("TopicTest", "TagA", "Hello RocketMQ".getBytes());
SendResult result = producer.send(message);  // 同步发送

// Consumer 消费消息
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("consumer_group");
consumer.setNamesrvAddr("localhost:9876");
consumer.subscribe("TopicTest", "*");

consumer.registerMessageListener(new MessageListenerConcurrently() {
    @Override
    public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
        ConsumeConcurrentlyContext context) {
        // 处理消息
        System.out.println("收到消息: " + new String(msgs.get(0).getBody()));
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;  // 提交消费进度
    }
});

consumer.start();

15. RocketMQ 的消息过滤机制有哪些?

答案:

RocketMQ 支持三种消息过滤方式:

1)Tag 过滤(常用)

特点:

  • 简单、高效
  • Broker 端基于 Tag HashCode 进行第一轮过滤
  • Consumer 端基于 Tag 字符串进行第二轮精确过滤

使用示例:

java 复制代码
// 发送时设置 Tag
Message message = new Message("TopicTest", "TagA", "Hello".getBytes());

// 订阅时指定 Tag
consumer.subscribe("TopicTest", "TagA || TagB");  // 订阅 TagA 或 TagB

2)SQL92 过滤(高级)

特点:

  • 支持复杂的 SQL 表达式过滤
  • 需要开启 Broker 端属性过滤:enablePropertyFilter=true
  • 支持对消息属性进行条件判断

使用示例:

java 复制代码
// 发送时设置属性
Message message = new Message("TopicTest", "", "Hello".getBytes());
message.putUserProperty("age", "18");
message.putUserProperty("price", "99");

// 订阅时使用 SQL 过滤
consumer.subscribe("TopicTest",
    MessageSelector.bySql("age > 17 AND price < 100"));

3)Filter Server 过滤(企业版)

特点:

  • 需要独立的 Filter Server 组件
  • Consumer 委托 Filter Server 进行过滤
  • 减少网络传输,提高效率

16. 消息发送模式有哪些?区别是什么?

答案:

发送模式 特点 适用场景
同步发送 阻塞等待 Broker 返回结果,可靠性高 重要业务消息(支付、订单)
异步发送 发送后立即返回,通过回调获取结果 非核心业务(通知、日志)
单向发送 只发送不等待结果,性能最高 可容忍丢失的消息(监控、统计)

代码示例

java 复制代码
// 1. 同步发送
SendResult syncResult = producer.send(message);
System.out.println("发送结果: " + syncResult.getSendStatus());

// 2. 异步发送
producer.send(message, new SendCallback() {
    @Override
    public void onSuccess(SendResult sendResult) {
        System.out.println("异步发送成功: " + sendResult);
    }

    @Override
    public void onException(Throwable e) {
        System.out.println("异步发送失败: " + e.getMessage());
    }
});

// 3. 单向发送
producer.sendOneway(message);  // 不关心结果

17. 消息消费模式有哪些?

答案:

1)集群消费(Clustering,默认)

复制代码
特点:
- 同一 Consumer Group 内的消费者分担消息
- 每条消息只被一个消费者消费
- 适合提高处理能力

例子:
Consumer Group 有 3 个消费者,Topic 有 6 个队列
每个消费者分配 2 个队列,并行消费

2)广播消费(Broadcasting)

复制代码
特点:
- 同一 Consumer Group 内的每个消费者都收到所有消息
- 适合通知、缓存刷新等场景

例子:
配置中心通知所有服务刷新缓存

代码示例

java 复制代码
// 集群消费(默认)
consumer.setMessageModel(MessageModel.CLUSTERING);

// 广播消费
consumer.setMessageModel(MessageModel.BROADCASTING);

18. 什么是 Rebalance(负载均衡)?何时触发?

答案:

Rebalance

  • Consumer Group 内重新分配 MessageQueue 的过程
  • 确保消息均匀分配给所有消费者

触发条件:

触发条件 说明
Consumer 实例数变化 新增或减少 Consumer
Broker 节点变化 Broker 宕机或新增
Queue 数量变化 Topic 队列数调整
订阅关系变化 Consumer 订阅的 Topic 变化

分配策略:

策略 说明
AllocateMessageQueueAveragely 平均分配(默认)
AllocateMessageQueueAveragelyByCircle 环形分配
AllocateMessageQueueConsistentHash 一致性哈希分配
AllocateMessageQueueByConfig 手动配置
AllocateMessageQueueByMachineRoom 机房分配策略

注意事项:

  • Rebalance 期间可能导致短暂的消息重复
  • 建议监听 Rebalance 事件,做好状态清理

19. RocketMQ 如何实现长轮询(Long Polling)?

答案:

长轮询机制

  • Consumer 发起拉取请求
  • Broker 如果没有消息,不立即返回空
  • Broker 挂起请求,等待消息到达或有消息写入
  • 最多等待指定时间(默认 15 秒)

优势:

  • 减少 Consumer 的无效请求
  • 实时性高,消息到达立即响应
  • 降低 Broker 压力

配置示例:

java 复制代码
// Consumer 端配置长轮询
consumer.setPullInterval(0);  // 拉取间隔 0
consumer.setPullBatchSize(32);  // 每次拉取 32 条

// Broker 端配置
brokerSuspendMaxTimeMillis=15000  # 最大挂起时间 15 秒

20. 什么是 IndexFile?有什么作用?

答案:

IndexFile

  • RocketMQ 的消息索引文件
  • 支持按消息 Key 或时间范围查询消息

存储结构:

复制代码
[Header][HashSlot][IndexNode][IndexNode]...

Header(固定 40 字节):

  • 文件创建时间
  • 文件开始 Hash 槽位置
  • 文件开始 Index 位置
  • Index 槽数量

IndexNode(固定 20 字节):

  • HashCode(4 字节)
  • CommitLog 物理偏移量(8 字节)
  • 时间差(4 字节)
  • 上一个 Index 位置(4 字节)

作用:

  • 快速查询:根据 Key 查询消息
  • 时间范围查询:查询指定时间范围内的消息
  • 问题排查:根据业务 ID 快速定位消息

使用示例:

java 复制代码
// 按消息 Key 查询
QueryResult queryResult = defaultMQAdminImpl.queryMessage(
    "TopicTest", "ORDER_001", 10, 0, System.currentTimeMillis() - 86400000);

21. RocketMQ 如何实现内存映射(MappedFile)?

答案:

内存映射机制

  • 使用 MappedByteBuffer 将文件映射到内存
  • 利用操作系统的 Page Cache 提高读写性能
  • 减少数据在用户空间和内核空间的拷贝

优势:

  • 读写性能高:利用 Page Cache,减少磁盘 IO
  • 延迟低:数据在内存中,读取速度快
  • CPU 开销低:减少数据拷贝

实现原理:

复制代码
文件系统
    ↓
MappedFile(内存映射)
    ↓
Page Cache(操作系统缓存)
    ↓
物理磁盘

配置示例:

properties 复制代码
# CommitLog 文件大小
mapedFileSizeCommitLog=1073741824  # 1GB

# ConsumeQueue 文件大小
mapedFileSizeConsumeQueue=300000  # 约 5.72MB

22. RocketMQ 如何管理文件的生命周期?

答案:

文件管理机制

  1. 文件创建

    • CommitLog 写满后自动创建新文件
    • 文件名包含起始偏移量
  2. 文件过期

    • 默认保留 72 小时(可配置)
    • 超时文件标记为删除
  3. 文件删除

    • 定时任务扫描过期文件
    • 文件未被引用时物理删除

配置示例:

properties 复制代码
# 文件保留时间(小时)
fileReservedTime=72

# 删除策略(删除过期文件)
deleteWhen=04

# 磁盘使用率阈值(超过此值强制删除)
diskMaxUsedSpaceRatio=88

注意事项:

  • 确保消费速度 > 文件过期速度
  • 避免消息还未消费就被删除

23. RocketMQ 如何实现消息的批量发送?

答案:

批量发送机制

  • 将多条消息打包成一批发送
  • 减少网络请求次数,提高吞吐量

代码示例:

java 复制代码
List<Message> messages = new ArrayList<>();
for (int i = 0; i < 100; i++) {
    messages.add(new Message("TopicTest", ("Hello " + i).getBytes()));
}

// 批量发送
SendResult result = producer.send(messages);

批量发送限制:

  • 单次发送消息大小不超过 4MB
  • 单次发送消息条数不超过 32 条(可配置)

批量发送注意事项:

  • 确保同批次消息的 Topic 相同
  • 建议消息体大小接近,避免消息分片

24. RocketMQ 如何实现消息压缩?

答案:

压缩机制

  • 对消息体进行压缩,减少网络传输和磁盘占用
  • 支持多种压缩算法(ZLIB、SNAPPY、LZ4 等)

代码示例:

java 复制代码
Message message = new Message("TopicTest", largeData.getBytes());
message.setCompressed(true);  // 启用压缩

压缩策略:

压缩算法 压缩比 压缩速度 解压速度
ZLIB
SNAPPY
LZ4 极快 极快

使用建议:

  • 消息体 > 1KB 时建议启用压缩
  • 选择 LZ4 算法获得最佳性能
  • 压缩会增加 CPU 开销,需权衡

三、消息可靠性与顺序性

25. RocketMQ 如何保证消息不丢失?

答案:

需要从生产、存储、消费三个环节全链路保障。

1)生产端:确保消息成功发送

措施:

  • ✅ 使用同步发送send()),检查返回结果
  • ✅ 捕获异常并重试
  • ❌ 避免使用单向发送(sendOneway()

代码示例:

java 复制代码
try {
    SendResult result = producer.send(message);
    if (result.getSendStatus() == SendStatus.SEND_OK) {
        System.out.println("消息发送成功");
    }
} catch (Exception e) {
    // 发送失败,重试或记录日志
    System.out.println("消息发送失败: " + e.getMessage());
}

2)存储端:确保消息持久化

措施 A:刷盘策略

刷盘模式 特点 适用场景
SYNC_FLUSH(同步刷盘) 消息写入磁盘后才返回确认,可靠性高 金融、支付等核心场景
ASYNC_FLUSH(异步刷盘) 消息先写入内存,批量刷盘,性能高 日志、监控等非核心场景

配置:

properties 复制代码
broker.conf
flushDiskType=SYNC_FLUSH  # 同步刷盘

措施 B:主从复制

复制模式 特点 适用场景
SYNC_MASTER(同步复制) Master 等待 Slave 复制完成后再返回确认 高可靠性场景
ASYNC_MASTER(异步复制) Master 立即返回,Slave 异步复制 高性能场景

配置:

properties 复制代码
broker.conf
brokerRole=SYNC_MASTER  # 同步复制

3)消费端:确保消息成功处理

措施:

  • ✅ 消息处理成功后再提交消费进度(ACK)
  • ❌ 避免在异常路径提前确认
  • ✅ 处理失败返回 RECONSUME_LATER 触发重试

代码示例:

java 复制代码
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
    ConsumeConcurrentlyContext context) {
    try {
        // 处理业务逻辑
        processMessage(msgs);

        // 处理成功后才返回成功
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    } catch (Exception e) {
        // 处理失败,稍后重试
        return ConsumeConcurrentlyStatus.RECONSUME_LATER;
    }
}

全链路可靠性总结

复制代码
生产端                存储端                  消费端
[同步发送]  +  [同步刷盘 + 同步复制]  +  [处理成功再ACK]
   ↓             ↓                      ↓
 确保发送      确保持久化             确保消费

26. RocketMQ 如何保证消息的顺序性?

答案:

RocketMQ 保证的是局部顺序(同一 Queue 内的消息有序),而非全局顺序。

1)顺序消息的实现原理

关键点:

  1. 生产端:将同一业务 ID(如订单 ID)的消息发送到同一个 Queue
  2. 存储端:同一 Queue 内的消息顺序写入 CommitLog
  3. 消费端:同一 Queue 内的消息顺序消费(单线程)

2)代码示例

java 复制代码
// Producer:将订单号哈希到固定 Queue
public class OrderQueueSelector implements MessageQueueSelector {
    @Override
    public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
        String orderId = (String) arg;
        int index = Math.abs(orderId.hashCode()) % mqs.size();
        return mqs.get(index);
    }
}

// 使用
producer.send(message, new OrderQueueSelector(), "ORDER_001");

// Consumer:顺序消费(单线程处理)
consumer.registerMessageListener(new MessageListenerOrderly() {
    @Override
    public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs,
        ConsumeOrderlyContext context) {
        // 单线程顺序处理
        for (MessageExt msg : msgs) {
            processMessage(msg);
        }
        return ConsumeOrderlyStatus.SUCCESS;
    }
});

3)顺序消息的限制

限制 说明
仅局部顺序 同一业务 ID 有序,不同业务 ID 之间无序
并发度受限 并发度 = Queue 数量
主从切换可能乱序 Master 宕机切换期间可能出现短暂乱序

4)全局顺序 vs 局部顺序

类型 实现方式 并发度 适用场景
全局顺序 Topic 只有 1 个 Queue 1 金融交易流水
局部顺序 多个 Queue,同一 ID 分到同一 Queue Queue 数 订单状态流转

27. 为什么会产生消息重复消费?如何解决?

答案:

1)消息重复的原因

场景 说明
生产端重试 发送超时后重试,Broker 实际已收到消息
消费端 ACK 丢失 消费成功但 ACK 丢失,Broker 重新投递
重平衡 Consumer 宕机触发重平衡,消费位点回滚

2)解决方案:业务幂等性

方案 A:数据库唯一约束

java 复制代码
// 使用订单号作为唯一键
public void consumeOrder(String orderId) {
    try {
        orderDao.insert(order);  // 订单表设置 order_id 唯一索引
    } catch (DuplicateKeyException e) {
        // 重复消费,跳过
        return;
    }
}

方案 B:Redis 原子操作

java 复制代码
// 使用 Redis 的 setIfAbsent
public boolean consumeMessage(String messageId) {
    Boolean success = redisTemplate.opsForValue()
        .setIfAbsent("msg:" + messageId, "1", 24, TimeUnit.HOURS);

    if (Boolean.FALSE.equals(success)) {
        // 已消费过,跳过
        return false;
    }

    // 执行业务逻辑
    return true;
}

方案 C:乐观锁

java 复制代码
// 使用版本号或状态机
public void updateStock(String productId, int count) {
    int updated = orderDao.updateStock(productId, count,
        "status = 'PENDING'");  // 仅更新待支付状态

    if (updated == 0) {
        // 已处理过或状态不匹配,跳过
        return;
    }
}

3)幂等性设计原则

原则 说明
唯一标识 每条消息携带唯一 ID(如订单号)
检查是否已处理 消费前查询是否已处理过
原子性操作 业务逻辑和去重操作在同一事务中

28. RocketMQ 的消息确认机制是怎样的?

答案:

消息确认(ACK)

  • Consumer 消费消息后向 Broker 返回确认
  • Broker 收到确认后才将消息标记为已消费

确认类型:

类型 说明
CONSUME_SUCCESS 消费成功,提交消费进度
RECONSUME_LATER 消费失败,稍后重试
SUSPEND_CURRENT_QUEUE_A_MOMENT 顺序消费时挂起当前队列

代码示例:

java 复制代码
consumer.registerMessageListener(new MessageListenerConcurrently() {
    @Override
    public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
        ConsumeConcurrentlyContext context) {
        try {
            // 处理消息
            processMessage(msgs);
            
            // 消费成功,确认消息
            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
        } catch (Exception e) {
            // 消费失败,稍后重试
            return ConsumeConcurrentlyStatus.RECONSUME_LATER;
        }
    }
});

29. 什么是消息的投递语义?

答案:

投递语义类型:

语义 说明 RocketMQ 支持
At Most Once 最多投递一次,可能丢失,不会重复
At Least Once 至少投递一次,不会丢失,可能重复 ✅ 默认
Exactly Once 精确投递一次,不丢失不重复 ❌ 不支持

RocketMQ 的语义:

  • 默认采用 At Least Once 语义
  • 需要业务层实现幂等性来达到 Exactly Once 效果

30. 如何保证消费的实时性?

答案:

影响实时性的因素:

因素 说明 优化方案
拉取间隔 Consumer 拉取消息的时间间隔 设置 pullInterval=0
批量大小 单次拉取的消息数量 设置 pullBatchSize
长轮询 Broker 挂起请求等待消息 启用长轮询
消费线程数 处理消息的线程数 增加 consumeThreadMax

配置示例:

java 复制代码
consumer.setPullInterval(0);  // 拉取间隔 0
consumer.setPullBatchSize(32);  // 每次拉取 32 条
consumer.setConsumeThreadMin(10);  // 最小 10 个线程
consumer.setConsumeThreadMax(32);  // 最大 32 个线程

31. 什么是死信队列(DLQ)?如何处理?

答案:

死信队列(Dead Letter Queue)

  • 消费失败超过最大重试次数后进入的队列
  • Topic 格式:%DLQ%ConsumerGroup

重试流程:

复制代码
1. 消息消费失败,返回 RECONSUME_LATER
   ↓
2. 消息进入重试 Topic:%RETRY%ConsumerGroup
   ↓
3. 按延迟等级重新投递
   ↓
4. 达到最大重试次数(默认 16 次)
   ↓
5. 消息进入死信 Topic:%DLQ%ConsumerGroup

处理死信队列:

java 复制代码
// 消费死信队列
DefaultMQPushConsumer dlqConsumer = new DefaultMQPushConsumer("dlq_consumer");
dlqConsumer.subscribe("%DLQ%consumer_group", "*");

dlqConsumer.registerMessageListener(new MessageListenerConcurrently() {
    @Override
    public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
        ConsumeConcurrentlyContext context) {
        // 记录日志或人工处理
        log.error("死信消息: {}", msgs);
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    }
});

32. 如何设置消费的重试次数和重试间隔?

答案:

重试次数配置:

java 复制代码
// 设置最大重试次数
consumer.setMaxReconsumeTimes(3);  // 最多重试 3 次

重试间隔(默认):

重试次数 间隔时间
1 1 秒
2 5 秒
3 10 秒
4 30 秒
5 1 分钟
6 2 分钟
7 3 分钟
8 4 分钟
9 5 分钟
10 6 分钟
11 7 分钟
12 8 分钟
13 9 分钟
14 10 分钟
15 20 分钟
16 30 分钟

自定义重试间隔:

java 复制代码
// 修改消息重试等级
msg.setReconsumeTimes(3);  // 设置为第 3 次重试

33. 如何实现消息的回溯消费?

答案:

回溯消费

  • 重新消费历史消息
  • 用于数据修复、历史数据分析

实现方式:

java 复制代码
// 方式 1:根据时间戳重置消费位点
consumer.setConsumeTimestamp("20240101120000");  // 从指定时间开始消费

// 方式 2:重置到最早位点
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);

// 方式 3:通过管理命令重置
mqadmin resetOffsetByTime -n localhost:9876 -g consumer_group -t TopicTest -s 20240101120000

注意事项:

  • 确保历史消息未过期
  • 回溯消费可能导致重复消费,需要幂等性

34. 如何监控消费进度?

答案:

消费进度指标:

指标 说明 查询方式
消费位点 Consumer 消费到的位置 consumer.currentOffset()
最大位点 Topic 中的最大消息位点 Message.PROPERTY_MAX_OFFSET
消息积压量 未消费的消息数量 最大位点 - 消费位点
消费延迟 消息从发送到消费的时间 当前时间 - 消息存储时间

代码示例:

java 复制代码
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
    ConsumeConcurrentlyContext context) {
    MessageExt msg = msgs.get(0);
    long offset = msg.getQueueOffset();
    long maxOffset = Long.parseLong(msg.getProperty(Message.PROPERTY_MAX_OFFSET));
    long lag = maxOffset - offset;
    
    System.out.println("消息积压: " + lag);
    
    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}

命令行查询:

bash 复制代码
# 查询消费进度
mqadmin consumerProgress -n localhost:9876 -g consumer_group

# 查询队列堆积情况
mqadmin queryMsgByQueue -n localhost:9876 -t TopicTest

35. 如何处理消费过程中的异常?

答案:

异常处理策略:

异常类型 处理方式
业务异常 记录日志,返回 RECONSUME_LATER
系统异常 短暂等待后重试
数据异常 记录死信队列,人工处理

代码示例:

java 复制代码
consumer.registerMessageListener(new MessageListenerConcurrently() {
    @Override
    public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
        ConsumeConcurrentlyContext context) {
        try {
            // 处理消息
            processMessage(msgs);
            
            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
        } catch (BusinessException e) {
            // 业务异常,记录日志并重试
            log.error("业务异常: {}", e.getMessage());
            return ConsumeConcurrentlyStatus.RECONSUME_LATER;
        } catch (SystemException e) {
            // 系统异常,等待后重试
            log.error("系统异常: {}", e.getMessage());
            context.setDelayLevel(1);  // 1 秒后重试
            return ConsumeConcurrentlyStatus.RECONSUME_LATER;
        } catch (DataException e) {
            // 数据异常,记录死信队列
            log.error("数据异常: {}", e.getMessage());
            // 手动发送到死信队列或记录数据库
            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
        }
    }
});

36. 如何实现消息的优先级?

答案:

RocketMQ 不直接支持消息优先级,但可以通过以下方式实现:

方案 1:使用不同的 Topic/Queue

  • 高优先级消息使用独立的 Topic 或 Queue
  • 分配更多的消费者处理高优先级队列

方案 2:调整消费逻辑

java 复制代码
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
    ConsumeConcurrentlyContext context) {
    // 按优先级排序
    msgs.sort((m1, m2) -> {
        int p1 = Integer.parseInt(m1.getUserProperty("priority"));
        int p2 = Integer.parseInt(m2.getUserProperty("priority"));
        return p2 - p1;  // 降序排序
    });
    
    // 优先处理高优先级消息
    for (MessageExt msg : msgs) {
        processMessage(msg);
    }
    
    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}

方案 3:延迟消息

  • 低优先级消息使用延迟等级
  • 让低优先级消息延后处理

四、事务消息深度解析

37. 什么是事务消息?解决了什么问题?

答案:

场景问题:

在分布式系统中,需要保证本地事务和消息发送的原子性。

示例:

复制代码
订单服务创建订单 → 发送消息通知库存服务扣减库存

问题:
- 订单创建成功,但消息发送失败 → 库存未扣减
- 消息发送成功,但订单创建失败 → 库存被错误扣减

事务消息的作用:

保证本地事务消息发送的最终一致性。


38. 事务消息的完整流程是什么?

答案:

阶段 1:发送半消息(Half Message)

复制代码
1. Producer 发送半消息到 Broker
   ↓
2. Broker 将半消息暂存,对 Consumer 不可见
   ↓
3. Broker 返回确认

什么是半消息?

  • 消息已发送到 Broker,但 Consumer 暂时无法消费
  • 等待本地事务执行完成后再决定是否投递

阶段 2:执行本地事务

复制代码
1. Producer 执行本地事务(如插入订单)
   ↓
2. 根据事务结果返回状态:
   - COMMIT:提交消息
   - ROLLBACK:回滚消息
   - UNKNOWN:未知状态
阶段 3:提交或回滚
复制代码
情况 A:本地事务成功
   Producer 发送 COMMIT → Broker 将半消息转为可消费状态

情况 B:本地事务失败
   Producer 发送 ROLLBACK → Broker 删除半消息

阶段 4:事务回查(关键机制)

复制代码
如果 Producer 宕机或网络故障,Broker 未收到 COMMIT/ROLLBACK:

1. Broker 定时回查 Producer(默认 60 秒)
   ↓
2. Producer 检查本地事务状态
   ↓
3. Producer 返回最终状态(COMMIT/ROLLBACK)
   ↓
4. Broker 根据回查结果处理消息

流程图

复制代码
                    ┌─────────────┐
                    │  Producer   │
                    └──────┬──────┘
                           │
            ① 发送半消息      │
                           ↓
                    ┌─────────────┐
                    │   Broker    │
                    └──────┬──────┘
                           │
            ② 返回确认        │
                           │
            ③ 执行本地事务     │
                           │
            ④ 提交/回滚       │
                           ↓
                    ┌─────────────┐
                    │   Broker    │
                    └──────┬──────┘
                           │
            ⑤ 投递消息(如提交)│
                           ↓
                    ┌─────────────┐
                    │  Consumer   │
                    └─────────────┘

如果 ④ 未收到 → ⑥ 事务回查 → ⑦ 返回最终状态

39. 事务消息的实现细节

1)半消息是如何对 Consumer 不可见的?

核心机制:偷梁换柱(Topic 替换)

复制代码
1. Producer 发送半消息时
   Broker 将消息的 Topic 替换为内部 Topic:RMQ_SYS_TRANS_HALF_TOPIC

2. Consumer 只订阅业务 Topic
   因此无法拉取到半消息

3. Producer 发送 COMMIT 后
   Broker 将消息还原到原始 Topic,Consumer 才能消费

2)代码示例

java 复制代码
// 创建事务生产者
TransactionMQProducer producer = new TransactionMQProducer("transaction_group");
producer.setNamesrvAddr("localhost:9876");

// 设置事务监听器
producer.setTransactionListener(new TransactionListener() {
    @Override
    public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        // 执行本地事务
        boolean success = createOrder(msg);

        if (success) {
            return LocalTransactionState.COMMIT_MESSAGE;  // 提交
        } else {
            return LocalTransactionState.ROLLBACK_MESSAGE;  // 回滚
        }
    }

    @Override
    public LocalTransactionState checkLocalTransaction(MessageExt msg) {
        // 事务回查:查询本地事务状态
        boolean success = checkOrderStatus(msg);

        if (success) {
            return LocalTransactionState.COMMIT_MESSAGE;
        } else {
            return LocalTransactionState.ROLLBACK_MESSAGE;
        }
    }
});

producer.start();

// 发送事务消息
Message message = new Message("OrderTopic", "create", orderJson.getBytes());
producer.sendMessageInTransaction(message, null);

40. 事务消息的注意事项

注意事项 说明
本地事务执行时间不要过长 避免触发事务回查
正确处理 UNKNOWN 状态 如果事务正在执行中,返回 UNKNOWN 让 Broker 继续回查
Consumer 需要幂等性 即使事务消息也可能重复投递
事务回查有次数限制 默认最多回查 15 次,超过后消息会被丢弃

41. 事务消息和本地消息表有什么区别?

答案:

对比项 事务消息 本地消息表
实现方式 RocketMQ 内置机制 业务自行实现
复杂度
性能
一致性 最终一致性 最终一致性
适用场景 异步解耦 需要强控制

本地消息表实现:

java 复制代码
// 1. 在本地事务中插入业务数据和消息记录
@Transactional
public void createOrder(Order order) {
    // 插入订单
    orderDao.insert(order);
    
    // 插入消息记录
    messageDao.insert(new Message("OrderTopic", orderJson));
}

// 2. 定时任务扫描未发送的消息
@Scheduled(fixedRate = 5000)
public void scanAndSendMessages() {
    List<Message> messages = messageDao.selectUnsentMessages();
    
    for (Message message : messages) {
        try {
            producer.send(message);
            messageDao.markAsSent(message.getId());
        } catch (Exception e) {
            // 发送失败,等待下次重试
        }
    }
}

42. 事务消息如何保证消息不丢失?

答案:

保证机制:

  1. 半消息持久化

    • 半消息写入 CommitLog 并同步刷盘
    • 确保半消息不丢失
  2. 事务回查机制

    • 即使 Producer 宕机,Broker 会主动回查
    • 确保最终状态明确
  3. 发送重试

    • 本地事务成功后,发送 COMMIT 失败会重试
    • 确保消息最终被投递

配置建议:

properties 复制代码
# Broker 配置(同步刷盘)
flushDiskType=SYNC_FLUSH

# Producer 配置(重试次数)
producer.setRetryTimesWhenSendAsyncFailed(3);

43. 事务消息的性能如何优化?

答案:

优化措施:

优化措施 说明 效果
减少事务回查频率 增加回查间隔时间 减少 Broker 压力
批量提交 将多个事务合并提交 减少 Broker 压力
异步化处理 本地事务异步执行 提高并发度

代码示例:

java 复制代码
// 增加回查间隔
producer.setTransactionCheckInterval(60000);  // 60 秒

// 异步执行本地事务
producer.setTransactionListener(new TransactionListener() {
    @Override
    public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        // 异步执行本地事务
        CompletableFuture.runAsync(() -> {
            boolean success = createOrder(msg);
            // 回调通知结果
        });
        
        return LocalTransactionState.UNKNOW;  // 先返回未知
    }
    
    @Override
    public LocalTransactionState checkLocalTransaction(MessageExt msg) {
        // 检查异步事务的状态
        return getAsyncTransactionStatus(msg);
    }
});

44. 事务消息和 TCC 有什么区别?

答案:

对比项 事务消息 TCC
实现复杂度
性能
一致性 最终一致性 最终一致性
业务侵入性
适用场景 异步解耦 需要强控制

TCC 实现:

java 复制代码
// Try 阶段:预留资源
public void tryDeductStock(String orderId, int count) {
    inventoryDao.freeze(orderId, count);  // 冻结库存
}

// Confirm 阶段:确认扣减
public void confirmDeductStock(String orderId) {
    inventoryDao.deduct(orderId);  // 扣减库存
}

// Cancel 阶段:取消预留
public void cancelDeductStock(String orderId) {
    inventoryDao.unfreeze(orderId);  // 释放冻结库存
}

45. 事务消息的回查超时时间是多久?如何配置?

答案:

默认配置:

  • 事务回查间隔:60 秒
  • 最大回查次数:15 次

配置方式:

properties 复制代码
# Broker 配置
transactionTimeOut=60000  # 事务超时时间 60 秒
transactionCheckMax=15   # 最大回查次数 15 次
transactionCheckInterval=60000  # 回查间隔 60 秒

代码配置:

java 复制代码
producer.setTransactionCheckInterval(60000);  // 回查间隔

46. 事务消息如何实现跨机房事务?

答案:

跨机房事务方案:

复制代码
机房 A(订单服务)
    ↓
发送事务消息到机房 B 的 RocketMQ
    ↓
机房 B(库存服务)消费消息

注意事项:

注意事项 说明
网络延迟 跨机房延迟较高,需增加超时时间
数据一致性 采用最终一致性模型
故障隔离 机房 B 故障不影响机房 A

配置示例:

java 复制代码
// 连接跨机房 RocketMQ
producer.setNamesrvAddr("机房东_B:9876");
producer.setSendMsgTimeout(10000);  // 增加超时时间

// 消费端增加重试
consumer.setMaxReconsumeTimes(5);

五、性能优化与实战技巧

47. 如何提高 RocketMQ 的吞吐量?

答案:

1)生产端优化

优化措施 说明 代码示例
批量发送 一次发送多条消息,减少网络请求 producer.send(messages)
异步发送 不阻塞等待返回 producer.send(message, callback)
消息压缩 对大消息启用压缩 message.setCompressed(true)

批量发送示例:

java 复制代码
List<Message> messages = new ArrayList<>();
for (int i = 0; i < 100; i++) {
    messages.add(new Message("TopicTest", ("Hello " + i).getBytes()));
}

// 批量发送
producer.send(messages);

2)消费端优化

优化措施 说明 配置参数
增加消费者实例 提高并行度(不超过队列数) 部署多个 Consumer
增加消费线程数 单实例多线程消费 setConsumeThreadMax(32)
批量消费 一次处理多条消息 setConsumeMessageBatchMaxSize(32)

批量消费示例:

java 复制代码
consumer.setConsumeMessageBatchMaxSize(32);  // 一次最多消费 32 条

consumer.registerMessageListener(new MessageListenerConcurrently() {
    @Override
    public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
        ConsumeConcurrentlyContext context) {
        // 批量处理
        batchProcess(msgs);
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    }
});

3)Broker 端优化

优化措施 说明 配置参数
异步刷盘 提高写入性能 flushDiskType=ASYNC_FLUSH
异步复制 提高写入性能 brokerRole=ASYNC_MASTER
增大队列数 提高并行度 defaultTopicQueueNums=16
开启内存映射 利用 Page Cache 提高读写性能 默认开启

4)系统级优化

优化措施 说明
调整 JVM 内存 堆内存建议 8-16GB,给 Page Cache 留足空间
使用 G1 GC 减少停顿时间
调整文件描述符限制 提高到 655350
使用 SSD 磁盘 提高 IO 性能

48. 如何处理消息堆积?

答案:

1)诊断问题

复制代码
1. 检查堆积情况
   mqadmin queryMsgByQueue -n localhost:9876 -t TopicTest

2. 分析堆积原因
   - 消费者处理速度慢?
   - 消费者实例过少?
   - 消息生产速度过快?

2)解决方案

方案 适用场景 说明
增加消费者 消费者实例不足 增加到与队列数相同
增加队列数 队列数过少 动态扩容 Topic 队列
优化消费逻辑 单条消息处理慢 批量消费、异步处理
跳过非重要消息 积压严重,可容忍丢失 重置消费位点
临时扩容 Broker Broker 压力大 增加存储和转发能力

3)代码示例:跳过堆积消息

java 复制代码
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
    ConsumeConcurrentlyContext context) {
    long offset = msgs.get(0).getQueueOffset();
    long maxOffset = Long.parseLong(msgs.get(0).getProperty(
        Message.PROPERTY_MAX_OFFSET));
    long diff = maxOffset - offset;

    // 如果堆积超过 10 万条,直接跳过
    if (diff > 100000) {
        System.out.println("消息堆积严重,跳过消费");
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    }

    // 正常消费
    processMessage(msgs);
    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}

49. RocketMQ 的重试机制是怎样的?

答案:

1)消息重试流程

复制代码
1. Consumer 消费失败,返回 RECONSUME_LATER
   ↓
2. 消息进入重试 Topic:%RETRY%ConsumerGroup
   ↓
3. 按延迟等级重新投递(1s 5s 10s 30s 1m 2m ...)
   ↓
4. 达到最大重试次数(默认 16 次)后
   ↓
5. 消息进入死信 Topic:%DLQ%ConsumerGroup

2)重试间隔时间

重试次数 间隔时间
1 1 秒
2 5 秒
3 10 秒
4 30 秒
5 1 分钟
6 2 分钟
... ...

3)配置示例

java 复制代码
// 设置最大重试次数
consumer.setMaxReconsumeTimes(3);

// 消费逻辑
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
    ConsumeConcurrentlyContext context) {
    try {
        processMessage(msgs);
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    } catch (Exception e) {
        // 返回稍后重试
        return ConsumeConcurrentlyStatus.RECONSUME_LATER;
    }
}

4)死信队列处理

java 复制代码
// 消费死信队列
DefaultMQPushConsumer dlqConsumer = new DefaultMQPushConsumer("dlq_consumer");
dlqConsumer.subscribe("%DLQ%consumer_group", "*");

dlqConsumer.registerMessageListener(new MessageListenerConcurrently() {
    @Override
    public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
        ConsumeConcurrentlyContext context) {
        // 记录日志或人工处理
        log.error("死信消息: {}", msgs);
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    }
});

50. 如何优化 Broker 的性能?

答案:

Broker 优化措施:

优化项 配置 说明
刷盘策略 flushDiskType=ASYNC_FLUSH 异步刷盘提高性能
复制模式 brokerRole=ASYNC_MASTER 异步复制提高性能
线程池配置 sendMessageThreadPoolNums 调整发送线程数
Netty 参数 serverSelectorThreads 调整网络线程数
文件预热 warmMapedFileEnable=true 预热 MappedFile

配置示例:

properties 复制代码
# Broker 配置优化
flushDiskType=ASYNC_FLUSH
brokerRole=ASYNC_MASTER
sendMessageThreadPoolNums=4
serverSelectorThreads=4
warmMapedFileEnable=true

51. 如何优化 Producer 的性能?

答案:

Producer 优化措施:

优化项 配置 说明
发送超时 sendMsgTimeout 设置合理的超时时间
重试次数 retryTimesWhenSendFailed 设置发送重试次数
压缩 compressMsgBodyOverHowmuch 设置压缩阈值
批量大小 defaultTopicQueueNums 增大队列数

配置示例:

java 复制代码
// Producer 配置优化
producer.setSendMsgTimeout(3000);  // 3 秒超时
producer.setRetryTimesWhenSendFailed(3);  // 重试 3 次
producer.setRetryTimesWhenSendAsyncFailed(3);
producer.setCompressMsgBodyOverHowmuch(4096);  // 超过 4KB 压缩

52. 如何优化 Consumer 的性能?

答案:

Consumer 优化措施:

优化项 配置 说明
消费线程 consumeThreadMin/Max 调整消费线程数
批量大小 pullBatchSize 调整拉取批次大小
拉取间隔 pullInterval 调整拉取间隔
最小消费 consumeMessageBatchMaxSize 调整最小消费数量

配置示例:

java 复制代码
// Consumer 配置优化
consumer.setConsumeThreadMin(10);  // 最小 10 个线程
consumer.setConsumeThreadMax(32);  // 最大 32 个线程
consumer.setPullBatchSize(32);  // 每次拉取 32 条
consumer.setPullInterval(0);  // 拉取间隔 0
consumer.setConsumeMessageBatchMaxSize(32);  // 最小消费 32 条

53. 如何监控 RocketMQ 的性能?

答案:

关键监控指标:

指标 说明 告警阈值
TPS 每秒处理的消息数 < 预期值
延迟 消息传输延迟 > 100ms
堆积量 未消费的消息数 > 10000
失败率 发送/消费失败比例 > 1%
CPU 使用率 Broker/CPU 占用 > 80%
内存使用率 Broker 内存占用 > 80%
磁盘 IO 磁盘读写速度 > 阈值

监控工具:

java 复制代码
// 使用 JMX 监控
MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
ObjectName name = new ObjectName("org.apache.rocketmq:type=BrokerSub");
MBeanInfo info = mBeanServer.getMBeanInfo(name);

54. 如何进行容量规划?

答案:

容量规划步骤:

  1. 预估消息量

    • 日消息量:1000 万条/天
    • 峰值 TPS:5000 条/秒
  2. 计算 Broker 数量

    • 单 Broker 吞吐量:10000 TPS
    • 需要 Broker 数量:5000 / 10000 = 0.5 → 至少 2 个(主从)
  3. 计算存储容量

    • 消息大小:1KB/条
    • 日存储量:1000 万 * 1KB = 10GB
    • 保留 7 天:10GB * 7 = 70GB
    • 预留冗余:70GB * 2 = 140GB
  4. 计算网络带宽

    • 峰值带宽:5000 * 1KB = 5MB/s
    • 考虑冗余:5MB/s * 3 = 15MB/s

配置建议:

properties 复制代码
# Broker 配置
maxMessageSize=4194304  # 4MB
flushDiskType=ASYNC_FLUSH
brokerRole=SYNC_MASTER

55. 如何优化大消息的处理?

答案:

大消息优化策略:

策略 说明
消息压缩 对消息体进行压缩
消息拆分 将大消息拆分成多条小消息
外部存储 将大消息内容存到 OSS,消息只存 URL
流式处理 使用流式读写,避免全量加载

代码示例(消息压缩):

java 复制代码
Message message = new Message("TopicTest", largeData.getBytes());
message.setCompressed(true);  // 启用压缩

代码示例(外部存储):

java 复制代码
// 上传到 OSS
String url = ossClient.upload("bucket", "file.txt", largeData);

// 发送 URL
Message message = new Message("TopicTest", url.getBytes());
producer.send(message);

// 消费端下载 URL
String url = new String(msg.getBody());
byte[] data = ossClient.download(url);

56. 如何实现消息的限流?

答案:

限流策略:

策略 说明
生产端限流 控制发送速率
Broker 限流 控制接收速率
消费端限流 控制拉取速率

代码示例(生产端限流):

java 复制代码
// 使用 RateLimiter
RateLimiter rateLimiter = RateLimiter.create(1000);  // 1000 TPS

for (Message message : messages) {
    rateLimiter.acquire();  // 限流
    producer.send(message);
}

Broker 配置:

properties 复制代码
# Broker 限流配置
maxTransferBytesOnMessageInMemory=1048576  # 最大传输字节
maxMessageSize=4194304  # 最大消息大小

57. 如何实现消息的降级?

答案:

降级策略:

策略 说明
跳过非重要消息 积压时跳过非核心消息
批量丢弃 积压严重时批量丢弃
异步处理 将同步改为异步处理

代码示例:

java 复制代码
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
    ConsumeConcurrentlyContext context) {
    long lag = calculateLag();
    
    // 积压严重,降级处理
    if (lag > 100000) {
        // 只处理高优先级消息
        msgs = filterHighPriority(msgs);
        
        // 批量丢弃部分消息
        if (msgs.size() > 100) {
            msgs = msgs.subList(0, 100);
        }
    }
    
    processMessage(msgs);
    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}

58. 如何实现多级缓存?

答案:

多级缓存架构:

复制代码
L1: Redis 缓存(热点数据)
    ↓
L2: RocketMQ(缓冲队列)
    ↓
L3: 数据库(持久存储)

代码示例:

java 复制代码
// 写入流程
public void writeData(Data data) {
    // L1: 写入 Redis
    redisTemplate.set(data.getKey(), data, 1, TimeUnit.HOURS);
    
    // L2: 发送到 RocketMQ
    Message message = new Message("DataTopic", data.toJson().getBytes());
    producer.send(message);
}

// 消费流程
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
    ConsumeConcurrentlyContext context) {
    for (MessageExt msg : msgs) {
        Data data = Data.fromJson(new String(msg.getBody()));
        
        // L1: 检查 Redis
        if (redisTemplate.hasKey(data.getKey())) {
            continue;
        }
        
        // L3: 写入数据库
        databaseDao.insert(data);
        
        // 清除 Redis 缓存
        redisTemplate.delete(data.getKey());
    }
    
    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}

六、延迟消息与高级特性

59. 什么是延迟消息?如何使用?

答案:

延迟消息

  • 消息发送后延迟指定时间后才被消费
  • 适用于定时任务、超时处理等场景

使用示例:

java 复制代码
// 设置延迟等级(18 个级别)
Message message = new Message("TopicTest", "TagA", "Hello".getBytes());
message.setDelayTimeLevel(3);  // 3 级对应 10 秒
producer.send(message);

延迟等级表:

等级 延迟时间
1 1 秒
2 5 秒
3 10 秒
4 30 秒
5 1 分钟
6 2 分钟
7 3 分钟
8 4 分钟
9 5 分钟
10 6 分钟
11 7 分钟
12 8 分钟
13 9 分钟
14 10 分钟
15 20 分钟
16 30 分钟
17 1 小时
18 2 小时

60. 延迟消息的实现原理是什么?

答案:

实现原理:

  1. 发送延迟消息

    • Producer 发送消息时设置延迟等级
    • Broker 将消息存储到延迟 Topic(SCHEDULE_TOPIC_XXXX
  2. 延迟投递

    • Broker 有定时任务扫描延迟 Topic
    • 到期的消息重新投递到原始 Topic
  3. 消息消费

    • Consumer 从原始 Topic 拉取消息

流程图:

复制代码
Producer 发送延迟消息
    ↓
Broker 存储到 SCHEDULE_TOPIC_XXXX(内部 Topic)
    ↓
定时任务扫描(默认 1 秒)
    ↓
到期消息投递到原始 Topic
    ↓
Consumer 消费消息

61. 延迟消息有什么限制?

答案:

限制:

限制 说明
固定等级 只支持 18 个固定延迟等级
最大延迟 最大延迟 2 小时
精度 延迟精度约 1 秒
顺序性 延迟消息不保证顺序

解决方案:

方案 说明
多次延迟 通过多次发送实现更长时间延迟
自定义延迟 使用定时任务 + 延迟消息
第三方服务 使用专业的延迟队列服务(如 Redisson)

62. 什么是消息轨迹?如何启用?

答案:

消息轨迹

  • 记录消息从发送到消费的完整生命周期
  • 用于问题排查、性能分析

启用方式:

properties 复制代码
# Broker 配置
traceTopicEnable=true
traceTopicName=RMQ_SYS_TRACE_TOPIC

# Producer 配置
producer.setUseTrace(true);

# Consumer 配置
consumer.setUseTrace(true);

轨迹信息:

复制代码
时间 | 操作 | 节点 | 状态
-----|------|------|------
10:00:00 | 发送消息 | Producer | 成功
10:00:01 | 存储消息 | Broker | 成功
10:00:02 | 拉取消息 | Consumer | 成功
10:00:03 | 消费消息 | Consumer | 成功

63. 如何实现消息的幂等性?

答案:

幂等性方案:

方案 1:数据库唯一约束

java 复制代码
public void consumeOrder(String orderId) {
    try {
        orderDao.insert(order);  // 唯一索引
    } catch (DuplicateKeyException e) {
        // 重复消费,跳过
        return;
    }
}

方案 2:Redis 原子操作

java 复制代码
public boolean consumeMessage(String messageId) {
    Boolean success = redisTemplate.opsForValue()
        .setIfAbsent("msg:" + messageId, "1", 24, TimeUnit.HOURS);
    
    if (Boolean.FALSE.equals(success)) {
        return false;
    }
    
    // 处理消息
    return true;
}

方案 3:分布式锁

java 复制代码
public void consumeMessage(String messageId) {
    String lockKey = "lock:msg:" + messageId;
    
    try {
        boolean locked = redisTemplate.opsForValue()
            .setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS);
        
        if (!locked) {
            return;  // 其他线程正在处理
        }
        
        // 处理消息
        processMessage(messageId);
    } finally {
        redisTemplate.delete(lockKey);
    }
}

64. 如何实现消息的批量发送?

答案:

批量发送方式:

方式 1:使用 List

java 复制代码
List<Message> messages = new ArrayList<>();
for (int i = 0; i < 100; i++) {
    messages.add(new Message("TopicTest", ("Hello " + i).getBytes()));
}

SendResult result = producer.send(messages);

方式 2:使用 MessageBatch

java 复制代码
MessageBatch batch = MessageBatch.generateFromList(messages);
SendResult result = producer.send(batch);

注意事项:

  • 单次发送大小不超过 4MB
  • 消息的 Topic 必须相同
  • 建议消息大小接近

65. 如何实现消息的顺序批量消费?

答案:

顺序批量消费:

java 复制代码
consumer.registerMessageListener(new MessageListenerOrderly() {
    @Override
    public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs,
        ConsumeOrderlyContext context) {
        // 批量处理(保持顺序)
        for (MessageExt msg : msgs) {
            processMessage(msg);
        }
        
        return ConsumeOrderlyStatus.SUCCESS;
    }
});

注意事项:

  • 使用 MessageListenerOrderly 监听器
  • 同一 Queue 内的消息有序
  • 批量处理不影响顺序性

66. 如何实现消息的广播和点对点模式?

答案:

广播模式:

java 复制代码
// 所有 Consumer 都能收到消息
consumer.setMessageModel(MessageModel.BROADCASTING);
consumer.subscribe("TopicTest", "*");

点对点模式(集群消费):

java 复制代码
// 一条消息只被一个 Consumer 消费
consumer.setMessageModel(MessageModel.CLUSTERING);
consumer.subscribe("TopicTest", "*");

对比:

模式 特点 适用场景
广播 所有 Consumer 都收到消息 配置通知、缓存刷新
集群 消息负载均衡 业务处理、任务分发

67. 如何实现消息的 TTL(过期时间)?

答案:

RocketMQ 没有直接的 TTL 机制,但可以通过以下方式实现:

方式 1:使用延迟消息

java 复制代码
// 发送延迟消息,消费时检查是否过期
message.setDelayTimeLevel(5);  // 1 分钟后消费

@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
    ConsumeConcurrentlyContext context) {
    long now = System.currentTimeMillis();
    long createTime = msgs.get(0).getStoreTimestamp();
    
    if (now - createTime > 60000) {  // 超过 1 分钟
        // 消息过期,丢弃
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    }
    
    processMessage(msgs);
    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}

方式 2:消费端检查过期

java 复制代码
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
    ConsumeConcurrentlyContext context) {
    long expiryTime = Long.parseLong(msgs.get(0).getUserProperty("expiryTime"));
    
    if (System.currentTimeMillis() > expiryTime) {
        // 消息过期
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    }
    
    processMessage(msgs);
    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}

68. 如何实现消息的优先级(自定义)?

答案:

自定义优先级方案:

方案 1:使用多个 Topic

java 复制代码
// 高优先级消息
Message highPriority = new Message("TopicHigh", "content".getBytes());
producer.send(highPriority);

// 低优先级消息
Message lowPriority = new Message("TopicLow", "content".getBytes());
producer.send(lowPriority);

// Consumer 优先消费高优先级 Topic
PriorityConsumer priorityConsumer = new PriorityConsumer();
priorityConsumer.setHighPriorityTopic("TopicHigh");
priorityConsumer.setLowPriorityTopic("TopicLow");

方案 2:消费端排序

java 复制代码
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
    ConsumeConcurrentlyContext context) {
    // 按优先级排序
    msgs.sort((m1, m2) -> {
        int p1 = Integer.parseInt(m1.getUserProperty("priority", "0"));
        int p2 = Integer.parseInt(m2.getUserProperty("priority", "0"));
        return p2 - p1;  // 降序排序
    });
    
    // 处理消息
    for (MessageExt msg : msgs) {
        processMessage(msg);
    }
    
    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}

69. 如何实现消息的路由策略?

答案:

路由策略:

策略 1:哈希路由

java 复制代码
public class HashMessageQueueSelector implements MessageQueueSelector {
    @Override
    public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
        String key = (String) arg;
        int index = Math.abs(key.hashCode()) % mqs.size();
        return mqs.get(index);
    }
}

// 使用
producer.send(message, new HashMessageQueueSelector(), "ORDER_001");

策略 2:随机路由

java 复制代码
public class RandomMessageQueueSelector implements MessageQueueSelector {
    @Override
    public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
        return mqs.get(ThreadLocalRandom.current().nextInt(mqs.size()));
    }
}

// 使用
producer.send(message, new RandomMessageQueueSelector(), null);

策略 3:机房路由

java 复制代码
public class RoomMessageQueueSelector implements MessageQueueSelector {
    @Override
    public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
        String room = (String) arg;
        
        // 优先选择同机房的 Queue
        for (MessageQueue mq : mqs) {
            if (mq.getBrokerName().startsWith(room)) {
                return mq;
            }
        }
        
        // 没有同机房,随机选择
        return mqs.get(ThreadLocalRandom.current().nextInt(mqs.size()));
    }
}

// 使用
producer.send(message, new RoomMessageQueueSelector(), "RoomA");

70. 如何实现消息的分组处理?

答案:

分组处理方案:

方案 1:使用 Group ID

java 复制代码
// 发送时设置 Group ID
message.putUserProperty("groupId", "GROUP_001");

// 消费端按 Group 分组处理
Map<String, List<MessageExt>> groupedMsgs = msgs.stream()
    .collect(Collectors.groupingBy(m -> m.getUserProperty("groupId")));

for (Map.Entry<String, List<MessageExt>> entry : groupedMsgs.entrySet()) {
    String groupId = entry.getKey();
    List<MessageExt> groupMsgs = entry.getValue();
    
    // 批量处理同一组的消息
    batchProcess(groupId, groupMsgs);
}

方案 2:使用事务消息

java 复制代码
// 发送事务消息
producer.sendMessageInTransaction(message, "GROUP_001");

// 消费端按组处理事务
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
    String groupId = (String) arg;
    
    // 处理同一组的事务
    processGroupTransaction(groupId, msg);
    
    return LocalTransactionState.COMMIT_MESSAGE;
}

七、场景面试题(实战必问)

71. 场景题:订单超时未支付自动取消,如何实现?

答案:

方案 1:延迟消息

java 复制代码
// 发送订单创建消息时,设置延迟等级(30 分钟)
Message message = new Message("OrderTopic", "create", orderJson.getBytes());
message.setDelayTimeLevel(16);  // 16 级对应 30 分钟
producer.send(message);

// 消费者检查订单状态
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
    ConsumeConcurrentlyContext context) {
    String orderId = msgs.get(0).getKeys();

    // 检查订单状态
    Order order = orderDao.selectById(orderId);

    if (order.getStatus() == OrderStatus.UNPAID) {
        // 未支付,自动取消
        orderDao.updateStatus(orderId, OrderStatus.CANCELLED);
    }

    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}

方案 2:定时任务扫描

java 复制代码
// 定时扫描未支付订单
@Scheduled(cron = "0 */10 * * * ?")  // 每 10 分钟执行
public void scanUnpaidOrders() {
    // 查询创建时间超过 30 分钟且未支付的订单
    List<Order> unpaidOrders = orderDao.selectUnpaidOrders(30);

    for (Order order : unpaidOrders) {
        // 发送取消消息或直接取消
        orderDao.updateStatus(order.getId(), OrderStatus.CANCELLED);
    }
}

对比两种方案

方案 优点 缺点
延迟消息 准时、无需定时任务 延迟等级固定,不支持任意时间
定时任务 灵活、支持任意时间 需要定时任务,可能有延迟

72. 场景题:如何保证下单和扣库存的一致性?

答案:

方案:事务消息 + 幂等性

复制代码
1. 订单服务发送事务消息
   - 半消息暂存

2. 订单服务执行本地事务
   - 创建订单(数据库)

3. 根据事务结果提交或回滚消息

4. 库存服务消费消息
   - 扣减库存
   - 保证幂等性(订单号唯一)

代码实现

java 复制代码
// 1. 订单服务发送事务消息
TransactionMQProducer producer = new TransactionMQProducer("order_group");
producer.setTransactionListener(new TransactionListener() {
    @Override
    public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        // 执行本地事务:创建订单
        boolean success = orderDao.create(order);

        if (success) {
            return LocalTransactionState.COMMIT_MESSAGE;
        } else {
            return LocalTransactionState.ROLLBACK_MESSAGE;
        }
    }

    @Override
    public LocalTransactionState checkLocalTransaction(MessageExt msg) {
        // 回查订单状态
        Order order = orderDao.selectById(msg.getKeys());
        return order != null ? LocalTransactionState.COMMIT_MESSAGE
                            : LocalTransactionState.ROLLBACK_MESSAGE;
    }
});

producer.start();

// 2. 库存服务消费消息(幂等)
consumer.subscribe("OrderTopic", "*");
consumer.registerMessageListener(new MessageListenerConcurrently() {
    @Override
    public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
        ConsumeConcurrentlyContext context) {
        String orderId = msgs.get(0).getKeys();

        // 幂等性:检查是否已扣减
        if (redisTemplate.hasKey("stock:" + orderId)) {
            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
        }

        // 扣减库存
        inventoryDao.deduct(orderId);

        // 标记已处理
        redisTemplate.opsForValue().set("stock:" + orderId, "1", 24, TimeUnit.HOURS);

        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    }
});

73. 场景题:消息积压严重,如何快速恢复?

答案:

应急处理步骤

复制代码
1. 停止消息生产(如业务允许)
   ↓
2. 增加消费者实例
   - 如果队列数 = 消费者数,先增加队列数
   ↓
3. 优化消费逻辑
   - 批量消费
   - 异步处理
   - 减少数据库查询次数
   ↓
4. 临时方案:重置消费位点
   - 跳过部分非重要消息
   ↓
5. 恢复消息生产
   ↓
6. 监控消费进度,确保追上

代码示例:批量消费 + 异步处理

java 复制代码
// 启用批量消费
consumer.setConsumeMessageBatchMaxSize(100);
consumer.setConsumeThreadMin(32);
consumer.setConsumeThreadMax(64);

consumer.registerMessageListener(new MessageListenerConcurrently() {
    @Override
    public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
        ConsumeConcurrentlyContext context) {
        // 异步批量处理
        CompletableFuture.runAsync(() -> {
            batchProcess(msgs);
        }, asyncExecutor);

        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    }
});

74. 场景题:如何设计 RocketMQ 保证系统高可用?

答案:

架构设计

复制代码
1. NameServer 集群(至少 3 个节点)
   - 无状态,任意节点宕机不影响

2. Broker 主从集群
   - Master 负责读写
   - Slave 实时同步数据
   - 支持 DLedger 模式自动故障转移

3. Producer/Consumer 多实例部署
   - 避免单点故障

高可用配置

properties 复制代码
# Broker 配置(同步复制 + 同步刷盘)
brokerRole=SYNC_MASTER
flushDiskType=SYNC_FLUSH

# NameServer 集群
namesrvAddr=192.168.1.1:9876;192.168.1.2:9876;192.168.1.3:9876

# Producer 配置(多个 NameServer)
producer.setNamesrvAddr("192.168.1.1:9876;192.168.1.2:9876");

# Consumer 配置(多个 NameServer)
consumer.setNamesrvAddr("192.168.1.1:9876;192.168.1.2:9876");

75. 场景题:分布式事务场景下,如何选择合适的技术方案?

答案:

方案对比

方案 一致性 复杂度 性能 适用场景
2PC 强一致性 金融核心交易
TCC 最终一致性 订单、库存
本地消息表 最终一致性 异步通知
RocketMQ 事务消息 最终一致性 异步解耦

RocketMQ 事务消息的优势

复制代码
1. 业务侵入性小
   - 不需要改造为 TCC Try/Confirm/Cancel 模式

2. 高性能
   - 基于消息队列的异步处理

3. 实现简单
   - 只需实现事务监听器接口

76. 场景题:电商秒杀场景如何使用 RocketMQ?

答案:

秒杀场景特点

特点 说明
高并发 瞬间大量请求
库存有限 需要严格控制库存
防超卖 不能超卖库存

架构设计

复制代码
1. 用户请求 → Gateway
   ↓
2. 预减库存(Redis)
   ↓
3. 发送消息到 RocketMQ
   ↓
4. 消费者处理订单(MySQL)

代码实现

java 复制代码
// 1. 预减库存(Redis)
public boolean preDeductStock(String productId, int count) {
    String key = "stock:" + productId;
    Long stock = redisTemplate.opsForValue().decrement(key, count);
    
    if (stock < 0) {
        redisTemplate.opsForValue().increment(key, count);  // 回滚
        return false;  // 库存不足
    }
    
    return true;
}

// 2. 发送消息
if (preDeductStock(productId, count)) {
    Message message = new Message("SeckillTopic", orderJson.getBytes());
    producer.send(message);
}

// 3. 消费者处理订单
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
    ConsumeConcurrentlyContext context) {
    for (MessageExt msg : msgs) {
        Order order = parseOrder(msg);
        
        // 幂等性:检查订单是否已创建
        if (orderDao.exists(order.getId())) {
            continue;
        }
        
        // 创建订单
        orderDao.insert(order);
        
        // 扣减真实库存(数据库)
        inventoryDao.deduct(order.getProductId(), order.getCount());
    }
    
    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}

77. 场景题:如何保证消息的消费顺序?

答案:

顺序消费场景

场景 要求
订单状态流转 创建 → 支付 → 发货 → 完成
支付流水 同一账户的交易按时间顺序
日志收集 同一用户的日志按时间顺序

实现方案

java 复制代码
// Producer:将同一 ID 的消息发到同一 Queue
public class OrderQueueSelector implements MessageQueueSelector {
    @Override
    public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
        String orderId = (String) arg;
        int index = Math.abs(orderId.hashCode()) % mqs.size();
        return mqs.get(index);
    }
}

// 发送订单状态变更消息
producer.send(createMsg, new OrderQueueSelector(), "ORDER_001");
producer.send(payMsg, new OrderQueueSelector(), "ORDER_001");
producer.send(shipMsg, new OrderQueueSelector(), "ORDER_001");

// Consumer:顺序消费
consumer.registerMessageListener(new MessageListenerOrderly() {
    @Override
    public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs,
        ConsumeOrderlyContext context) {
        // 单线程顺序处理
        for (MessageExt msg : msgs) {
            processMessage(msg);
        }
        return ConsumeOrderlyStatus.SUCCESS;
    }
});

78. 场景题:如何实现消息的回溯和补单?

答案:

回溯消费方案

java 复制代码
// 1. 查询需要补单的订单
List<Order> failedOrders = orderDao.selectFailedOrders();

// 2. 发送补单消息
for (Order order : failedOrders) {
    Message message = new Message("OrderTopic", "补单", order.toJson().getBytes());
    message.setKeys(order.getId());
    producer.send(message);
}

// 3. 消费者处理补单
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
    ConsumeConcurrentlyContext context) {
    for (MessageExt msg : msgs) {
        String orderId = msg.getKeys();
        
        // 检查是否需要补单
        Order order = orderDao.selectById(orderId);
        if (order != null && order.getStatus() == OrderStatus.PENDING) {
            // 执行补单逻辑
            processOrder(order);
        }
    }
    
    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}

历史数据修复

java 复制代码
// 重置消费位点到指定时间
mqadmin resetOffsetByTime -n localhost:9876 -g consumer_group -t OrderTopic -s 20240101000000

// 或代码方式重置
consumer.setConsumeTimestamp("20240101000000");

79. 场景题:如何实现跨服务的数据同步?

答案:

数据同步方案

复制代码
服务 A 修改数据
    ↓
发送消息到 RocketMQ
    ↓
服务 B 消费消息
    ↓
更新本地缓存/数据库

代码实现

java 复制代码
// 1. 服务 A:发送数据变更消息
public void updateProduct(Product product) {
    // 更新数据库
    productDao.update(product);
    
    // 发送消息
    Message message = new Message("ProductTopic", "UPDATE", product.toJson().getBytes());
    message.setKeys(product.getId());
    producer.send(message);
}

// 2. 服务 B:消费消息同步数据
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
    ConsumeConcurrentlyContext context) {
    for (MessageExt msg : msgs) {
        Product product = Product.fromJson(new String(msg.getBody()));
        
        // 幂等性:检查版本号
        Product localProduct = productDao.selectById(product.getId());
        if (localProduct.getVersion() >= product.getVersion()) {
            continue;  // 已经是最新的
        }
        
        // 更新本地数据
        productDao.update(product);
        
        // 刷新缓存
        redisTemplate.delete("product:" + product.getId());
    }
    
    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}

80. 场景题:如何实现消息的优先级处理?

答案:

优先级处理方案

方案 1:使用多个 Topic

java 复制代码
// 高优先级 Topic
String highTopic = "Order_High";
String normalTopic = "Order_Normal";

// 发送消息
if (order.isUrgent()) {
    producer.send(new Message(highTopic, orderJson.getBytes()));
} else {
    producer.send(new Message(normalTopic, orderJson.getBytes()));
}

// 消费端优先处理高优先级
consumer1.subscribe(highTopic, "*");  // 专门处理高优先级
consumer2.subscribe(normalTopic, "*");  // 处理普通优先级

方案 2:消费端排序处理

java 复制代码
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
    ConsumeConcurrentlyContext context) {
    // 按优先级排序
    msgs.sort((m1, m2) -> {
        int p1 = Integer.parseInt(m1.getUserProperty("priority", "0"));
        int p2 = Integer.parseInt(m2.getUserProperty("priority", "0"));
        return p2 - p1;  // 降序排序
    });
    
    // 优先处理高优先级消息
    for (MessageExt msg : msgs) {
        processMessage(msg);
    }
    
    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}

81. 场景题:如何实现消息的分组批量处理?

答案:

分组批量处理方案

java 复制代码
// 1. 发送时设置分组 ID
Message message = new Message("BatchTopic", content.getBytes());
message.putUserProperty("groupId", "BATCH_001");
producer.send(message);

// 2. 消费端按分组处理
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
    ConsumeConcurrentlyContext context) {
    // 按分组 ID 分组
    Map<String, List<MessageExt>> groupedMsgs = msgs.stream()
        .collect(Collectors.groupingBy(m -> m.getUserProperty("groupId")));
    
    // 批量处理每个分组
    for (Map.Entry<String, List<MessageExt>> entry : groupedMsgs.entrySet()) {
        String groupId = entry.getKey();
        List<MessageExt> groupMsgs = entry.getValue();
        
        // 批量处理同一组的消息
        batchProcessGroup(groupId, groupMsgs);
    }
    
    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}

private void batchProcessGroup(String groupId, List<MessageExt> msgs) {
    // 开启事务
    transactionTemplate.execute(status -> {
        for (MessageExt msg : msgs) {
            processMessage(msg);
        }
        return null;
    });
}

82. 场景题:如何实现消息的限流和熔断?

答案:

限流和熔断方案

java 复制代码
// 1. 使用 RateLimiter 限流
RateLimiter rateLimiter = RateLimiter.create(1000);  // 1000 TPS

@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
    ConsumeConcurrentlyContext context) {
    // 检查积压情况
    long lag = calculateLag();
    
    if (lag > 100000) {
        // 熔断:跳过部分消息
        context.setDelayLevel(5);  // 1 分钟后重试
        return ConsumeConcurrentlyStatus.RECONSUME_LATER;
    }
    
    // 限流
    for (MessageExt msg : msgs) {
        rateLimiter.acquire();  // 限流
        processMessage(msg);
    }
    
    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}

// 2. 使用 Hystrix 熔断
@HystrixCommand(
    fallbackMethod = "fallback",
    commandProperties = {
        @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "100"),
        @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50")
    }
)
public void processMessage(MessageExt msg) {
    // 处理消息
}

public void fallback(MessageExt msg) {
    // 熔断后的降级处理
    log.warn("熔断降级: {}", msg.getKeys());
}

83. 场景题:如何实现消息的可观测性?

答案:

可观测性方案

1. 消息轨迹

java 复制代码
// 启用消息轨迹
producer.setUseTrace(true);
consumer.setUseTrace(true);

// 查询轨迹
QueryResult result = defaultMQAdminImpl.queryMessage(
    "TopicTest", "ORDER_001", 10, 0, System.currentTimeMillis() - 86400000);

for (QueryResult.QueryResultEntry entry : result.getMessageList()) {
    System.out.println("轨迹: " + entry);
}

2. 链路追踪

java 复制代码
// 发送时传递 Trace ID
String traceId = MDC.get("traceId");
message.putUserProperty("traceId", traceId);
producer.send(message);

// 消费时记录 Trace ID
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
    ConsumeConcurrentlyContext context) {
    String traceId = msgs.get(0).getUserProperty("traceId");
    MDC.put("traceId", traceId);
    
    try {
        processMessage(msgs);
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    } finally {
        MDC.clear();
    }
}

3. 监控指标

java 复制代码
// 自定义监控
Metrics.counter("rocketmq.consume.count").increment();
Metrics.timer("rocketmq.consume.latency").record(() -> {
    processMessage(msgs);
});

84. 场景题:如何实现消息的死信处理和人工干预?

答案:

死信处理方案

java 复制代码
// 1. 消费死信队列
DefaultMQPushConsumer dlqConsumer = new DefaultMQPushConsumer("dlq_consumer");
dlqConsumer.subscribe("%DLQ%consumer_group", "*");

dlqConsumer.registerMessageListener(new MessageListenerConcurrently() {
    @Override
    public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
        ConsumeConcurrentlyContext context) {
        for (MessageExt msg : msgs) {
            // 记录到数据库
            dlqMessageDao.insert(new DlqMessage(msg));
            
            // 发送告警通知
            alertService.sendAlert("死信消息: " + msg.getKeys());
        }
        
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    }
});

// 2. 人工干预处理
public void manualProcess(String dlqId) {
    DlqMessage dlqMsg = dlqMessageDao.selectById(dlqId);
    
    try {
        // 重新处理
        processMessage(dlqMsg.getOriginalMessage());
        
        // 标记已处理
        dlqMessageDao.updateStatus(dlqId, "PROCESSED");
    } catch (Exception e) {
        // 记录失败原因
        dlqMessageDao.updateError(dlqId, e.getMessage());
    }
}

// 3. 管理接口
@RestController
@RequestMapping("/dlq")
public class DlqController {
    @GetMapping("/list")
    public List<DlqMessage> listDlqMessages() {
        return dlqMessageDao.selectAll();
    }
    
    @PostMapping("/process/{id}")
    public String processDlqMessage(@PathVariable String id) {
        manualProcess(id);
        return "处理成功";
    }
}

85. 场景题:如何实现消息的灰度发布?

答案:

灰度发布方案

java 复制代码
// 1. 发送时标记灰度标识
Message message = new Message("TopicTest", content.getBytes());
boolean isGray = isGrayUser(userId);
message.putUserProperty("gray", String.valueOf(isGray));
producer.send(message);

// 2. 消费端根据灰度标识处理
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
    ConsumeConcurrentlyContext context) {
    for (MessageExt msg : msgs) {
        boolean isGray = Boolean.parseBoolean(msg.getUserProperty("gray"));
        
        if (isGray) {
            // 灰度用户,使用新逻辑
            processWithNewLogic(msg);
        } else {
            // 普通用户,使用旧逻辑
            processWithOldLogic(msg);
        }
    }
    
    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}

// 3. 动态调整灰度比例
@Scheduled(fixedRate = 60000)
public void adjustGrayRatio() {
    // 根据监控数据调整灰度比例
    double errorRate = calculateErrorRate();
    
    if (errorRate > 0.01) {
        // 错误率过高,回滚灰度
        setGrayRatio(0);
    } else {
        // 逐步扩大灰度范围
        increaseGrayRatio();
    }
}

八、问题排查与应急处理

86. 常见问题及解决方案

1)消息丢失

环节 原因 解决方案
生产端 异步发送未处理异常 改为同步发送 + 异常重试
存储端 异步刷盘 + 异步复制 改为同步刷盘 + 同步复制
消费端 提前 ACK 处理成功后再 ACK

2)消息重复

原因 解决方案
网络超时重试 业务幂等性设计
ACK 丢失 检查是否已处理
重平衡 消费前查询状态

3)消息乱序

原因 解决方案
并发消费 使用顺序消费模式
重平衡 同一 ID 发送到同一队列

4)消费慢

原因 解决方案
消费者实例少 增加消费者
单条消息处理慢 批量消费、异步处理
数据库查询慢 优化 SQL、增加索引

87. 如何排查消息堆积问题?

答案:

排查步骤:

复制代码
1. 检查堆积量
   mqadmin consumerProgress -n localhost:9876 -g consumer_group

2. 检查消费速度
   mqadmin consumerConnection -n localhost:9876 -g consumer_group

3. 检查消费者日志
   查看是否有异常、超时

4. 检查数据库性能
   慢查询、连接池耗尽

5. 检查网络延迟
   ping/traceroute 检查网络

命令示例:

bash 复制代码
# 查看消费进度
mqadmin consumerProgress -n localhost:9876 -g consumer_group

# 查看消费者连接
mqadmin consumerConnection -n localhost:9876 -g consumer_group

# 查看队列堆积
mqadmin queryMsgByQueue -n localhost:9876 -t TopicTest

88. 如何排查消息丢失问题?

答案:

排查步骤:

复制代码
1. 检查发送日志
   确认消息是否发送成功

2. 检查 Broker 日志
   确认消息是否存储成功

3. 检查消费日志
   确认消息是否被消费

4. 使用消息查询功能
   根据 Key 查询消息是否存在

5. 检查配置
   刷盘策略、复制模式

代码示例:

java 复制代码
// 根据消息 Key 查询
QueryResult result = defaultMQAdminImpl.queryMessage(
    "TopicTest", "ORDER_001", 10, 0, System.currentTimeMillis() - 86400000);

if (result.getMessageList().isEmpty()) {
    log.error("消息丢失: ORDER_001");
} else {
    log.info("消息存在: {}", result.getMessageList());
}

89. 如何排查消费失败问题?

答案:

排查步骤:

复制代码
1. 查看消费日志
   记录异常信息

2. 查看重试队列
   mqadmin queryMsgByQueue -n localhost:9876 -t %RETRY%consumer_group

3. 查看死信队列
   mqadmin queryMsgByQueue -n localhost:9876 -t %DLQ%consumer_group

4. 分析失败原因
   业务异常、数据异常、系统异常

5. 修复问题后
   重新发送死信消息

代码示例:

java 复制代码
// 记录详细消费日志
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
    ConsumeConcurrentlyContext context) {
    for (MessageExt msg : msgs) {
        try {
            log.info("开始消费消息: {}", msg.getKeys());
            processMessage(msg);
            log.info("消费成功: {}", msg.getKeys());
        } catch (BusinessException e) {
            log.error("业务异常: keys={}, error={}", msg.getKeys(), e.getMessage());
            return ConsumeConcurrentlyStatus.RECONSUME_LATER;
        } catch (SystemException e) {
            log.error("系统异常: keys={}, error={}", msg.getKeys(), e.getMessage());
            return ConsumeConcurrentlyStatus.RECONSUME_LATER;
        }
    }
    
    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}

90. 如何监控 RocketMQ 的健康状态?

答案:

监控指标:

指标 说明 告警阈值
消息堆积量 未消费消息数 > 10000
消费延迟 消息从发送到消费的时间 > 5 分钟
发送失败率 发送失败的比例 > 1%
消费失败率 消费失败的比例 > 1%
Broker CPU 使用率 Broker CPU 占用 > 80%
Broker 内存使用率 Broker 内存占用 > 80%
磁盘使用率 磁盘占用 > 80%
网络带宽 网络吞吐 > 阈值

Prometheus 配置:

yaml 复制代码
groups:
- name: rocketmq
  rules:
  - alert: RocketMQMessageLag
    expr: rocketmq_consumer_lag > 10000
    for: 5m
    annotations:
      summary: "消息堆积告警"
      
  - alert: RocketMQBrokerCPUHigh
    expr: rocketmq_broker_cpu_usage > 0.8
    for: 5m
    annotations:
      summary: "Broker CPU 使用率过高"

91. 如何进行 RocketMQ 的性能测试?

答案:

性能测试方案:

1. 准备测试环境

bash 复制代码
# 部署测试集群
# 配置 Producer 和 Consumer

2. 编写测试脚本

java 复制代码
// Producer 压测
public class ProducerStressTest {
    public static void main(String[] args) throws Exception {
        DefaultMQProducer producer = new DefaultMQProducer("test_producer");
        producer.setNamesrvAddr("localhost:9876");
        producer.start();
        
        int threadCount = 10;
        int messagesPerThread = 100000;
        
        CountDownLatch latch = new CountDownLatch(threadCount);
        AtomicLong totalCount = new AtomicLong(0);
        
        long startTime = System.currentTimeMillis();
        
        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                try {
                    for (int j = 0; j < messagesPerThread; j++) {
                        Message message = new Message("TestTopic", "content".getBytes());
                        producer.send(message);
                        totalCount.incrementAndGet();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    latch.countDown();
                }
            }).start();
        }
        
        latch.await();
        long endTime = System.currentTimeMillis();
        
        long duration = endTime - startTime;
        double tps = (double) totalCount.get() / (duration / 1000.0);
        
        System.out.println("Total: " + totalCount.get());
        System.out.println("Duration: " + duration + "ms");
        System.out.println("TPS: " + tps);
    }
}

3. 分析测试结果

复制代码
指标:
- TPS(每秒处理的消息数)
- 延迟(平均、P99、P999)
- 错误率
- 资源使用率(CPU、内存、IO)

92. 如何进行 RocketMQ 的容量规划?

答案:

容量规划步骤:

1. 预估消息量

复制代码
日消息量 = 每日用户数 * 每用户操作数 * 消息系数

例如:
- 日活用户:100 万
- 每用户操作:10 次
- 消息系数:2(每个操作产生 2 条消息)
- 日消息量:1000000 * 10 * 2 = 2000 万条

2. 计算峰值 TPS

复制代码
峰值 TPS = 日消息量 / 86400 * 峰值系数

例如:
- 日消息量:2000 万条
- 峰值系数:10(峰值是平均的 10 倍)
- 峰值 TPS:20000000 / 86400 * 10 ≈ 2315 TPS

3. 计算存储容量

复制代码
日存储量 = 日消息量 * 消息大小 * 保留天数

例如:
- 日消息量:2000 万条
- 消息大小:1KB
- 保留天数:7 天
- 日存储量:20000000 * 1KB * 7 = 140GB

4. 计算 Broker 数量

复制代码
Broker 数量 = 峰值 TPS / 单 Broker 吞吐量

例如:
- 峰值 TPS:2315 TPS
- 单 Broker 吞吐量:10000 TPS
- Broker 数量:2315 / 10000 = 0.23 → 至少 2 个(主从)

93. 如何进行 RocketMQ 的故障演练?

答案:

故障演练方案:

1. Broker 故障演练

bash 复制代码
# 停止 Master
mqadmin shutdownBroker -n localhost:9876 -b broker-a

# 观察:
# - 是否自动切换到 Slave
# - 消息是否继续发送/消费
# - 是否有消息丢失

2. NameServer 故障演练

bash 复制代码
# 停止一个 NameServer
# 观察:
# - 是否影响消息发送/消费
# - 是否自动切换到其他 NameServer

3. Consumer 故障演练

bash 复制代码
# 停止一个 Consumer
# 观察:
# - 是否触发 Rebalance
# - 消息是否被其他 Consumer 接管

4. 网络故障演练

bash 复制代码
# 模拟网络延迟
tc qdisc add dev eth0 root netem delay 100ms

# 观察:
# - 消息发送/消费是否受影响
# - 是否有超时

94. 如何进行 RocketMQ 的备份与恢复?

答案:

备份方案:

1. 手动备份

bash 复制代码
# 备份 CommitLog
cp -r /opt/rocketmq/store/commitlog /backup/commitlog_$(date +%Y%m%d)

# 备份 ConsumeQueue
cp -r /opt/rocketmq/store/consumequeue /backup/consumequeue_$(date +%Y%m%d)

2. 自动备份脚本

bash 复制代码
#!/bin/bash
BACKUP_DIR=/backup/rocketmq
STORE_DIR=/opt/rocketmq/store
DATE=$(date +%Y%m%d)

mkdir -p $BACKUP_DIR/$DATE

# 备份
tar -czf $BACKUP_DIR/$DATE/store.tar.gz $STORE_DIR

# 删除 7 天前的备份
find $BACKUP_DIR -type d -mtime +7 -exec rm -rf {} \;

恢复方案:

bash 复制代码
# 停止 Broker
mqadmin shutdownBroker -n localhost:9876 -b broker-a

# 恢复数据
tar -xzf /backup/rocketmq/20240101/store.tar.gz -C /

# 启动 Broker
nohup sh mqnamesrv &
nohup sh mqbroker -n localhost:9876 &

95. 监控与告警

关键指标

指标 说明 阈值建议
消息堆积量 未消费消息数 > 10000
消费延迟 消息从发送到消费的时间 > 5 分钟
发送失败率 发送失败的比例 > 1%
消费失败率 消费失败的比例 > 1%
Broker CPU 使用率 Broker CPU 占用 > 80%

监控工具

复制代码
1. RocketMQ Console(官方控制台)
   - 监控 Topic、Consumer、Broker 状态
   - 查看消息堆积、消费进度

2. Prometheus + Grafana
   - 自定义监控大盘
   - 配置告警规则

3. 日志分析(ELK)
   - 收集 Broker、Consumer 日志
   - 快速定位问题

总结

本文整理了 RocketMQ 的核心面试题,涵盖以下内容:

  1. 基础概念:核心组件、Topic/Queue、NameServer(12 题)
  2. 核心原理:存储模型、消息流程、过滤机制(12 题)
  3. 可靠性:消息不丢失、顺序性、幂等性(12 题)
  4. 事务消息:实现原理、代码示例、注意事项(10 题)
  5. 性能优化:吞吐量提升、消息堆积处理(12 题)
  6. 延迟消息与高级特性:延迟消息、轨迹、幂等性(12 题)
  7. 场景实战:超时取消、库存扣减、高可用设计(15 题)
  8. 问题排查:常见问题、监控告警(10 题)

扩展阅读


声明:本文整理自公开资料和面试经验,如有错误欢迎指正。

祝大家面试顺利! 🚀

相关推荐
m0_475064501 天前
RocketMQ可视化界面
rocketmq
摇滚侠1 天前
Rocketmq Dashboard jar 包启动,使用启动命令参数,修改 NameServer 的地址
rocketmq·java-rocketmq·jar
J_liaty2 天前
RocketMQ快速入门与Spring Boot整合实践
spring boot·rocketmq·java-rocketmq
Hui Baby2 天前
rocketMq源码简介
rocketmq
hzk的学习笔记4 天前
RocketMQ的消息是推还是拉?
中间件·消息队列·rocketmq
星辰_mya6 天前
零拷贝技术之前提
rocketmq
冰冰菜的扣jio6 天前
理解RocketMQ的消息模型
java·rocketmq·java-rocketmq
河北小博博6 天前
阿里云RocketMQ和MNS(现轻量消息队列)全方位对比
阿里云·云计算·rocketmq
冰冰菜的扣jio7 天前
RocketMQ入门——快速搭建
linux·rocketmq