这是《RocketMQ 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 本章按“基础、存储、消息类型、高可用、性能、项目治理”整理高频问题。回答面试题时,最好先给结论,再讲机制,最后补生产实践和边界条件。
30.1 NameServer 和 Broker 的关系是什么?
NameServer 维护路由信息,Broker 定期注册并上报 Topic 队列数据,客户端从 NameServer 获取路由后直连或通过 Proxy 访问 Broker。
回答要点:
- NameServer 是轻量路由中心;
- Broker 承担消息存储和请求处理;
- NameServer 节点之间通常不强同步,客户端会综合路由视图;
- NameServer 宕机不立即影响已有连接;
- 新路由发现和拓扑更新会受影响。
加分项:说明 5.x Controller 与高可用架构演进。
30.2 RocketMQ 如何保证消息不丢?
分层回答:
producer send + retry + local compensation
broker flush + replication
consumer consume success + idempotent
platform monitoring + reconciliation
关键点:
- 生产端确认 SendStatus;
- 重要消息配合 Outbox 或事务消息;
- Broker 使用满足 SLA 的刷盘和副本策略;
- 消费成功后再确认;
- 所有链路有对账;
- 断电、磁盘损坏、机房故障需要不同方案。
不存在的万能开关,可靠性和成本必须一起讨论。
30.3 RocketMQ 为什么会重复消费?
典型场景:
- 发送超时后内部重试;
- 消费成功但位点提交失败;
- 消费超时触发重试;
- 死信重放;
- 补偿任务和消息并发。
处理方式:使用 eventId 或业务唯一键幂等,业务表用唯一约束或状态机条件更新。Redis 可作为优化,不能作为唯一保障。
30.4 顺序消息如何保证顺序?
三个条件:
same key -> same queue
same queue -> orderly consume
failure -> bounded retry
回答时说明局部顺序和全局顺序,队列选择器、MessageListenerOrderly、重试挂起和扩缩容影响。大多数业务只需要订单号、账号或 SKU 级局部顺序。
30.5 事务消息的流程是什么?
流程:
send half message
-> execute local transaction
-> commit or rollback
-> unknown then checkback
关键点:
- 半消息对消费者不可见;
- 本地事务必须可回查;
- 回查不能依赖内存状态;
- 消费失败不会回滚生产端本地事务;
- 通常与本地事件表配合;
- 它是最终一致,不是分布式强事务。
30.6 延迟消息的原理和限制是什么?
4.x 以固定延迟级别为主,Broker 按级别调度后投递到真实 Topic。5.x 增强定时消息能力,可按时间触发。
限制:
- 到期精度受调度和消费能力影响;
- 版本能力差异大;
- 最大延迟时间可能有限制;
- 相同到期时间会造成洪峰;
- 业务仍需状态机幂等。
30.7 CommitLog、ConsumeQueue、IndexFile 的关系是什么?
CommitLog: physical message body
ConsumeQueue: topic queue logical index
IndexFile: key/time query index
回答要点:
- 所有 Topic 消息顺序追加 CommitLog;
- ConsumeQueue 保存 CommitLog offset、size 和 tag hash;
- IndexFile 支持按 Key 和时间查询;
- 索引由 ReputMessageService 异步构建;
- dispatch lag 可能造成短暂不可见。
30.8 刷盘和复制策略如何选择?
| 策略 | 目的 |
|---|---|
| 同步刷盘 | 降低宿主机断电丢失风险 |
| 异步刷盘 | 提升吞吐 |
| 同步复制 | 降低节点故障丢失风险 |
| 异步复制 | 降低延迟和带宽成本 |
核心是业务 RPO 与延迟预算。交易类偏可靠,日志类可偏成本。必须结合磁盘、网络和故障演练验证。
30.9 Broker 主从切换为什么可能丢消息?
异步复制下,主节点返回成功但部分尾部消息尚未复制到从节点。主节点磁盘损坏并切换到落后副本时,这部分消息可能不可恢复。
防范:
- 核心链路使用同步复制;
- 监控副本差距;
- 自动切换选择位点最新副本;
- 使用 Controller 或共识模式;
- epoch fencing 防脑裂;
- 保留业务级对账。
30.10 消费者数量和队列数量的关系是什么?
集群消费下,队列是最小分配单元。消费者数量超过队列数量时,多余实例空转;消费者数量少于队列数量时,单实例承担多个队列。
扩容步骤:
- 先确认消费慢原因;
- 优化单条处理耗时;
- 增加实例;
- 增加线程;
- 最后考虑扩队列;
- 注意顺序消息影响。
30.11 消息积压如何处理?
先分类:
| 现象 | 原因 |
|---|---|
| 消费组不在线 | 部署或配置问题 |
| 消费耗时高 | 业务和下游瓶颈 |
| 重试风暴 | 下游异常 |
| 热点队列 | key 或路由集中 |
处理顺序:
- 保护下游;
- 降级非核心逻辑;
- 增加有效消费者;
- 拆分热点 Topic 或 key;
- 评估扩队列;
- 恢复后对账。
30.12 如何设计消息幂等?
回答框架:
- 选择业务幂等键,如 eventId、paymentId;
- 事件表或业务表唯一约束;
- 事件处理与业务变更同事务;
- 状态机条件更新;
- Redis 只做快速拦截;
- 针对并发和重放测试;
- 用对账兜底。
不要只说“用 Redis setnx”。
30.13 RocketMQ 和 Kafka 怎么选?
RocketMQ 适合业务消息、事务消息、延迟消息、重试死信、大量 Topic 队列和消息轨迹查询。Kafka 适合流日志、实时计算管道、丰富连接器生态和大规模 append-only 回放。
加分项:
- 不做绝对化比较;
- 提到两者语义差异;
- 强调用自身负载压测;
- 讨论团队能力和运维成本;
- 提混合架构。
30.14 死信队列如何治理?
死信不是删除终点,而是治理队列:
- 保存异常、堆栈和上下文;
- 告警按未处理年龄和业务影响分级;
- 修复代码或数据;
- 小批量重放;
- 幂等保护;
- 记录处理结论;
- 沉淀告警规则。
30.15 消息轨迹有什么用?
轨迹能证明:
- 生产端是否发送成功;
- 消息何时写入 Broker;
- 进入哪个队列和位点;
- 哪个消费组拉取;
- 消费耗时和结果;
- 是否重试或死信。
限制:需要开启、有开销、可能采样,敏感信息要脱敏。
30.16 项目中如何做容量规划?
回答公式:
disk = peak bytes/s × retention × replica × expansion
queues = required consumer parallelism
network = send + replicate + consume + trace
再补充峰值、重试、死信、轨迹、消费 lag、故障恢复窗口、多租户隔离和压测修正。
30.17 高频追问清单
- 半消息回查失败怎么办?
- 顺序消息消费失败能否跳过?
- 广播模式和集群模式区别?
- Tag 过滤和 SQL 过滤区别?
- Proxy 模式解决什么问题?
- Controller 如何防止脑裂?
- 磁盘满了怎么办?
- 位点重置有什么风险?
- 迁移集群时位点怎么处理?
- 如何验证消息没有丢?
回答策略:先讲业务目标,再讲机制,最后讲监控、演练和回滚。
本章小结
RocketMQ 面试的高频考点集中在消息语义、存储结构、事务、顺序、延迟、重试幂等、高可用和容量治理。高质量回答要区分“机制”和“生产实践”,明确版本差异和故障边界,并能给出可验证的监控与对账方案。
思考题
- 如何用一句话说清事务消息的边界?
- 顺序消息为什么容易牺牲吞吐?
- 为什么“不丢”必须分层讨论?
- 死信治理需要哪些字段?
- 面试中如何展示生产经验?