设计背景
RocketMQ 最初由阿里巴巴集团在 2012 年左右自主研发,主要为了解决当时在大规模分布式系统中消息中间件所面临的以下挑战:
- 高并发与高吞吐:阿里巴巴电商业务(如"双11"大促)对消息系统的吞吐量要求极高,传统消息中间件(如 ActiveMQ、RabbitMQ)在极端场景下难以满足性能需求。
- 低延迟:金融、交易等核心业务对消息投递的延迟非常敏感,需要毫秒级甚至亚毫秒级的响应能力。
- 高可用与容错性:在分布式环境下,系统必须具备故障自动恢复、数据不丢失、服务不中断的能力。
- 顺序消息支持:电商订单、支付等场景需要严格保证消息的顺序性,而很多开源消息队列对顺序消息支持较弱。
- 海量堆积能力:在流量洪峰或下游系统处理缓慢时,消息系统需能可靠地堆积大量消息而不崩溃。
- 可扩展性与运维友好性:系统需支持水平扩展,并便于监控、运维和管理。
基于这些实际业务痛点,阿里巴巴团队设计并实现了 RocketMQ,最初用于支撑淘宝、天猫等核心业务的消息流转。
项目定位与目标
RocketMQ 定位为一个分布式、高吞吐、高可用、低延迟、强一致性的消息中间件,适用于对消息可靠性、顺序性、事务性和性能有严苛要求的大规模互联网应用场景。
定位
- 企业级消息总线:作为微服务架构中的核心通信基础设施。
- 金融级可靠性保障:支持事务消息、顺序消息、Exactly-Once 语义等高级特性。
- 云原生友好:支持容器化部署、Kubernetes 集成,适配现代云环境。
核心设计:
- 高性能:单机可支持十万级 TPS(每秒事务数),端到端延迟在毫秒级别。
- 高可用:通过主从复制、Dledger(基于 Raft 协议)实现自动故障转移,保障 99.99%+ 的可用性。
- 强一致性与可靠性:消息持久化到磁盘,支持同步/异步刷盘,确保消息不丢失。
- 丰富的消息模型 :
- 普通消息
- 顺序消息(全局/分区顺序)
- 事务消息(解决分布式事务问题)
- 延迟消息
- 批量消息
- 海量消息堆积:支持 TB 级消息堆积,不影响系统稳定性。
- 良好的可运维性:提供完善的监控指标、管理控制台(如 RocketMQ Dashboard)、命令行工具等。
- 开放生态:2016 年捐赠给 Apache 基金会,成为 Apache 顶级项目(Apache RocketMQ),拥有活跃的社区和多语言客户端支持(Java、C++、Go、Python 等)。
消息类型详解
普通消息
实现原理
普通消息是最基础的消息类型,采用异步发送模式。Producer 将消息发送到 Broker 的指定 Topic,Broker 将消息持久化到 CommitLog 文件中,然后 Consumer 从 Broker 拉取消息进行消费。
关键流程:
- Producer 选择一个 MessageQueue(基于负载均衡策略)
- 将消息序列化并发送到对应的 Broker
- Broker 接收消息,写入 CommitLog
- Consumer 拉取并消费消息
Java 代码示例
java
// Producer 端
DefaultMQProducer producer = new DefaultMQProducer("ProducerGroup");
producer.setNamesrvAddr("localhost:9876");
producer.start();
Message msg = new Message("TopicTest", "TagA", "Hello RocketMQ".getBytes());
SendResult sendResult = producer.send(msg);
System.out.printf("SendResult=%s%n", sendResult);
producer.shutdown();
// Consumer 端
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("ConsumerGroup");
consumer.setNamesrvAddr("localhost:9876");
consumer.subscribe("TopicTest", "*");
consumer.registerMessageListener((List<MessageExt> msgs, ConsumeContext context) -> {
for (MessageExt msg : msgs) {
System.out.println(new String(msg.getBody()));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
});
consumer.start();
顺序消息
实现原理
顺序消息保证同一个业务标识的消息按照发送顺序被消费。RocketMQ 通过以下机制实现:
- 分区顺序:同一 Topic 下,相同 MessageQueue 内的消息保证顺序
- 全局顺序:整个 Topic 只有一个 MessageQueue(不推荐,性能差)
核心思想 : Producer 发送消息时,通过 MessageQueueSelector 选择特定的 MessageQueue,确保相同业务键(如订单ID)的消息发送到同一个队列。
Java 代码示例
java
// Producer - 分区顺序消息
DefaultMQProducer producer = new DefaultMQProducer("OrderProducerGroup");
producer.setNamesrvAddr("localhost:9876");
producer.start();
String orderId = "ORDER_001";
Message msg = new Message("OrderTopic", "TagA",
("Order:" + orderId + ", Step:" + step).getBytes());
// 选择 MessageQueue,确保相同 orderId 发送到同一队列
SendResult sendResult = producer.send(msg, (mqs, msg1, arg) -> {
Long orderIdKey = (Long) arg;
int index = (int) (orderIdKey % mqs.size());
return mqs.get(index);
}, orderId.hashCode());
// Consumer - 顺序消费
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("OrderConsumerGroup");
consumer.setNamesrvAddr("localhost:9876");
consumer.subscribe("OrderTopic", "*");
// 使用 MessageListenerOrderly 保证顺序消费
consumer.registerMessageListener((MessageListenerOrderly) (msgs, context) -> {
for (MessageExt msg : msgs) {
// 处理消息逻辑
System.out.println("Consume: " + new String(msg.getBody()));
}
return ConsumeOrderlyStatus.SUCCESS;
});
consumer.start();
顺序消息处理流程图
Consumer Broker Producer Consumer Broker Producer 通过MessageQueueSelector 选择特定MessageQueue loop [消费循环] 发送消息(带业务键) 持久化到指定MessageQueue 拉取指定MessageQueue消息 返回有序消息列表 串行处理消息(单线程) 提交消费位点
事务消息
实现原理
事务消息解决分布式事务问题,采用两阶段提交协议:
第一阶段(Half Message):
- Producer 发送 Half 消息到 Broker
- Broker 存储 Half 消息到特殊的 RMQ_SYS_TRANS_HALF_TOPIC
- 返回发送结果给 Producer
第二阶段(Commit/Rollback):
- Producer 执行本地事务
- 根据本地事务结果,向 Broker 发送 Commit 或 Rollback 指令
- Broker 将 Half 消息转移到真实 Topic 或删除
异常处理(事务状态回查):
- 如果 Producer 宕机未发送最终状态,Broker 会定期回查 Producer 的本地事务状态
Java 代码示例
java
// Producer - 事务消息
TransactionMQProducer producer = new TransactionMQProducer("TransactionProducerGroup");
producer.setNamesrvAddr("localhost:9876");
// 设置事务监听器
producer.setTransactionListener(new TransactionListener() {
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
// 执行本地事务
try {
// 1. 扣减库存
// 2. 保存订单
// 3. 其他业务操作
return LocalTransactionState.COMMIT_MESSAGE;
} catch (Exception e) {
return LocalTransactionState.ROLLBACK_MESSAGE;
}
}
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
// 事务状态回查
String bizId = msg.getUserProperty("bizId");
// 查询本地事务状态
if (isTransactionCommitted(bizId)) {
return LocalTransactionState.COMMIT_MESSAGE;
} else if (isTransactionRolledBack(bizId)) {
return LocalTransactionState.ROLLBACK_MESSAGE;
} else {
return LocalTransactionState.UNKNOW;
}
}
});
producer.start();
// 发送事务消息
Message msg = new Message("TransactionTopic", "TagA", "Transaction Data".getBytes());
SendResult sendResult = producer.sendMessageInTransaction(msg, null);
事务消息流程图
LocalDB Broker Producer LocalDB Broker Producer alt [本地事务成功] [本地事务失败] 事务状态回查机制 loop [定期检查未决事务] 发送Half消息 存储到RMQ_SYS_TRANS_HALF_TOPIC 返回Half消息发送成功 执行本地事务 发送COMMIT指令 将消息转移到真实Topic 发送ROLLBACK指令 删除Half消息 回查本地事务状态 查询事务状态 返回事务状态 返回COMMIT/ROLLBACK/UNKNOW
延迟消息
实现原理
RocketMQ 通过延迟级别实现延迟消息,内置 18 个延迟级别(1s, 5s, 10s, ..., 2h)。
存储机制:
- 延迟消息首先存储在特殊的 SCHEDULE_TOPIC_XXXX Topic 中
- 每个延迟级别对应一个 MessageQueue
- Broker 后台线程定时扫描,将到期的消息重新投递到真实 Topic
注意: RocketMQ 的延迟消息是粗粒度的,只能使用预定义的延迟级别。
Java 代码示例
java
// Producer - 延迟消息
DefaultMQProducer producer = new DefaultMQProducer("DelayProducerGroup");
producer.setNamesrvAddr("localhost:9876");
producer.start();
Message msg = new Message("DelayTopic", "TagA", "Delayed Message".getBytes());
// 设置延迟级别 (1-18)
// level 1: 1s, level 2: 5s, level 3: 10s, ..., level 18: 2h
msg.setDelayTimeLevel(3); // 10秒延迟
SendResult sendResult = producer.send(msg);
producer.shutdown();
// Consumer - 正常消费(无需特殊处理)
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("DelayConsumerGroup");
consumer.setNamesrvAddr("localhost:9876");
consumer.subscribe("DelayTopic", "*");
consumer.registerMessageListener((List<MessageExt> msgs, ConsumeContext context) -> {
for (MessageExt msg : msgs) {
System.out.println("Received delayed message at: " + new Date());
System.out.println("Original store time: " + new Date(msg.getStoreTimestamp()));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
});
consumer.start();
延迟消息处理流程图
是
否
Producer发送延迟消息
设置DelayTimeLevel
Broker接收消息
存储到SCHEDULE_TOPIC_XXXX
对应延迟级别的MessageQueue
DelayScanTimer线程
消息是否到期?
重新投递到真实Topic
继续等待
Consumer正常消费
批量消息
实现原理
批量消息允许 Producer 一次发送多条消息,减少网络开销,提高吞吐量。
关键点:
- 所有消息必须属于同一个 Topic
- 所有消息必须有相同的 waitStoreMsgOK 属性
- 单次批量消息总大小不能超过 1MB(可配置)
分批策略: 如果批量消息过大,Producer 会自动进行分片。
Java 代码示例
java
// Producer - 批量消息
DefaultMQProducer producer = new DefaultMQProducer("BatchProducerGroup");
producer.setNamesrvAddr("localhost:9876");
producer.start();
// 准备批量消息
List<Message> messages = new ArrayList<>();
for (int i = 0; i < 100; i++) {
messages.add(new Message("BatchTopic", "TagA",
("Batch Message " + i).getBytes()));
}
try {
// 直接发送批量消息
SendResult sendResult = producer.send(messages);
System.out.println("Batch send success");
} catch (Exception e) {
// 如果批量消息过大,需要手动分片
ListSplitter splitter = new ListSplitter(messages);
while (splitter.hasNext()) {
List<Message> partition = splitter.next();
producer.send(partition);
}
}
// 手动分片工具类
class ListSplitter implements Iterator<List<Message>> {
private final int SIZE_LIMIT = 1024 * 1024; // 1MB
private final List<Message> messages;
private int currentIndex = 0;
public ListSplitter(List<Message> messages) {
this.messages = messages;
}
@Override
public boolean hasNext() {
return currentIndex < messages.size();
}
@Override
public List<Message> next() {
int nextIndex = currentIndex;
int totalSize = 0;
for (; nextIndex < messages.size(); nextIndex++) {
Message message = messages.get(nextIndex);
int tmpSize = message.getTopic().length() +
message.getBody().length +
// 其他属性大小估算
200;
if (tmpSize > SIZE_LIMIT) {
if (nextIndex == currentIndex) {
nextIndex++;
}
break;
}
if (totalSize + tmpSize > SIZE_LIMIT) {
break;
} else {
totalSize += tmpSize;
}
}
List<Message> result = messages.subList(currentIndex, nextIndex);
currentIndex = nextIndex;
return result;
}
}
批量消息处理流程图
是
否
准备多条消息
消息总大小 <= 1MB?
直接批量发送
按大小分片
逐个发送分片
Broker处理批量消息
写入CommitLog
Consumer拉取消息
总结对比
| 消息类型 | 适用场景 | 性能影响 | 复杂度 |
|---|---|---|---|
| 普通消息 | 一般业务场景 | 无 | 低 |
| 顺序消息 | 订单状态变更等 | 中(串行消费) | 中 |
| 事务消息 | 分布式事务 | 高(两阶段) | 高 |
| 延迟消息 | 定时任务、超时处理 | 低 | 低 |
| 批量消息 | 高吞吐场景 | 低(提升吞吐) | 低 |
解决什么问题?
- 高吞吐与低延迟
- 问题:传统消息队列(如 ActiveMQ、RabbitMQ)在面对每秒数十万甚至上百万消息的写入和消费时,容易成为系统瓶颈,延迟显著增加。
- RocketMQ 的解决方案 :
- 采用顺序写磁盘 + PageCache 零拷贝技术,极大提升 I/O 性能;
- 消息存储基于 CommitLog 文件顺序追加,避免随机写;
- 支持批量发送/消费,减少网络开销;
- 单机可支持 10万+ TPS,端到端延迟控制在毫秒级。
- 消息可靠性(不丢失)
- 问题:在网络抖动、机器宕机等异常情况下,如何保证消息不丢失?
- RocketMQ 的解决方案 :
- 消息持久化到磁盘(支持同步刷盘或异步刷盘);
- 主从复制(Master-Slave) 或基于 Dledger(Raft 协议) 的多副本机制,实现数据冗余;
- 提供 At-Least-Once 语义,并通过事务消息、幂等消费等机制向 Exactly-Once 靠近;
- 生产者支持失败重试 ,消费者支持ACK 机制,确保消息被成功处理。
- 严格的消息顺序性
- 问题:在订单创建、支付、物流等业务中,消息必须按发送顺序被消费,否则会导致状态不一致。
- RocketMQ 的解决方案 :
- 支持分区顺序消息(Queue 级别顺序):同一业务键(如订单 ID)的消息路由到同一个 MessageQueue,由单个消费者线程顺序消费;
- 虽然不支持全局严格顺序(因性能代价过高),但在绝大多数业务场景中已足够。
- 分布式事务一致性(最终一致性)
- 问题:在微服务架构中,跨服务的操作(如"扣库存 + 创建订单")需要保证事务一致性,但传统两阶段提交(2PC)性能差、可用性低。
- RocketMQ 的解决方案 :
- 提供事务消息(Transactional Message) 机制,基于"半消息 + 本地事务状态回查"实现最终一致性;
- 生产者先发送"半消息"(不可见),执行本地事务后,根据结果提交或回滚消息;
- Broker 定期回查未决事务状态,确保事务最终完成。
- 海量消息堆积能力
- 问题:当下游系统处理缓慢或故障时,上游持续生产消息,中间件需能长时间、稳定地堆积大量消息。
- RocketMQ 的解决方案 :
- 基于文件系统的存储模型,支持 TB 级消息堆积;
- 堆积不影响生产/消费性能(因读写分离,消费指针独立);
- 支持消息过期删除策略,避免磁盘无限增长。
- 高可用与容灾
- 问题:单点故障会导致整个消息系统不可用,影响业务连续性。
- RocketMQ 的解决方案 :
- 无单点架构:NameServer 集群无状态,Broker 可主从部署;
- 自动故障转移:结合 Dledger 实现自动选主,保障服务连续性;
- 支持多机房部署、同城双活等容灾方案。
- 灵活的消息模型与扩展性
- 问题:不同业务对消息类型(延迟、批量、过滤等)有差异化需求。
- RocketMQ 的解决方案 :
- 支持多种消息类型:普通消息、顺序消息、事务消息、延迟消息(18 个等级)、批量消息;
- 支持消息标签(Tag)和 SQL 表达式过滤,实现精准投递;
- 提供丰富的客户端 SDK(Java、Go、C++、Python 等)和插件机制。
RocketMQ 解决的核心问题是:
在超大规模、高并发、强一致性的分布式系统中,如何实现高性能、高可靠、有序、可扩展的消息传递。
目标用户是谁?
1. 大型互联网企业(尤其是电商、金融、物流等)
- 典型场景:双11大促、秒杀、订单处理、支付通知、库存同步等。
- 需求特点 :
- 高吞吐(每秒数十万消息)
- 低延迟(毫秒级响应)
- 消息不丢失、强一致性
- 支持顺序消息(如订单状态变更)
- 分布式事务支持(如"下单+扣库存")
- 代表用户:阿里巴巴、京东、拼多多、美团、滴滴等。
RocketMQ 最初就是为支撑阿里"双11"海量交易而设计,因此天然适配这类高并发、高可靠场景。
2. 金融机构与支付平台
- 典型场景:交易流水、风控告警、对账系统、资金清算、审计日志等。
- 需求特点 :
- 金融级可靠性:消息绝对不能丢;
- 审计可追溯:消息需持久化并长期保留;
- 事务消息:保障跨系统操作的一致性;
- 合规性要求高,系统需具备完善的监控与灾备能力。
- 代表用户:银行、证券公司、第三方支付平台(如蚂蚁金服早期大量使用 RocketMQ)。
3. 云计算与 SaaS 服务商
- 典型场景:多租户事件通知、异步任务调度、微服务解耦、日志收集等。
- 需求特点 :
- 多租户隔离与资源管控;
- 弹性伸缩与云原生支持(Kubernetes、Service Mesh);
- 提供托管消息服务(如阿里云 RocketMQ 版本);
- 开放 API 与多语言 SDK 支持。
- 代表用户:阿里云、腾讯云、华为云等云厂商(提供 RocketMQ 托管服务);SaaS 企业用于构建事件驱动架构。
4. 物联网(IoT)与车联网企业
- 典型场景:设备状态上报、远程控制指令下发、数据聚合分析等。
- 需求特点 :
- 海量设备连接(百万级 Topic/Queue);
- 消息堆积能力强(设备离线时缓存指令);
- 支持轻量级客户端(如 C++、Go SDK);
- 延迟消息用于定时任务(如定时唤醒设备)。
- 代表用户:智能硬件厂商、新能源车企、工业互联网平台。
5. 技术团队与架构师(中高级开发者)
- 典型角色:后端工程师、系统架构师、DevOps 工程师。
- 使用动机 :
- 构建事件驱动架构(EDA) 或 CQRS 模式;
- 实现微服务之间的最终一致性;
- 替代 Kafka(当需要更强的事务/顺序/可靠性保障时);
- 自主可控、开源可定制(Apache 2.0 协议,无商业限制)。
注意:RocketMQ 学习曲线略高于 RabbitMQ,更适合有一定分布式系统经验的团队。
6. 政府与传统企业(数字化转型中)
- 典型场景:政务数据交换、ERP 系统集成、跨部门协同等。
- 需求特点 :
- 系统稳定、国产化适配(信创生态);
- 支持私有化部署;
- 与国产操作系统、数据库兼容;
- 中文文档与本地化支持完善。
不适合的用户(边界说明)
RocketMQ 并非万能,以下场景可能不是其最佳选择:
- 简单应用或小团队:若只需基本队列功能,RabbitMQ 或 Redis Streams 更轻量;
- 纯日志/监控数据采集:Kafka 在高吞吐日志场景下生态更成熟;
- 极低延迟要求(微秒级):如高频交易,可能需专用消息总线(如 ZeroMQ、LMAX Disruptor)。
核心设计理念是什么?
- 顺序写 + 零拷贝:极致 I/O 性能
- 理念:磁盘顺序写比随机写快几个数量级;减少数据在内核态与用户态之间的拷贝可显著提升吞吐。
- 实现 :
- 所有消息追加写入 CommitLog 文件(顺序写);
- 消费时通过 mmap + PageCache 或 sendfile(零拷贝) 直接从磁盘缓存传输到网络,避免多次内存拷贝;
- 单机可支撑 10万+ TPS,延迟稳定在毫秒级。
核心思想 :"用最简单、最高效的方式处理 I/O",而非依赖昂贵硬件。
- 存储与计算分离(读写分离)
- 理念:将消息存储(CommitLog)与逻辑队列(ConsumeQueue)解耦,提升系统灵活性和性能。
- 实现 :
- CommitLog:所有 Topic 的消息物理上统一顺序写入;
- ConsumeQueue:每个 Topic/Queue 对应一个逻辑索引文件(记录消息在 CommitLog 中的偏移量、大小、Tag Hash);
- 消费者通过 ConsumeQueue 快速定位消息,无需扫描整个 CommitLog。
- 优势 :
- 支持海量 Topic(数万个)而不显著影响性能;
- 存储结构简单,易于维护和扩展。
核心思想 :"物理存储统一,逻辑视图隔离"。
- 无单点、去中心化架构
- 理念:避免任何中心节点成为性能瓶颈或故障点。
- 实现 :
- NameServer:轻量级注册中心,无状态、集群部署,仅提供路由发现;
- Broker:消息存储节点,支持主从复制或 Dledger(Raft)多副本;
- 客户端(Producer/Consumer)直接与 Broker 通信,不经过 NameServer 转发数据。
- 优势 :
- 架构清晰,扩展性强;
- NameServer 故障不影响已建立的生产/消费连接。
核心思想 :"控制面与数据面分离,控制面轻量,数据面高效"。
- 消息可靠性优先(At-Least-Once + 可靠持久化)
- 理念 :在分布式系统中,消息不丢失比重复更可接受(可通过幂等解决重复)。
- 实现 :
- 消息默认持久化到磁盘;
- 支持同步刷盘 (强可靠)和异步刷盘(高性能);
- 主从复制或 Dledger 多副本保障数据冗余;
- 消费者需显式 ACK,未 ACK 消息可重试。
- 目标 :满足金融、交易等场景对消息零丢失的要求。
核心思想 :"宁可重复,不可丢失"。
- 功能丰富但保持简洁内核
- 理念:在保证核心引擎轻量高效的前提下,通过模块化设计支持高级特性。
- 支持的高级能力 :
- 顺序消息(Queue 级别)
- 事务消息(基于两阶段提交 + 回查)
- 延迟消息(18 个固定等级)
- 消息过滤(Tag / SQL92 表达式)
- 批量发送/消费
- 关键原则 :
- 高级功能不侵入核心存储引擎;
- 默认关闭非必要特性,按需启用,避免性能损耗。
核心思想 :"核心极简,扩展灵活"。
- 面向运维与可观测性设计
- 理念:生产环境必须可监控、可诊断、可治理。
- 实现 :
- 提供丰富的 Metrics(JMX、Prometheus);
- 内置 Admin CLI 工具 (如
mqadmin); - 支持 Dashboard(如 RocketMQ Console);
- 日志结构清晰,支持 trace ID 追踪。
- 目标:降低大规模集群的运维复杂度。
核心思想 :"可观测性是高可用的前提"。
整体架构概览
- 主要组件有哪些?它们如何协作?
- NameServer(注册中心 / 路由管理)
- 角色 :轻量级的服务发现与路由注册中心。
- 功能 :
- 管理所有 Broker 的元数据(如 Broker 地址、Topic 分布、读写队列数量等);
- 为 Producer 和 Consumer 提供路由信息查询服务;
- 不参与消息的存储和转发,仅提供控制面服务。
- 特点 :
- 无状态:NameServer 节点彼此独立,不共享数据;
- 集群部署:通常部署多个实例以提高可用性;
- 最终一致性:Broker 定期向所有 NameServer 发送心跳,客户端从任一 NameServer 获取路由(容忍短暂不一致)。
类比:类似 Dubbo 中的 ZooKeeper 或 Kubernetes 中的 API Server(但更轻量)。
- Broker(消息中转与存储节点)
- 角色 :RocketMQ 的核心数据节点,负责消息的接收、存储、投递。
- 功能 :
- 接收 Producer 发送的消息并持久化到磁盘;
- 根据 Consumer 的拉取请求分发消息;
- 管理 Topic、MessageQueue、消费位点(offset)等元数据;
- 支持主从复制(Master-Slave)或基于 Dledger(Raft 协议) 的自动容灾。
- 内部关键模块 :
- CommitLog:所有消息物理顺序写入的文件;
- ConsumeQueue:逻辑消费队列,作为 CommitLog 的索引;
- IndexFile:支持按 Key 快速查找消息(用于消息轨迹、运维);
- HAService:主从同步服务;
- TransactionService:事务消息回查服务。
- 部署模式 :
- 单 Master
- 多 Master(无 Slave)
- 多 Master-Slave(异步/同步复制)
- Dledger 集群(自动选主,强一致)
Broker 是 RocketMQ 性能与可靠性的核心载体。
- Producer(生产者)
- 角色 :消息的发送方,将业务数据封装为消息并发送到 Broker。
- 功能 :
- 与 NameServer 通信获取 Topic 路由信息;
- 根据负载均衡策略(如轮询、机房就近)选择 MessageQueue 发送消息;
- 支持多种发送方式:
- 同步发送(
send()) - 异步发送(
send(callback)) - 单向发送(
sendOneway(),只发不等响应)
- 同步发送(
- 支持事务消息 、顺序消息 、批量发送等高级特性;
- 自动重试机制(默认 2 次)。
- 最佳实践 :
- 一个应用通常复用一个 Producer 实例;
- 需指定
ProducerGroup(用于事务回查标识)。
- Consumer(消费者)
-
角色 :消息的接收方,从 Broker 拉取消息并处理业务逻辑。
-
功能:
- 从 NameServer 获取 Topic 路由;
- 主动拉取(Pull) 消息(RocketMQ 采用 Pull 模型,非 Push);
- 支持两种消费模式:
- 集群消费(Clustering):同一 ConsumerGroup 内多实例负载均衡消费;
- 广播消费(Broadcasting):每条消息被 Group 内所有实例消费;
- 支持顺序消费(单队列单线程);
- 消费成功后需手动或自动提交 offset;
- 消费失败可重试(默认 16 次,进入重试 Topic)。
-
类型:
DefaultMQPushConsumer:封装 Pull 模型,提供类似 Push 的编程体验;DefaultMQPullConsumer:完全手动控制拉取(已废弃,推荐使用LitePullConsumer)。
-
画一张简单的架构图(可用文字描述)
Broker(Master) NameServer Consumer Producer Broker(Master) NameServer Consumer Producer 消息处理全流程(含路由发现、存储、拉取、ACK) 若消费失败:- 集群模式:消息延迟重试(进入 %RETRY%Group Topic)- 顺序消费:暂停当前 Queue 直至成功 1. 查询 "TopicA" 路由信息 2. 返回 Broker 列表及 MessageQueue 分布 3. 发送消息到指定 "MessageQueue" (含 Topic, Tag, Body) 4. 写入 "CommitLog" (顺序追加,磁盘文件) 5. 异步构建 "ConsumeQueue" 索引 (offset, size, tagCode) 6. (可选)构建 "IndexFile" 用于按 Key 查找 7. 返回 "SendResult" (含 msgId, queueOffset) 8. 查询 TopicA 路由信息 9. 返回 Broker 列表及 Queue 分布 10. 拉取请求 (PullMessageRequest, 指定 Queue + offset) 11. 返回一批消息 ("PullResult", 含消息列表 + nextBeginOffset) 12a. 业务逻辑处理消息 12b. 提交消费位点 ("UpdateOffsetRequest") 12c. 更新 "ConsumerOffset" 文件(或存储到 Broker 内存)
关键细节说明(对应图中步骤):
-
路由发现(1~2, 8~9)
- Producer/Consumer 启动时向任意 NameServer 查询 Topic 路由;
- NameServer 返回该 Topic 所在的 Broker 地址及每个 Broker 上的
MessageQueue列表; - 客户端本地缓存路由,后续直接连 Broker,不再经过 NameServer。
-
消息存储(3~6)
- 所有消息物理写入统一 CommitLog 文件(顺序 I/O);
- ConsumeQueue 是逻辑队列,每条记录 20 字节(offset + size + tag hash),用于快速定位 CommitLog;
- IndexFile 支持通过
msgKey快速检索(用于运维排查); - 这些写入操作是异步并行的,不影响主写入路径。
-
消费模型(10~12)
- Consumer 主动 Pull,非 Broker Push;
- 每次拉取指定
MessageQueue和起始offset; - 消费成功后必须显式提交 offset,否则重启后会重复消费;
- 顺序消费时,RocketMQ 会对 Queue 加分布式锁,确保单线程处理。
-
可靠性保障
- 消息写入 CommitLog 后即认为"已接收";
- 可配置 同步/异步刷盘 、同步/异步主从复制;
- Dledger 模式下,基于 Raft 协议保证多副本强一致。
补充:
- 事务消息会在步骤 3 前插入"发送半消息",并在步骤 7 后由 Broker 回查本地事务状态;
- 顺序消息 在步骤 3 中需指定
MessageQueueSelector,确保相同业务键进同一 Queue; - 延迟消息 在 Broker 内部按 level 转发到特殊 Topic(如
SCHEDULE_TOPIC_XXXX),到期后再投递到真实 Topic。
数据面(有状态)
控制面(无状态)
客户端
Broker 内部存储模块
- 查询 TopicA 路由 2. 返回 Broker 列表及 MessageQueue 分布 3. 查询 TopicA 路由 4. 返回 Broker 列表及 MessageQueue 分布 5. 发送消息到指定 MessageQueue 6. 顺序写入 CommitLog 7. 异步构建索引 8. (可选) 构建 IndexFile 9. 返回 SendResult (msgId, offset) 10. 拉取请求 (Queue + offset) 11. 提供消息位置信息 12. 返回消息批次 (含 nextBeginOffset) 13. 业务处理消息 14. 提交消费位点 15. 更新 ConsumerOffset 16a. 消费失败?
16b. 集群模式:
投递到 %RETRY%GroupName Topic
16c. 顺序模式:
暂停当前 Queue 消费
Producer
Consumer
NameServer
Broker(Master)
CommitLog
(物理存储)
ConsumeQueue
(逻辑队列索引)
IndexFile
(可选, Key索引)
ConsumerOffset
(消费位点)
重试逻辑
流程详解(对应编号):
| 编号 | 步骤 | 说明 |
|---|---|---|
| 1--2 | Producer 获取路由 | 启动时向 NameServer 查询 Topic 所在 Broker 及 Queue 列表 |
| 3--4 | Consumer 获取路由 | 同上,Consumer 也需要知道从哪个 Broker 拉取 |
| 5 | Producer 发送消息 | 指定 Topic、Tag、Body,并选择一个 MessageQueue(如按订单 ID 路由) |
| 6 | 写入 CommitLog | 所有消息统一顺序追加到 CommitLog 文件(高性能关键) |
| 7 | 构建 ConsumeQueue | 异步线程为每个 Queue 生成索引(offset + size + tagCode),供消费快速定位 |
| 8 | 构建 IndexFile | 若消息设置了 keys,会额外写入 IndexFile,支持运维按 Key 查消息 |
| 9 | 返回发送结果 | 包含全局唯一 msgId 和在 Queue 中的 queueOffset |
| 10 | Consumer 拉取 | 主动发起 Pull 请求,携带要拉取的 Queue 和起始 offset |
| 11 | ConsumeQueue 提供位置 | Broker 根据 ConsumeQueue 找到 CommitLog 中的实际位置 |
| 12 | 返回消息批次 | 返回一批消息及下一个拉取位点(nextBeginOffset) |
| 13 | 业务处理 | 应用执行消费逻辑(如更新数据库) |
| 14--15 | 提交位点 | 成功后提交 offset,Broker 持久化到 ConsumerOffset(文件或内存) |
| 16a--c | 失败重试 | • 集群消费 :消息发往重试 Topic,延迟后重新投递; • 顺序消费:暂停当前 Queue,避免乱序 |
关键设计亮点(图中体现):
-
读写分离:
- 写入走
CommitLog(统一顺序写); - 读取走
ConsumeQueue(轻量索引),互不干扰。
- 写入走
-
控制面与数据面解耦:
NameServer仅提供路由,不参与消息传输;- 客户端直连
Broker,降低延迟。
-
异步构建索引:
ConsumeQueue和IndexFile异步生成,不影响主写入性能。
-
消费位点独立管理:
ConsumerOffset与消息存储分离,支持灵活重置。
-
异常处理机制显式化:
- 重试逻辑根据消费模式(集群/顺序)采取不同策略。
关键设计与实现机制
选择 2--3 个最具代表性的设计点深入分析
🔹 设计点 1:CommitLog + ConsumeQueue 分离的存储架构
-
问题背景 :
在高并发场景下,若每个 Topic/Queue 独立写文件,会导致大量随机 I/O 和文件句柄开销;同时,消费者需要高效定位消息,但直接扫描全量消息日志成本极高。
-
解决方案 :
RocketMQ 将物理存储与逻辑视图分离:所有消息统一顺序追加写入一个共享的 CommitLog 文件(物理层),再为每个 MessageQueue 异步构建轻量级索引文件 ConsumeQueue(逻辑层),消费者通过 ConsumeQueue 快速定位 CommitLog 中的消息位置。
-
关键技术:
- 顺序写 CommitLog:利用磁盘顺序写性能优势,避免随机 I/O;
- 异步 Reput 线程:后台线程从 CommitLog 构建 ConsumeQueue 和 IndexFile;
- PageCache + 零拷贝:读取消息时通过 mmap 或 sendfile 直接从内核缓存传输,减少内存拷贝;
- 定长索引:ConsumeQueue 每条记录仅 20 字节(offset + size + tagCode),内存和磁盘占用极小。
-
优点:
- 极致写入性能:单机可支撑 10万+ TPS;
- 支持海量 Topic:新增 Topic 几乎无 I/O 开销(仅需创建索引文件);
- 读写解耦:消费不影响生产,堆积不影响写入;
- 简化存储管理:只需管理少量大文件(CommitLog 轮转),而非成千上万小文件。
-
缺点:
- 读放大:消费需先查 ConsumeQueue 再读 CommitLog,多一次间接寻址;
- 冷数据访问慢:若消息未命中 PageCache,需两次磁盘 I/O(索引 + 数据);
- 实现复杂度高:需维护 CommitLog 与 ConsumeQueue 的一致性(依赖 Reput 线程可靠性)。
🔹 设计点 2:事务消息(Half Message + 状态回查)机制
-
问题背景 :
在分布式系统中,"本地数据库操作 + 发送消息"需保证原子性。若先发消息再提交 DB,DB 宕机导致状态不一致;若先提交 DB 再发消息,消息服务宕机导致消息丢失。
-
解决方案 :
RocketMQ 引入两阶段提交变种:
- Producer 先发送"半消息"(对 Consumer 不可见);
- 执行本地事务;
- 根据事务结果提交或回滚消息;
- 若 Broker 未收到最终状态(如 Producer 宕机),则主动回调 Producer 回查本地事务状态。
-
关键技术:
- Half Message 隔离 :半消息写入特殊 Topic(
RMQ_SYS_TRANS_HALF_TOPIC),Consumer 无法拉取; - Op Queue 机制:记录半消息的提交/回滚操作,用于判断是否需回查;
- Broker 主动回查 :定时扫描未决半消息,向 Producer 发起
checkLocalTransaction请求; - 幂等回查设计:Producer 需根据消息 Key 或 TransactionID 查询本地 DB 状态。
- Half Message 隔离 :半消息写入特殊 Topic(
-
优点:
- 业务无侵入协调服务:无需 ZooKeeper、Seata 等外部组件;
- 最终一致性保障:即使 Producer 宕机,Broker 也能驱动事务完成;
- 与消息模型深度集成:天然支持顺序事务、延迟事务等组合场景;
- 金融级可靠性:广泛应用于支付、订单等核心链路。
-
缺点:
- 增加业务复杂度 :开发者需实现
TransactionListener回查接口; - 回查延迟:默认每 60 秒扫描一次,极端情况下事务收敛较慢;
- 不支持回滚已提交事务:一旦本地事务提交,只能确保消息发出,无法撤销业务操作。
- 增加业务复杂度 :开发者需实现
我的收获与启发
把"学到的东西"转化为"我能用的东西"
| 启发 | 我可以怎么应用到实际工作中? |
|---|---|
| 消息顺序性需业务协同设计 | 在开发订单、支付等状态机类业务时,主动使用 MessageQueueSelector 按业务ID(如订单号)路由,确保同一实体的消息进入同一队列;同时采用 MessageListenerOrderly 消费,避免因并发处理导致状态错乱。 |
| 事务消息可替代本地表+轮询方案 | 遇到"DB更新 + 发通知"类需求(如用户注册后发欢迎消息),不再用"本地消息表 + 定时任务"这种复杂模式,而是直接使用 RocketMQ 事务消息,简化架构并提升可靠性。 |
| 存储模型影响系统扩展性 | 在设计高吞吐系统时,借鉴 RocketMQ "统一写入 + 异步索引"思想:例如日志收集可先写入大文件,再异步构建查询索引,避免高频小文件写入成为瓶颈。 |
延伸思考(可选)
-
如果让你改进它,你会做什么?
- 增强 Exactly-Once 支持:在 Broker 层集成幂等写入或消费去重窗口(类似 Kafka 的 idempotent producer + transactional consumer),减少业务层幂等负担。
- 动态延迟等级:当前延迟消息仅支持 18 个固定等级(1s~2h),可支持自定义 TTL(如 Redis Stream 的 XADD + XREAD with BLOCK),提升灵活性。
-
它不适合什么场景?
- 超低延迟要求(微秒级):如高频交易系统,RocketMQ 毫秒级延迟仍过高,应选用内存队列(如 Disruptor)或 RDMA 网络方案。
- 纯日志/指标流式分析:若只需高吞吐管道而无需事务、顺序、ACK 等语义,Kafka 或 Pulsar 的批处理和压缩效率更高,生态工具(如 Flink/Kafka Streams)也更成熟。
参考资料
- 官方文档链接
- GitHub 仓库
- 推荐阅读文章或视频
常见问题和解决办法
消息堆积(Consumer Lag)
现象:
CONSUME_LAG 持续增长,监控显示消费位点远落后于最新消息。
常见原因:
- 消费者处理逻辑慢(如 DB 慢查询、外部 RPC 超时);
- 消费线程数不足或被阻塞;
- 消费者实例宕机或网络分区;
- 顺序消费中某条消息失败导致队列暂停。
解决方案:
- 扩容消费者:增加 Consumer 实例或提升单机线程数(
consumeThreadMin/Max); - 优化业务逻辑:异步化非核心操作、加缓存、批量处理;
- 跳过异常消息 (谨慎):通过
mqadmin重置 offset 或使用SuspendCurrentQueueTimeMillis跳过卡住的顺序消息; - 监控告警:设置
CONSUME_LAG > 阈值告警,结合消息轨迹快速定位卡点。
消息丢失
现象:
Producer 返回成功,但 Consumer 未收到消息。
常见原因:
- Broker 异步刷盘 + 主从异步复制,主节点宕机导致未同步数据丢失;
- Consumer 未正确 ACK(如自动提交 offset 但业务处理失败);
- 磁盘故障且无有效副本。
解决方案:
-
关键业务配置强可靠:
properties# Producer sendMsgTimeout=3000 retryTimesWhenSendFailed=3 # Broker flushDiskType=SYNC_FLUSH # 同步刷盘 brokerRole=SYNC_MASTER # 同步主从(或使用 Dledger) -
Consumer 手动 ACK:关闭自动提交,确保业务成功后再提交 offset;
-
启用 Dledger 集群:基于 Raft 协议实现多副本强一致,避免主从切换丢数据;
-
开启消息轨迹:通过
traceTopicEnable=true追踪消息全链路状态。
Broker 磁盘写满
现象:
Broker 日志报 disk full,Producer 发送失败(SYSTEM_ERROR)。
常见原因:
- 消息堆积 + 未设置过期策略;
- CommitLog 文件未清理(默认保留 小时);
- 监控缺失,未及时扩容磁盘。
解决方案:
- 紧急处理:
- 扩容磁盘;
- 临时调低
fileReservedTime(如从 72h → 24h); - 清理无用 Topic(
mqadmin deleteTopic)。
- 长期预防:
- 设置合理的
deleteWhen(如凌晨低峰期清理); - 监控磁盘使用率(>80% 告警);
- 对非关键 Topic 设置较短保留时间。
- 设置合理的
NameServer 路由不一致
现象:
Producer/Consumer 报 TOPIC_NOT_EXIST,但 Topic 已创建。
常见原因:
- NameServer 集群未全部更新(Broker 心跳只发部分节点);
- 客户端缓存旧路由未刷新(默认 30s 更新一次);
- 网络隔离导致部分 NameServer 不可达。
解决方案:
-
强制刷新客户端路由:
java// Producer/Consumer DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(); consumer.setPollNameServerInterval(10000); // 缩短路由拉取间隔 -
检查 NameServer 健康:
- 确保所有 NameServer 可访问;
- 使用
mqadmin clusterList -n <namesrv>验证集群状态;
-
避免单 NameServer:至少部署 2 个 NameServer 实例。
事务消息卡住(半消息未决)
现象:
RMQ_SYS_TRANS_HALF_TOPIC 中消息持续堆积,业务状态已提交但消息未发出。
常见原因:
- Producer 未实现
checkLocalTransaction回查接口; - 回查接口返回
UNKNOWN导致无限重试; - Broker 回查超时(默认 6s)。
解决方案:
-
完善回查逻辑:
java@Override public LocalTransactionState checkLocalTransaction(MessageExt msg) { String txId = msg.getTransactionId(); // 必须根据 txId 查询本地 DB 状态,不可返回 UNKNOWN return db.getStatus(txId) ? COMMIT_MESSAGE : ROLLBACK_MESSAGE; } -
调整回查参数 (Broker 端):
propertiestransactionTimeOut=6000 # 半消息超时时间(ms) transactionCheckMax=5 # 最大回查次数 -
监控半消息堆积:对
RMQ_SYS_TRANS_HALF_TOPIC设置消费延迟告警。
高延迟(发送/消费耗时突增)
现象:
SEND_MSG_TIME 或 CONSUME_TIME 监控指标飙升。
常见原因:
- PageCache 被刷出(大量冷读触发磁盘 I/O);
- GC 停顿(尤其是 Broker Full GC);
- 网络抖动或带宽打满。
解决方案:
-
优化 JVM 参数 (Broker):
bash-Xms8g -Xmx8g -XX:+UseG1GC -XX:MaxGCPauseMillis=100 -
分离 IO 负载:CommitLog 与 ConsumeQueue 使用独立磁盘;
-
限流保护:通过
putMessageFlowControl防止突发流量压垮 Broker; -
网络排查:使用
tcpdump或netstat检查丢包/重传。
Consumer Rebalance 频繁
现象:
消费者日志频繁打印 doRebalance,消费吞吐波动大。
常见原因:
- Consumer 实例频繁上下线(如 K8s 健康检查失败);
- 网络不稳定导致心跳超时;
- Consumer 处理单条消息超时(>
suspendCurrentQueueTimeMillis)。
解决方案:
-
调大心跳超时 (Consumer):
javaconsumer.setHeartbeatBrokerInterval(30000); // 默认 30s consumer.setPollNameServerInterval(30000); -
优化健康检查:K8s 中延长
livenessProbe初始延迟; -
避免长耗时消费:拆分大任务,或使用异步线程池处理。
总结:RocketMQ 线上问题应对原则
| 问题类型 | 核心思路 |
|---|---|
| 堆积 | 扩容 + 优化 + 跳过异常 |
| 丢失 | 同步刷盘 + Dledger + 手动 ACK |
| 资源耗尽 | 监控告警 + 自动清理策略 |
| 一致性 | 路由刷新 + 回查完备性 |
| 性能抖动 | JVM 调优 + IO 隔离 + 限流 |
最佳实践:
- 关键业务必开:同步刷盘 + Dledger + 消息轨迹;
- 必设监控项:
CONSUME_LAG、SEND_MSG_TIME、磁盘使用率、Broker GC;- 上线前压测:模拟堆积、宕机、网络分区等场景。
Kafka VS RocketMQ 元数据(路由信息)获取机制
元数据(路由信息)获取机制 和数据请求路径上存在根本性差异,这直接影响了它们的性能、扩展性和客户端复杂度。
核心结论速览
| 维度 | Apache Kafka | Apache RocketMQ |
|---|---|---|
| 元数据服务 | ZooKeeper(旧) / KRaft(新) (由 Broker 代理访问) | NameServer(无状态注册中心) (Client 直连) |
| Client 是否直连元数据服务? | ❌ 否(只连 Broker) | ✅ 是(Producer/Consumer 直连 NameServer) |
| 元数据获取时机 | 按需触发 : • 首次使用 • 缓存过期(默认 5 分钟) • 收到 NOT_LEADER 错误 |
定时轮询 : • 启动时 • 每 30 秒(默认)主动拉取 |
| 数据请求路径 | 直连 Partition Leader Broker (无中间转发) | 直连 Master Broker (无中间转发) |
| 元数据变更感知延迟 | 秒级(错误驱动 + 可选推送) | ≤30 秒(依赖轮询间隔) |
| 架构哲学 | Client 智能 + Broker 简单 (路由决策在 Client) | NameServer 轻量 + Client 主动 (路由由 NameServer 集中管理) |
一、Kafka:按需获取元数据 + 直连 Leader
场景:Producer 首次向 Topic orders 发送消息
步骤 1️⃣:首次元数据发现(仅一次)
ZooKeeper / KRaft Broker-1 (bootstrap) Producer ZooKeeper / KRaft Broker-1 (bootstrap) Producer 1. MetadataRequest("orders") 2. (内部)从元数据存储获取集群拓扑 3. 返回:Partition 列表 + Leader Broker ID 4. 返回完整元数据: - Broker 列表 - orders-0 → Leader=Broker-2 - orders-1 → Leader=Broker-1
关键点:
- Producer 只与 Broker 通信,不直连 ZK/KRaft;
- Broker 内部从元数据存储(ZK 或 KRaft)获取信息;
- 返回内容包含 所有 Broker 地址 + 每个 Partition 的 Leader。
步骤 2️⃣:后续消息直连 Leader(无元数据交互)
Broker-2 (Leader for orders-0) Producer Broker-2 (Leader for orders-0) Producer 5. ProduceRequest(orders-0, message) 6. ACK(成功)
关键点:
- 消息直接发往 Leader Broker,不经过其他节点;
- 后续 5 分钟内不再查询元数据(除非出错或超时)。
步骤 3️⃣:Leader 变更时自动恢复
- 若 Broker-2 宕机,Producer 下次发送收到
NOT_LEADER_FOR_PARTITION; - 立即触发元数据刷新,重新查询,直连新 Leader。
二、RocketMQ:定时拉取路由 + 直连 Master
场景:Producer 首次向 Topic ORDERS 发送消息
步骤 1️⃣:启动时 + 定期拉取路由信息
NameServer Producer NameServer Producer 1. GET_ROUTEINFO_BY_TOPIC("ORDERS") 2. 返回 TopicRouteData: - QueueData(读写队列数) - BrokerData: • brokerName=broker-a • master=192.168.1.10:10911 • slave=192.168.1.11:10911
关键点:
- Producer 直连 NameServer(通常配置多个);
- NameServer 无状态,只返回注册的 Broker 信息;
- 每 30 秒自动拉取一次 (
pollNameServerInterval=30s)。
步骤 2️⃣:消息直连 Master Broker
Broker Master (192.168.1.10) Producer Broker Master (192.168.1.10) Producer 3. SEND_MESSAGE(ORDERS, message) 4. SEND_OK
关键点:
- 消息直接发往 Master Broker,不经过 NameServer;
- Slave 仅用于读(Consumer 可配置从 Slave 读)。
步骤 3️⃣:Broker 变更需等待下一次轮询
- 若 Master 宕机,NameServer 会在 10--30 秒内(心跳超时)剔除该 Broker;
- Producer 最多延迟 30 秒 才能感知变更(下次拉取路由时)。
三、深度对比:设计权衡分析
| 对比项 | Kafka | RocketMQ |
|---|---|---|
| 元数据一致性 | 强一致(ZK/KRaft 保证) | 最终一致(NameServer 无主从,靠 Client 轮询收敛) |
| 客户端复杂度 | 高(需管理元数据缓存、错误重试) | 低(只需定时拉取) |
| 元数据服务压力 | 低(Broker 缓存 + 批量同步) | 高(每个 Client 每 30s 拉取一次) |
| 大规模集群扩展性 | 极佳(KRaft 支持百万 Partition) | 受限(NameServer 成为瓶颈) |
| 故障感知速度 | 快(秒级,错误驱动) | 慢(≤30s,依赖轮询) |
| 网络拓扑 | Client ↔ Broker(简单) | Client ↔ NameServer + Client ↔ Broker(两跳) |
四、典型场景适用性
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 超大规模日志/事件流 (百万级 Partition) | ✅ Kafka | 元数据服务可扩展,Client 智能路由 |
| 中小规模业务消息 (如订单、通知) | ✅ RocketMQ | 架构简单,NameServer 足够支撑 |
| 要求秒级故障切换 | ✅ Kafka | 错误驱动刷新,无固定延迟 |
| 运维希望简化客户端 | ✅ RocketMQ | Client 逻辑简单,无需处理元数据错误 |
总结
-
Kafka:
"先问一次路,然后直达目的地;走错了立刻问新路。 "
→ 按需、精准、高效,适合高吞吐、大规模场景。
-
RocketMQ:
"每隔 30 秒看一眼地图,然后按图索骥。 "
→ 简单、稳定、易理解,适合中小规模业务系统。
两者并无绝对优劣,Kafka 的"智能客户端"模式牺牲了简单性换取极致性能与扩展性,而 RocketMQ 的"定时拉取"模式以轻微延迟换取架构简洁。选择应基于业务规模、SLA 要求和团队运维能力。
RocketMQ 消息可靠性保证机制
RocketMQ 通过 生产者、Broker、消费者 三个层面的多重保障机制,确保消息不丢失。
生产者端保障
关键机制
- 同步发送模式 :
send()方法阻塞等待 Broker 确认 - 发送重试机制:默认重试 2 次(共 3 次尝试)
- 异常处理:捕获异常并记录日志,支持人工补偿
代码示例
java
// 同步发送 + 异常处理
try {
SendResult result = producer.send(msg);
if (result.getSendStatus() != SendStatus.SEND_OK) {
// 记录失败消息,后续补偿
log.error("消息发送失败: {}", msg);
}
} catch (Exception e) {
// 网络异常等,进行重试或持久化到本地
saveToLocalDB(msg);
}
Broker 端保障
关键机制
- 同步刷盘 vs 异步刷盘 :
- 同步刷盘:消息写入内存后立即刷盘(性能低,可靠性高)
- 异步刷盘:消息写入内存后定时刷盘(性能高,默认配置)
- 主从复制:Master-Slave 架构,支持同步/异步复制
- CommitLog 持久化:所有消息先写入 CommitLog,再分发到 ConsumeQueue
配置示例
properties
# broker.conf
flushDiskType = SYNC_FLUSH # 同步刷盘
brokerRole = SYNC_MASTER # 同步主从
消费者端保障
关键机制
- 手动 ACK 机制:消费成功后才提交 offset
- 消费重试机制:消费失败自动重试(最多 16 次)
- 死信队列:重试超过次数的消息进入 DLQ
代码示例
java
consumer.registerMessageListener((msgs, context) -> {
try {
// 处理业务逻辑
processMessages(msgs);
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
} catch (Exception e) {
// 返回 RECONSUME_LATER 触发重试
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
});
完整消息可靠性流程图
CommitLog Consumer Broker Producer CommitLog Consumer Broker Producer 生产者端保障 alt [重试成功] [重试失败] alt [发送成功] [发送失败] Broker端保障 alt [同步刷盘] [异步刷盘] 消费者端保障 alt [重试超限] alt [消费成功] [消费失败] loop [消费循环] 同步发送消息(send()) 返回SendResult(包含offset) 记录发送成功 自动重试(最多3次) 返回SendResult 保存到本地DB/日志 人工补偿处理 写入CommitLog(内存) 立即持久化到磁盘 返回刷盘成功 后台线程定时刷盘 主从同步(可选) 拉取消息 返回消息列表 处理消息业务逻辑 提交offset(CONSUME_SUCCESS) 更新消费位点 标记重试(RECONSUME_LATER) 延迟重新投递 (最多16次) 移入死信队列(DLQ)
各环节可靠性配置建议
| 组件 | 高可靠性配置 | 性能影响 |
|---|---|---|
| Producer | 同步发送 + 重试 + 异常持久化 | 中等 |
| Broker | 同步刷盘 + 同步主从复制 | 较高 |
| Consumer | 手动ACK + 重试机制 | 低 |
最佳实践
- 生产者:关键业务使用同步发送,配合完善的异常处理
- Broker:根据业务重要性选择刷盘和复制策略
- 消费者:确保消费逻辑幂等,正确处理异常返回状态
- 监控:监控发送成功率、消费延迟、重试队列等指标