声明:本文整理了 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 工作机制:
-
Broker 注册
- Broker 启动时向所有 NameServer 注册自己的地址信息
- Broker 每隔 30 秒向 NameServer 发送心跳
-
路由管理
- NameServer 维护 Topic 与 MessageQueue 的映射关系
- NameServer 120 秒未收到 Broker 心跳则将其移除
-
路由查询
- 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 如何处理消息的持久化?
答案:
持久化机制:
-
写入 CommitLog
- 所有消息先写入 CommitLog(物理文件)
- 顺序追加写入,保证高性能
-
刷盘策略
- SYNC_FLUSH:每条消息写入磁盘后才返回确认
- ASYNC_FLUSH:消息先写入内存,批量刷盘
-
文件管理
- 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 如何管理文件的生命周期?
答案:
文件管理机制
-
文件创建
- CommitLog 写满后自动创建新文件
- 文件名包含起始偏移量
-
文件过期
- 默认保留 72 小时(可配置)
- 超时文件标记为删除
-
文件删除
- 定时任务扫描过期文件
- 文件未被引用时物理删除
配置示例:
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)顺序消息的实现原理
关键点:
- 生产端:将同一业务 ID(如订单 ID)的消息发送到同一个 Queue
- 存储端:同一 Queue 内的消息顺序写入 CommitLog
- 消费端:同一 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. 事务消息如何保证消息不丢失?
答案:
保证机制:
-
半消息持久化
- 半消息写入 CommitLog 并同步刷盘
- 确保半消息不丢失
-
事务回查机制
- 即使 Producer 宕机,Broker 会主动回查
- 确保最终状态明确
-
发送重试
- 本地事务成功后,发送 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. 如何进行容量规划?
答案:
容量规划步骤:
-
预估消息量
- 日消息量:1000 万条/天
- 峰值 TPS:5000 条/秒
-
计算 Broker 数量
- 单 Broker 吞吐量:10000 TPS
- 需要 Broker 数量:5000 / 10000 = 0.5 → 至少 2 个(主从)
-
计算存储容量
- 消息大小:1KB/条
- 日存储量:1000 万 * 1KB = 10GB
- 保留 7 天:10GB * 7 = 70GB
- 预留冗余:70GB * 2 = 140GB
-
计算网络带宽
- 峰值带宽: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. 延迟消息的实现原理是什么?
答案:
实现原理:
-
发送延迟消息
- Producer 发送消息时设置延迟等级
- Broker 将消息存储到延迟 Topic(
SCHEDULE_TOPIC_XXXX)
-
延迟投递
- Broker 有定时任务扫描延迟 Topic
- 到期的消息重新投递到原始 Topic
-
消息消费
- 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 的核心面试题,涵盖以下内容:
- 基础概念:核心组件、Topic/Queue、NameServer(12 题)
- 核心原理:存储模型、消息流程、过滤机制(12 题)
- 可靠性:消息不丢失、顺序性、幂等性(12 题)
- 事务消息:实现原理、代码示例、注意事项(10 题)
- 性能优化:吞吐量提升、消息堆积处理(12 题)
- 延迟消息与高级特性:延迟消息、轨迹、幂等性(12 题)
- 场景实战:超时取消、库存扣减、高可用设计(15 题)
- 问题排查:常见问题、监控告警(10 题)
扩展阅读
声明:本文整理自公开资料和面试经验,如有错误欢迎指正。
祝大家面试顺利! 🚀