Files
cs-note/hhs/MQ/07-主流MQ对比/26-NATS-NSQ-Redis-Streams.md
T
2026-05-24 20:51:06 +08:00

174 lines
9.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [MQ, NATS, NSQ, Redis Streams, 消息队列, 轻量级MQ]
create time: 2026-05-24 19:52
---
# NATS / NSQ / Redis Streams
## 概述
并不是所有场景都需要 Kafka 这样的重量级消息平台。微服务间的轻量通信、已有 Redis 基础设施上的流处理、去中心化的简单队列——这些场景下 NATS、NSQ 和 Redis Streams 各有胜场。本文分别解析三者的设计理念和核心能力,最后给出选型建议。
## 正文
### NATS:极简高性能的消息系统
NATS 由 Derek Collison 创建,设计哲学是"做一件事并做到极致"——提供简单、快速、可靠的消息通信。NATS Server 是一个单一的 Go 可执行文件,无外部依赖,启动即用。
**Subject-Based 消息模型**:NATS 使用 Subject(主题)路由消息,支持通配符匹配(`orders.*` 匹配 `orders.create`,`orders.>` 匹配 `orders.create.us` 等任意层级)。发送者只需指定 Subject,NATS 自动将消息路由到所有匹配的订阅者。
**核心特点**:
- 极致性能:单节点可支撑每秒千万级消息,微秒级延迟
- At-Most-Once 语义:默认不持久化,消息发出去就不管了,适合实时性要求高但允许丢消息的场景
- 自适应发现:客户端无需知道完整的集群拓扑,连接任意节点即可自动发现
**NATS JetStream**:这是 NATS 的持久化层,补齐了消息持久化和流式消费的短板。JetStream 提供:
- 消息持久化到磁盘
- 消费者可以回放历史消息
- At-Least-Once 和 Exactly-Once 语义支持
- 消费者组(Queue Groups)和消息确认机制
```mermaid
graph LR
Publisher["Publisher"] -->|"Subject: orders.new"| NATS["NATS Server Cluster"]
NATS -->|"实时投递"| Sub1["Subscriber 1"]
NATS -->|"实时投递"| Sub2["Subscriber 2"]
NATS -->|"持久化"| JS["JetStream 存储"]
JS -->|"回放消费"| Sub3["JetStream Consumer"]
style NATS fill:#4A90D9,color:#fff
style JS fill:#F5A623,color:#fff
style Publisher fill:#6EC1E0,color:#fff
style Sub1 fill:#6EC1E0,color:#fff
style Sub2 fill:#6EC1E0,color:#fff
style Sub3 fill:#6EC1E0,color:#fff
```
> [!question] NATS 和 RabbitMQ 都是"传统的"消息中间件,本质区别在哪?
> NATS 的核心是"消息路由器",设计优先级是速度和简洁,持久化是后加的能力(JetStream)。RabbitMQ 的核心是"消息代理",设计优先级是投递保证和灵活路由(Exchange/Binding),持久化是一等公民。简单说:NATS 是跑车,RabbitMQ 是卡车。
### NSQ:去中心化的实时消息平台
NSQ 由 Bitly 开发,最大特点是**去中心化**——没有中心化的元数据管理节点,每个 nsqd 实例独立运行,通过 nsqlookupd 实现拓扑发现。
**架构组件**:
- **nsqd**:消息守护进程,负责接收、队列和投递消息。每个 nsqd 独立管理自己 Topic 和 Channel(类似 Consumer Group),数据存储在本地。
- **nsqlookupd**:拓扑发现服务,nsqd 启动时向 nsqlookupd 注册自己,消费者通过查询 nsqlookupd 发现某个 Topic 在哪些 nsqd 上。nsqlookupd 之间互不通信,可以部署多个实现高可用。
**消息存储**:NSQ 采用内存 + 磁盘的混合策略。消息先写入内存队列,内存队列达到阈值后溢写到磁盘。这种设计在性能和持久性之间取了折中。
**特点**:去中心化意味着没有单点故障,但也不支持消息回溯——消费过的消息就没了(At-Most-Once)。适合日志收集、实时通知等对消息可靠性要求不高的场景。
```mermaid
graph TB
Producer1["Producer"] -->|"发布消息"| NSQD1["nsqd 1"]
Producer1 -->|"发布消息"| NSQD2["nsqd 2"]
NSQD1 -->|"注册"| Lookupd["nsqlookupd"]
NSQD2 -->|"注册"| Lookupd
Lookupd -->|"拓扑发现"| Consumer["Consumer"]
Consumer -->|"消费消息"| NSQD1
Consumer -->|"消费消息"| NSQD2
style Lookupd fill:#4A90D9,color:#fff
style NSQD1 fill:#F5A623,color:#fff
style NSQD2 fill:#F5A623,color:#fff
style Producer1 fill:#6EC1E0,color:#fff
style Consumer fill:#6EC1E0,color:#fff
```
### Redis Streams:基于 Redis 的流数据结构
Redis 5.0 引入的 Streams 是一个功能完备的流数据结构,基于 **Radix Tree**(基数树)实现内存中的高效存储。它让 Redis 从"缓存+简单队列"进化为一个轻量级消息流平台。
**核心命令**:
- `XADD key ID field value`:向 Stream 追加消息,返回自动生成的消息 ID(时间戳-序号)
- `XREAD COUNT 10 BLOCK 5000 STREAMS key ID`:从指定位置读取消息,支持阻塞等待
- `XREADGROUP GROUP mygroup consumer1 COUNT 10 BLOCK 5000 STREAMS key >`:消费者组模式消费
**Consumer Group 支持**:Redis Streams 原生支持消费者组,多个消费者共享一个 Stream,每条消息只被组内的一个消费者处理。通过 Pending Entries List(PEL)跟踪已投递但未确认的消息,支持消息重试和死信处理。
```mermaid
graph LR
Producer["Producer"] -->|"XADD"| Stream["Redis Stream Radix Tree"]
Stream -->|"XREADGROUP"| CG["Consumer Group"]
CG -->|"分配消息"| C1["Consumer 1"]
CG -->|"分配消息"| C2["Consumer 2"]
C1 -->|"XACK"| Stream
C2 -->|"XACK"| Stream
style Stream fill:#D0021B,color:#fff
style CG fill:#F5A623,color:#fff
style Producer fill:#6EC1E0,color:#fff
style C1 fill:#6EC1E0,color:#fff
style C2 fill:#6EC1E0,color:#fff
```
### 三者对比
| 维度 | NATS(JetStream) | NSQ | Redis Streams |
|------|-------------------|-----|---------------|
| 持久化 | JetStream 支持,可配置副本数 | 内存+磁盘混合,不支持回溯 | 基于 AOF/RDB 持久化,可配置 |
| 吞吐量 | 极高(千万级/秒) | 高(百万级/秒) | 中等(十万级/秒,受限于单线程) |
| 延迟 | 微秒级 | 毫秒级 | 毫秒级 |
| 运维复杂度 | 低,单一二进制 | 低,组件少但 nsqlookupd 需独立部署 | 低,如果已有 Redis 则零额外成本 |
| 消费语义 | At-Most-Once / At-Least-Once / Exactly-Once | At-Most-Once | At-Least-Once(Consumer Group + ACK) |
| 消息回溯 | 支持 | 不支持 | 支持(按 ID 回溯) |
| 适用场景 | 微服务通信、IoT、实时事件 | 日志收集、实时通知 | 已有 Redis 基础设施上的轻量队列 |
### 轻量级 MQ 选型建议
> [!question] Redis Streams 功能这么强大,为什么还需要专门的 MQ?
> Redis Streams 功能确实很全面,但它的吞吐受限于 Redis 单线程模型,大数据量下会成为瓶颈。而且 Redis 本质是内存数据库,Streams 只是它的一个数据结构——如果消息量把内存撑爆,会影响 Redis 上其他业务。更重要的是,专门的 MQ(如 NATS JetStream)在集群扩展、数据分片、消费语义保证上做得更深入。简单说:Redis Streams 是"顺带能做消息队列",NATS/Kafka 是"专门为消息队列而生"。
**什么时候选 NATS**:微服务间高频通信、IoT 设备消息上报、需要极低延迟的实时事件系统。NATS 的 Go 原生实现和零依赖特性,让它特别适合云原生和 Kubernetes 环境。
**什么时候选 NSQ**:日志收集管道、实时通知推送、对消息可靠性要求不高的异步任务分发。去中心化设计让它在需要快速搭建、不想维护额外基础设施时很香。
**什么时候选 Redis Streams**:团队已经深度使用 Redis,需要在不引入新组件的前提下获得消息队列能力;消息量不大(日均千万级以内);对延迟和吞吐要求不极端。
### Go 代码示例:NATS JetStream
```go
// 连接 NATS 并使用 JetStream 发布和消费
nc, _ := nats.Connect("nats://localhost:4222")
defer nc.Close()
js, _ := nc.JetStream()
// 创建 Stream(持久化存储单元)
js.AddStream(&nats.StreamConfig{
Name: "ORDERS",
Subjects: []string{"orders.>"}, // 订阅所有 orders. 开头的 Subject
Storage: nats.FileStorage, // 持久化到磁盘
Replicas: 3, // 3 副本
})
// 发布消息
_, err := js.Publish("orders.new", []byte(`{"orderId":"1001","item":"laptop"}`))
if err != nil {
fmt.Println("发布失败:", err)
}
// 消费消息(Push 模式)
sub, _ := js.Subscribe("orders.>", func(msg *nats.Msg) {
fmt.Println("收到消息:", string(msg.Data))
msg.Ack() // 确认消费
}, nats.Durable("order-processor"), // 持久化消费者名,重启后从上次位点继续
nats.ManualAck(), // 手动确认模式
)
defer sub.Unsubscribe()
select {} // 阻塞等待
```
代码解析:先通过 `AddStream` 创建一个名为 `ORDERS` 的 JetStream Stream,配置了文件存储和 3 副本。`Publish` 将消息发送到 `orders.new` Subject,该 Subject 匹配 Stream 的 `orders.>` 通配符。`Subscribe` 使用 Push 模式消费,`Durable` 参数让消费位点持久化——即使消费者重启也能从断点继续。这是 NATS JetStream 相比原生 NATS 最大的增强。
## 关联笔记
- [[07-主流MQ对比/22-Kafka|Kafka]]
- [[07-主流MQ对比/24-RocketMQ|RocketMQ]]
- [[07-主流MQ对比/25-Apache-Pulsar|Apache Pulsar]]
- [[02-消息模型/3-MQ-消息模型|MQ 消息模型]]
- [[07-主流MQ对比/27-MQ-选型对比|MQ 选型对比]]
- [[12-架构与实战/47-MQ-与微服务|MQ 与微服务]]