为什么项目要选择 RocketMQ?
结论:选消息队列(Message Queue,MQ)比的不是峰值参数,而是"业务真正需要的能力"和"团队能承担多少维护成本"。交易类系统要事务消息、顺序消息、海量堆积削峰,RocketMQ 通常是默认答案;日志采集与流式计算选 Kafka;协议多、路由灵活、消息量不大的系统集成选 RabbitMQ。
一、三个候选的定位差异
| 维度 | RocketMQ | Kafka | RabbitMQ |
|---|---|---|---|
| 出身 | 阿里交易链路,经双 11 验证 | LinkedIn 日志管道 | Erlang 实现的 AMQP 标准 |
| 单机吞吐 | 十万级 | 十万到百万级 | 万级 |
| 延迟 | 毫秒级 | 毫秒级(批量大时偏高) | 微秒到毫秒,最低 |
| 消息模型 | 队列 + Tag 过滤 | 分区日志 + 消费组位点 | 交换机 + 路由键绑定 |
| 事务消息 | 原生支持(半消息 + 回查) | 仅支持流内事务,不能与本地库绑定 | 不支持(仅发送方事务) |
| 延迟消息 | 4.x 固定 18 级,5.x 任意时间 | 不支持,需自建 | 靠 TTL + 死信队列拼装 |
| 顺序消息 | 队列级顺序,原生 API | 分区内有序 | 单队列单消费者才有序 |
| 消息回溯 | 按时间戳重置位点 | 按 offset / 时间戳 | 不支持,消费即删 |
| 典型场景 | 订单、支付、库存、削峰 | 日志、埋点、CDC、流计算 | 业务解耦、复杂路由、RPC 异步化 |
二、选型决策树
业务诉求是什么?
├─ 日志/埋点/CDC,追求吞吐,允许秒级延迟 ......... Kafka
├─ 本地事务与发消息必须一致(下单扣库存 + 发消息)
│ └─ 需要 Broker 侧回查兜底 .................. RocketMQ 事务消息
├─ 同一订单的消息必须严格按序 .................... RocketMQ 顺序消息
├─ 30 分钟未支付自动关单 ........................ RocketMQ 延迟消息
├─ 按 routing key 做多播/主题订阅,客户端语言杂 ... RabbitMQ
└─ 日消息量 < 十万、团队 2 人 ................... 先别上 MQ
最后一条常被忽略:MQ 引入的是最终一致性和幂等的复杂度。日消息量很小、也没有削峰诉求时,用一张 outbox 表加定时任务反而更省心。
三、RocketMQ 的独有能力
| 能力 | 解决的问题 | 关键机制 |
|---|---|---|
| 事务消息 | 本地事务成功但消息发送失败 | 半消息(Half Message)先落盘,本地事务提交后二次确认,Broker 定时回查 |
| 顺序消息 | 同一业务键的消息乱序 | 按业务键选队列,消费端对队列加锁,串行消费 |
| 延迟消息 | 定时任务分散在业务代码里 | 4.x 用 18 个延迟级别,5.x 支持任意时间戳 |
| Tag / SQL92 过滤 | 一个 Topic 下按业务分流 | Broker 侧过滤,减少无效网络传输 |
| 消息轨迹 | 排查"消息去哪了" | 轨迹 Topic 记录发送、存储、消费三个节点 |
| 海量堆积 | 大促削峰 | CommitLog 顺序写 + ConsumeQueue 稀疏索引,堆积只增磁盘不增随机 IO |
四、落地配置示例
选定 RocketMQ 之后,生产端的可靠性配置要一起定下来:
# producer.properties
rocketmq.namesrv.addr=10.0.0.11:9876;10.0.0.12:9876
rocketmq.producer.group=order_producer
rocketmq.producer.send.timeout=3000
rocketmq.producer.retry.times=2
rocketmq.producer.compress.msg.body.over.how.much=4096
DefaultMQProducer producer = new DefaultMQProducer("order_producer");
producer.setNamesrvAddr("10.0.0.11:9876;10.0.0.12:9876");
producer.setSendMsgTimeout(3000);
producer.setRetryTimesWhenSendFailed(2);
producer.start();
// 用订单号做分区键:同一订单落到同一队列,天然获得顺序
Message msg = new Message("ORDER_TOPIC", "PAID", orderId,
JSON.toJSONBytes(event));
SendResult r = producer.send(msg, 3000);
if (r.getSendStatus() != SendStatus.SEND_OK) {
// 进入本地补偿表,由定时任务重投
}
选型结论要落到配置上:既然选择了支持顺序消息的 RocketMQ,就应该用订单号做分区键、用 ORDER_TOPIC 而不是"一个业务一个 Topic";既然看中可靠投递,就应该关掉"发送失败只打日志"的写法。
五、常见追问
- RocketMQ 吞吐不如 Kafka,为什么交易系统还选它? 单机十万级对订单链路已经够用,换来的是事务消息、顺序消息、延迟消息这些 Kafka 没有的原生能力,省掉大量自研补偿代码。
- 5.0 相比 4.x 变了什么? 引入无状态 Proxy 统一接入、POP 消费模式、任意时间延迟消息、存算分离部署,客户端协议上兼容 gRPC。
- 能不能同时用 Kafka 和 RocketMQ? 能,但要有清晰边界:日志类走 Kafka,交易类走 RocketMQ;并在公司内部封装统一发送接口,避免业务直接耦合两套客户端。
- 团队没人懂 Erlang,是不是就不能用 RabbitMQ? 可以,但要清楚运维成本:队列堆积、流控、集群分区处理都需要经验,出问题时排查难度不比 RocketMQ 低。
小结
- 选型先看业务需要哪一种语义(事务、顺序、延迟、广播),再看吞吐和延迟。
- RocketMQ 的核心卖点是把交易场景的可靠性能力做进了 Broker,而不是留给业务自研。
- Kafka 强在吞吐与生态,RabbitMQ 强在路由灵活与低延迟,三者不是替代关系。
- 消息量极小的系统,引入 MQ 的复杂度可能高于收益,先考虑数据库表 + 定时轮询。