53-消息队列与事件流
8 分钟阅读
消息队列与事件流 (Messaging and Event Streams)
面向 Rust 1.97.1 (stable, 2026-07)。本篇假设你熟悉 Go,凡有可比性处均给出 🐹 Go 对比。
热度:
hot高频 |common常见 |occasional偶尔 |advanced进阶少见
本篇能解决什么:
- 你是否在 Go 里用过 Kafka / NATS / RabbitMQ,一到 Rust 就问「官方客户端是哪个」?
- 你是否分不清 Redis Pub/Sub、NATS、Kafka 各自该用在哪?
- 你是否想写 async 消费循环 + 优雅关停,却怕丢消息或重复消费?
- 你是否卡在重试、死信、幂等、背压这些「生产必问」?
术语速查表:
| 术语 / 缩略词 | 全称 / 读法 | 中文 | 一句话解释 | Go 里的近亲 |
|---|---|---|---|---|
| MQ | Message Queue | 消息队列 | 异步解耦的中间件统称 | 同概念 |
| Kafka | — | 分布式日志/流平台 | 高吞吐、可重放的分区日志 | sarama / confluent |
| rdkafka | — | librdkafka 的 Rust 绑定 | Rust 侧常见 Kafka 客户端 | sarama 角色近似 |
| NATS | — | 云原生消息系统 | 轻量 pub/sub、请求应答 | nats.go |
| async-nats | — | NATS 异步客户端 | Tokio 友好的 NATS crate | nats.go |
| RabbitMQ | — | AMQP 经纪人 | 队列、路由键、传统企业 MQ | amqp091-go |
| lapin | — | AMQP 客户端 | Rust 常见 RabbitMQ 库 | amqp 客户端 |
| consumer group | — | 消费者组 | 组内分摊分区/队列 | Kafka consumer group |
| offset | — | 偏移量 | 消费进度游标 | Kafka offset |
| DLQ | Dead Letter Queue | 死信队列 | 多次失败后转入的隔离队列 | 同概念 |
| idempotency | — | 幂等 | 同一消息处理多次结果相同 | 同概念 |
| backpressure | — | 背压 | 下游慢时限制上游灌入 | 同概念 |
| serde | — | 序列化 | 消息正文编解码;见 36 | json/protobuf |
说明:本表覆盖本篇出现的所有专业名词与缩略词;正文首次出现时仍会就地解释一次。
热度索引:
| 热度 | 题目 |
|---|---|
hot | Q1, Q2, Q3, Q4, Q5, Q8 |
common | Q6, Q7, Q9, Q10, Q11, Q12 |
occasional | — |
advanced | — |
Q1. Rust 里 Kafka 用什么?和 Go sarama 怎么对照?
Tags: hot Kafka rdkafka sarama
适用版本: rdkafka 等;以 crates.io 为准
一句话答案:
常见选择是 rdkafka(绑 librdkafka)。对标 Go 的 sarama / confluent:都是客户端库,不是标准库。生产还要自己管序列化、提交 offset、错误与再均衡。
解答:
| |
| |
| |
Go 对比:
| |
- Go 怎么做:纯 Go 客户端较多。
- Rust 为什么不同:rdkafka 走 C 库,绑定与链接是额外成本。
- Go 程序员易踩的坑:
cargo add后本机构建失败——缺原生依赖。
记忆点:
- Kafka 客户端 ≈
rdkafka。 - 链接/CI 环境要单独准备。
Q2. NATS / async-nats 最小 pub/sub?
Tags: hot NATS async-nats pubsub
适用版本: async-nats;需 Tokio
一句话答案:
async-nats 连接后 publish / subscribe;适合轻量通知与请求-应答。比 Kafka 简单,比 Redis Pub/Sub 更「消息系统」一些(见 Q4)。
解答:
| |
| |
| |
Go 对比:
| |
- API 心智非常接近。
- Go 程序员易踩的坑:在 async 里用阻塞客户端——选
async-nats。
记忆点:
- 轻量总线 → NATS +
async-nats。 - subject 通配要设计好层级。
Q3. RabbitMQ / lapin 和 Go amqp 差在哪?
Tags: hot RabbitMQ lapin AMQP
适用版本: lapin;AMQP 0.9.1 直觉
一句话答案:
Rust 常用 lapin 谈 AMQP:声明 exchange/queue、bind、publish、consume。和 Go amqp091-go 同模型;差异在 async API 与连接恢复要自己接。
解答:
| |
| |
| |
Go 对比:
- 协议相同;crate 名不同。
- Go 程序员易踩的坑:以为有
database/sql那种 std AMQP——没有。
记忆点:
- Rabbit →
lapin。 - 先搞清 exchange 类型再写代码。
Q4. Redis Pub/Sub 够用吗?何时必须上 Kafka?
Tags: hot Redis Kafka 选型
适用版本: 架构选型
一句话答案: 扇出通知、允许丢、不需长期重放 → Redis Pub/Sub(见 48)或 NATS 常够。要堆积、消费组、重放、审计日志 → Kafka(或同类日志系统)。别把 Redis Pub/Sub 当任务队列。
解答:
| 需求 | 倾向 |
|---|---|
| 缓存失效广播 | Redis Pub/Sub |
| 轻量 RPC/通知 | NATS |
| 传统任务队列/路由 | RabbitMQ |
| 事件溯源/大数据流/重放 | Kafka |
| |
| |
Go 对比:
- 同样选型问题;语言不改变 CAP/运维成本。
- Go 程序员易踩的坑:用 Redis list 当「简易 Kafka」却无消费语义设计。
记忆点:
- 要重放/堆积 → 日志型(Kafka)。
- 只要广播 → Pub/Sub 可能够。
Q5. 消费者组 / offset 提交直觉是什么?
Tags: hot consumer-group offset
适用版本: Kafka 心智;其它系统有近似
一句话答案: 消费者组让多实例分摊分区;offset 记录「读到哪」。先处理成功再提交(或事务性提交)——提交太早会丢,太晚会重复。Rust 客户端 API 不同,但这条语义不变。
解答:
| |
| |
| |
Go 对比:
- sarama/consumer-group 同语义。
- Go 程序员易踩的坑:换语言以为语义变简单了——没有。
记忆点:
- 组内分摊;提交策略决定丢还是重。
- 默认按 at-least-once + 幂等想。
Q6. 消息正文用 JSON 还是 Protobuf/Avro?
Tags: common serde protobuf schema
适用版本: 序列化选型
一句话答案: 内部快速迭代 → JSON + serde 往往够。跨语言/强 schema/体积敏感 → Protobuf(见 45)或 Avro + Schema Registry。队列里传的是字节,契约要单独管。
解答:
| |
| |
| |
Go 对比:
encoding/jsonvs protobuf 同一权衡。- Go 程序员易踩的坑:Kafka 里混用无版本字段的 JSON。
记忆点:
Q7. async 消费循环 + 优雅关停怎么写?
Tags: common async shutdown select
适用版本: Tokio;见 31-async
一句话答案:
select! 同时等「下一条消息」和「关闭信号」(CancellationToken / ctrl_c);退出前停止拉取、等 in-flight 处理完再提交/断开。对标 Go ctx 取消 + WaitGroup。
解答:
| |
| |
| |
Go 对比:
| |
- 心智相同。
- Go 程序员易踩的坑:
drop连接就走,in-flight 未完成。
记忆点:
select!+ 取消令牌。- 关停要排空,不是直接杀进程。
Q8. 死信 / 重试 / 幂等怎么建模?
Tags: hot DLQ retry idempotency
适用版本: 业务设计;与具体 MQ 配置结合
一句话答案: 瞬时错误 → 有限次重试 + 退避;超限 → DLQ(死信队列);业务上用 幂等键(消息 ID / 业务唯一键)防重复。Rust 不替你做这些,要写进处理器。
解答:
| |
| |
| |
Go 对比:
- 同样要自己做;框架只给钩子。
- Go 程序员易踩的坑:只靠「恰好不重复」上线。
记忆点:
- 重试有上限;失败进 DLQ。
- at-least-once ⇒ 必须幂等。
Q9. 和 gRPC 流式怎么分工?
Tags: common gRPC streaming 边界
适用版本: 架构边界
一句话答案: 同步 RPC / 强类型服务间调用 → gRPC(45)。异步解耦、多订阅者、削峰 → MQ。gRPC stream ≠ 消息队列:连接生命周期和背压模型不同。
解答:
| |
| |
Go 对比:
- 同样分工。
- Go 程序员易踩的坑:用 gRPC stream 当公司级事件总线。
记忆点:
- 请求响应 → RPC;事件扩散 → MQ。
Q10. 本地怎么起 Kafka/Rabbit/NATS?
Tags: common docker local-dev
适用版本: Docker Compose 惯例
一句话答案:
开发机用 Docker Compose 起单节点;连接串写进 .env(见 44-configuration)。别在文档里假设「本机已装 broker」。
解答:
| |
| |
| |
Go 对比:
- 一样用容器。
- Go 程序员易踩的坑:CI 没起依赖导致 flaky。
记忆点:
- 本地依赖容器化。
- URL 走配置,别写死。
Q11. 背压 / 批量发送常见写法?
Tags: common backpressure batch
适用版本: 客户端配置 + 应用层
一句话答案:
生产者:限制 in-flight、批量 flush;消费者:prefetch / 并发 worker 有界。应用层用有界 channel 接业务(见 29/31)。无界缓冲 = 延迟内存爆。
解答:
| |
| |
Go 对比:
- buffered channel 同思路。
- Go 程序员易踩的坑:
make(chan T, 1_000_000)当解决方案。
记忆点:
- 有界队列 + 有界并发。
- 批量换吞吐,换来延迟与复杂。
Q12. 选型表:Redis vs NATS vs Kafka vs RabbitMQ?
Tags: common cheatsheet 选型
适用版本: 架构决策
一句话答案:
按「要不要持久/重放、路由复杂度、运维成本」选;语言只决定客户端 crate。Rust:Redis 见 48,NATS→async-nats,Kafka→rdkafka,Rabbit→lapin。
解答:
| Redis Pub/Sub | NATS | RabbitMQ | Kafka | |
|---|---|---|---|---|
| 复杂度 | 低 | 低-中 | 中 | 高 |
| 持久/重放 | 弱 | 可选 JetStream | 队列持久 | 强 |
| 典型 Rust crate | redis | async-nats | lapin | rdkafka |
| Go 近亲 | go-redis | nats.go | amqp091 | sarama |
| |
记忆点:
- 先语义后实现。
- 本篇给地图;运维细节看各中间件文档。