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 = 多一套运维、监控、容量与故障域,要有明确理由。

关联