43.8 产品 (Kafka / RocketMQ / RabbitMQ / NATS)
一句话
四个产品的差别在模型不在“谁快”:Kafka 是“分区日志”(吞吐与回放最强)、RocketMQ 是“带事务与延迟级别的队列”(业务特性最全)、RabbitMQ 是“灵活路由的队列”(低延迟、小规模最舒服)、NATS 是“轻量发布订阅”(极简、云原生/边缘)。
四个模型,四种形状
| 维度 | Kafka | RocketMQ | RabbitMQ | NATS |
|---|---|---|---|---|
| 抽象 | 分区日志(topic-partition) | 队列 + 延迟级别 + 事务消息 | Exchange + Queue + 路由键 | Subject(层级通配) |
| 消费模型 | 拉,消费组,位点由客户端提交 | 拉,消费组,位点由 broker 管理 | 推,ack/nack | 推/拉,at-most/at-least |
| 顺序 | 分区内 | 队列内(可选全局) | 单队列 | 单订阅(core);JetStream 可持久 |
| 延迟消息 | 需外部实现(时间轮/表) | 内置 18 个延迟级别 | TTL + 死信交换机 | 不支持(需应用层) |
| 事务消息 | 事务 + 幂等生产(broker 内) | 半消息 + 回查 | 无(靠 publisher confirm) | 无 |
| 吞吐量级 | 十万–百万级/秒 | 十万级/秒 | 万–十万级/秒 | 百万级/秒(轻量消息) |
| 保留与回放 | 按时间/大小保留,可回放 | 保留期内可回放 | 消费即删(默认) | 内存流(JetStream 可持久) |
| 运维复杂度 | 高(分区、副本、ISR) | 中高 | 低–中 | 低 |
🔑 选型的三个问题:要不要回放历史(Kafka 独一档)、要不要事务/延迟消息(RocketMQ 最省事)、是不是只要“发出去、收得到”(NATS 最轻)。
选定 Kafka 作为日志层之后,流处理引擎用 Flink 还是 Kafka Streams,见 62.5 Flink / Kafka Streams。
代码:按需求做规则匹配
package main
import "fmt"
type need struct {
name string
tps int // 峰值消息量
ordered bool // 需要严格顺序
delay bool // 需要延迟消息
tx bool // 需要事务消息(半消息/回查)
replay bool // 需要回放历史
light bool // 追求极简/边缘部署
}
var scenarios = []need{
{"日志采集 / 埋点", 500_000, false, false, false, true, false},
{"订单事件(含事务)", 5_000, true, false, true, false, false},
{"30 分钟后提醒", 1_000, false, true, false, false, false},
{"IoT 遥测", 200_000, false, false, false, false, false},
{"边缘节点通知", 2_000, false, false, false, false, true},
{"简单业务解耦", 500, false, false, false, false, false},
}
func recommend(n need) (string, string) {
switch {
case n.light:
return "NATS", "极简、低资源,适合边缘/通知类"
case n.tx:
return "RocketMQ", "半消息 + 回查是内置能力,Kafka 要自己实现"
case n.delay:
return "RocketMQ", "内置延迟级别;RabbitMQ 需 TTL+DLX 组合"
case n.replay && n.tps > 100_000:
return "Kafka", "分区日志 + 按时间保留,回放与吞吐都最强"
case n.ordered && n.tps > 100_000:
return "Kafka", "分区内顺序 + 高吞吐;注意 key 倾斜"
case n.tps < 10_000:
return "RabbitMQ", "低延迟、路由灵活、运维简单,规模不大时最省心"
default:
return "Kafka", "吞吐足够、生态最广,后续加流处理也顺"
}
}
func main() {
fmt.Printf("%-22s %-10s %-10s %-8s %-6s %-6s %-6s %s\n",
"场景", "峰值tps", "顺序", "延迟", "事务", "回放", "轻量", "推荐")
for _, n := range scenarios {
p, why := recommend(n)
fmt.Printf("%-22s %-10d %-10v %-8v %-6v %-6v %-6v %s(%s)\n",
n.name, n.tps, n.ordered, n.delay, n.tx, n.replay, n.light, p, why)
}
}
本机实测(macOS,Apple M1,Go 1.27):
场景 峰值tps 顺序 延迟 事务 回放 轻量 推荐
日志采集 / 埋点 500000 false false false true false Kafka(分区日志 + 按时间保留,回放与吞吐都最强)
订单事件(含事务) 5000 true false true false false RocketMQ(半消息 + 回查是内置能力,Kafka 要自己实现)
30 分钟后提醒 1000 false true false false false RocketMQ(内置延迟级别;RabbitMQ 需 TTL+DLX 组合)
IoT 遥测 200000 false false false false false Kafka(吞吐足够、生态最广,后续加流处理也顺)
边缘节点通知 2000 false false false false true NATS(极简、低资源,适合边缘/通知类)
简单业务解耦 500 false false false false false RabbitMQ(低延迟、路由灵活、运维简单,规模不大时最省心)
读数:
| 场景 | 推荐 | 关键理由 |
|---|---|---|
| 日志采集 | Kafka | 回放 + 高吞吐,生态里有 Flink/Connector |
| 订单事件 | RocketMQ | 事务消息内置,不用自己实现回查 |
| 延迟提醒 | RocketMQ | 延迟级别开箱即用;RabbitMQ 要 TTL+DLX |
| IoT 遥测 | Kafka | 吞吐足够,按时间保留便于补算 |
| 边缘通知 | NATS | 单二进制、资源占用极小 |
| 简单解耦 | RabbitMQ | 规模小、路由灵活,别上重家伙 |
🔑 注意“规则匹配”只是起点:同一家公司往往同时用 2–3 个(Kafka 做数据管道、RocketMQ 做交易事件、NATS 做边缘),不要为了统一而把延迟消息硬塞进 Kafka——那会在业务代码里长出一套定时扫描。
落地:Kafka 的四个动作(未验证)
⚠️ 本机未安装 Kafka/RocketMQ/RabbitMQ,下面的命令未在真实集群验证,版本与参数以官方文档为准。
# 1) 指名 + 可抄:单节点 KRaft 模式(Kafka 3.7+ 不需要 ZooKeeper)
docker run -d --name kafka -p 9092:9092 apache/kafka:3.7.0
# 2) 可验:建 topic 并看描述(分区数、副本数)
docker exec kafka /opt/kafka/bin/kafka-topics.sh \
--bootstrap-server localhost:9092 --create --topic orders \
--partitions 12 --replication-factor 1
docker exec kafka /opt/kafka/bin/kafka-topics.sh \
--bootstrap-server localhost:9092 --describe --topic orders
# 预期输出包含:Topic: orders PartitionCount: 12 ReplicationFactor: 1
# 3) 可验:生产 + 消费一条
echo "hello" | docker exec -i kafka /opt/kafka/bin/kafka-console-producer.sh \
--bootstrap-server localhost:9092 --topic orders
docker exec kafka /opt/kafka/bin/kafka-console-consumer.sh \
--bootstrap-server localhost:9092 --topic orders --from-beginning --max-messages 1
# 4) 可退:删 topic(或停容器)
docker exec kafka /opt/kafka/bin/kafka-topics.sh \
--bootstrap-server localhost:9092 --delete --topic orders
| 步骤 | 判据 | 失败时看什么 |
|---|---|---|
| 启动 | 容器 Up,9092 可连 | 端口占用、KAFKA_NODE_ID 配置 |
| 建 topic | describe 输出分区数/副本数符合预期 | 副本数 > broker 数会失败 |
| 收发 | 消费者能打印 hello | 是否加了 --from-beginning |
| 回退 | 删 topic 后 describe 报不存在 | 是否开了 delete.topic.enable |
反模式
| 反模式 | 后果 |
|---|---|
| 用 RabbitMQ 扛日志/埋点(百万级) | 队列堆积、内存暴涨、吞吐上不去 |
| 用 Kafka 做延迟消息(自己写扫描表) | 业务代码里长出一套定时器,精度与可靠性都差 |
| 用 NATS Core 做“不能丢”的业务消息 | 无持久化,消费者不在线就丢 |
| 认为“RocketMQ 事务消息 = 分布式事务” | 它只保证“本地事务 + 消息发送”的一致性,消费侧仍要幂等 |
| 为了统一全公司只用一个 MQ | 用错模型,靠人肉补能力 |
| 不规划分区数,后期频繁扩 | 再均衡与顺序问题(43.3、43.4) |
自检
Q1 为什么“日志/埋点”几乎都选 Kafka,而不是 RabbitMQ?
▸ 答案:因为日志场景要的是高吞吐 + 按时间回放 + 多消费者各自读。Kafka 的分区日志把消息按顺序追加、按时间保留,多个消费组可以各自从任意位点读,天然适合“一份数据多种用途”;RabbitMQ 的模型是“队列消费即删”,要回放得靠额外存储,且吞吐量级低一个数量级。
Q2 RocketMQ 的“事务消息”与数据库事务是什么关系?
▸ 答案:它解决的是“本地事务 + 消息发送”的原子性:先发半消息(对消费者不可见)→ 执行本地事务 → 提交/回滚半消息;如果提交失败,broker 回查生产者的本地事务状态。它不保证消费侧,消费者仍要幂等;也不跨多个数据库。本质是“用半消息 + 回查替代了本地消息表的轮询”。
Q3 一个团队同时用 Kafka 和 RocketMQ,会不会太重?
▸ 答案:取决于是否真的需要两种模型。常见的合理分工是“Kafka 做数据管道(日志、CDC、流计算)+ RocketMQ 做业务事件(事务消息、延迟消息、顺序)”。如果只是“业务解耦 + 少量延迟消息”,一个 RocketMQ 就够,Kafka 是多余的;反过来“纯数据管道”也不需要 RocketMQ。多一个 MQ = 多一套运维、监控、容量与故障域,要有明确理由。