vault backup: 2026-05-24 20:51:06
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
---
|
||||
tags: [MQ, Kafka, 消息队列, 分布式系统]
|
||||
create time: 2026-05-24 19:52
|
||||
---
|
||||
|
||||
# Kafka
|
||||
|
||||
## 概述
|
||||
|
||||
Kafka 是 LinkedIn 开源的分布式流处理平台,以极高的吞吐量和水平扩展能力著称。它采用分区日志(Partition Log)作为核心存储模型,配合副本机制和消费者组协议,成为大数据和事件驱动架构的事实标准。
|
||||
|
||||
## 正文
|
||||
|
||||
### 1. 架构总览
|
||||
|
||||
Kafka 集群由多个 Broker 组成,消息按 Topic 逻辑分类,每个 Topic 被划分为若干 Partition 分散在不同 Broker 上。每个 Partition 有多个 Replica(副本),其中一个为 Leader 负责读写,其余 Follower 通过拉取同步数据。
|
||||
|
||||
早期版本依赖 ZooKeeper 管理元数据(Broker 注册、Topic 配置、Leader 选举等),从 Kafka 3.3 开始正式推出 KRaft 模式,用内置的 Raft 共识协议取代 ZooKeeper,简化了运维和启动流程。
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph Cluster["Kafka 集群"]
|
||||
B1["Broker 1<br/>Leader P0, Follower P1"]
|
||||
B2["Broker 2<br/>Leader P1, Follower P2"]
|
||||
B3["Broker 3<br/>Leader P2, Follower P0"]
|
||||
end
|
||||
|
||||
Producer["Producer"] --> B1
|
||||
Producer --> B2
|
||||
Producer --> B3
|
||||
B1 --> ConsumerGroup["Consumer Group"]
|
||||
B2 --> ConsumerGroup
|
||||
B3 --> ConsumerGroup
|
||||
|
||||
B1 <-.-> B2
|
||||
B2 <-.-> B3
|
||||
B3 <-.-> B1
|
||||
|
||||
Controller["KRaft Controller<br/>元数据管理"] --> B1
|
||||
Controller --> B2
|
||||
Controller --> B3
|
||||
|
||||
style Cluster fill:#f9f9f9,color:#333
|
||||
style Controller fill:#4A90D9,color:#fff
|
||||
```
|
||||
|
||||
### 2. 高吞吐原理
|
||||
|
||||
Kafka 单集群可轻松达到百万级 TPS,核心秘诀在于:
|
||||
|
||||
- **顺序写磁盘**:Partition 是 append-only 日志,写入永远追加到文件末尾,避免了随机 I/O。顺序写磁盘的吞吐甚至可以媲美内存随机写(600MB/s vs 200MB/s 这种量级)。
|
||||
- **零拷贝(sendfile)**:Consumer 拉取消息时,Kafka 直接通过 Linux `sendfile` 系统调用将 Page Cache 中的数据发送到网卡,绕过用户态缓冲区,减少两次上下文切换和数据拷贝。
|
||||
- **批量发送**:Producer 端将多条消息攒成一个批次(batch)再发送,减少网络 RTT。`linger.ms` 和 `batch.size` 控制攒批策略。
|
||||
- **消息压缩**:支持 Snappy、LZ4、Zstd 等压缩算法,在 Producer 端压缩、Consumer 端解压,显著减少网络带宽和磁盘占用。
|
||||
- **分区并行**:多个 Partition 可以被不同的 Consumer 并行消费,也可以被不同的 Producer 并行写入,天然支持水平扩展。
|
||||
|
||||
> [!question] 思考:零拷贝要求数据不能被修改,那 Kafka 对消息做压缩怎么办?
|
||||
> 压缩发生在 Producer 端,写入 Partition 的数据已经是压缩后的字节流。Broker 存储和传输时保持原样,解压由 Consumer 端完成。所以零拷贝和压缩并不冲突——Broker 不需要解压就能直接 sendfile。
|
||||
|
||||
### 3. 副本机制
|
||||
|
||||
Kafka 的数据可靠性靠副本机制保证。每个 Partition 有多个 Replica,分布在不同 Broker 上:
|
||||
|
||||
- **Leader**:处理所有读写请求。
|
||||
- **Follower**:被动拉取 Leader 的数据,保持同步。
|
||||
- **ISR(In-Sync Replicas)**:与 Leader 保持同步的副本集合。如果 Follower 落后太多(由 `replica.lag.time.max.ms` 控制),会被踢出 ISR。
|
||||
- **LEO(Log End Offset)**:每个副本最后一条消息的下一个偏移量。
|
||||
- **HW(High Watermark)**:ISR 中最小的 LEO。Consumer 只能读到 HW 之前的消息,保证读到的数据在所有 ISR 副本上都存在。
|
||||
|
||||
Leader 崩溃时,Controller 从 ISR 中选举新 Leader。如果 `unclean.leader.election.enable=true`,允许从非 ISR 副本中选举(可能丢数据),生产环境应设为 `false`。
|
||||
|
||||
> [!question] 为什么 HW 要取 ISR 中最小的 LEO,而不是最大或平均值?
|
||||
> 因为 HW 的意义是"所有同步副本都确认收到的位置"。取最小值才能保证:即使 Leader 挂了,任何一个 ISR 副本接替后,Consumer 已读到的数据都不会丢失。
|
||||
|
||||
### 4. Producer 分区策略
|
||||
|
||||
Producer 发送消息时需要决定发往哪个 Partition:
|
||||
|
||||
| 策略 | 说明 |
|
||||
|------|------|
|
||||
| **轮询(Round-Robin)** | 默认策略,消息均匀分配到各 Partition,保证最大吞吐 |
|
||||
| **Key 哈希** | 指定 Key 时,对 Key 做 hash 取模确定 Partition。相同 Key 的消息总是发到同一 Partition,保证局部有序 |
|
||||
| **自定义** | 实现 `Partitioner` 接口,按业务逻辑路由(如按用户 ID 分区) |
|
||||
|
||||
**acks 参数**控制 Producer 的可靠性级别:
|
||||
|
||||
| acks | 含义 | 适用场景 |
|
||||
|------|------|----------|
|
||||
| `0` | 不等确认,发完即忘 | 日志采集等允许丢失的场景 |
|
||||
| `1` | Leader 写入成功即返回 | 大多数业务场景,平衡性能与可靠性 |
|
||||
| `all` | 所有 ISR 副本写入成功才返回 | 金融级可靠性要求 |
|
||||
|
||||
> [!question] 思考题:Kafka 的 acks=all + min.insync.replicas=2 配置为什么能兼顾性能和可靠性?
|
||||
>
|
||||
> **提示**:想想如果只有 1 个副本(min.insync.replicas=1),acks=all 等于 acks=1,可靠性没有实质提升。设为 2 意味着至少有两个副本确认,即使 Leader 宕机,数据也不会丢。而 ISR 副本通常是同步延迟极低的(毫秒级),所以对性能影响有限。这个组合的精髓在于:acks=all 保证写入被多个副本确认,min.insync.replicas 保证 ISR 中至少有足够副本,两者配合才真正实现了"不丢数据"。
|
||||
|
||||
### 5. Consumer 与 Consumer Group
|
||||
|
||||
Consumer Group 是 Kafka 实现"发布-订阅"和"队列"两种语义的核心机制:
|
||||
|
||||
- 同一 Group 内的 Consumer 分担消费(类似队列模式)。
|
||||
- 不同 Group 各自独立消费全部消息(类似发布-订阅模式)。
|
||||
|
||||
**Rebalance**:当 Consumer 加入/离开 Group,或 Topic 分区数变化时,触发分区重新分配。Kafka 引入了 **Cooperative Rebalance**(渐进式再平衡),避免 Stop-the-World 式的全局暂停。
|
||||
|
||||
**Offset 管理**:
|
||||
- **自动提交**:`enable.auto.commit=true`,后台定期提交 Offset。简单但可能丢消息(提交后还没处理完就崩溃)或重复消费。
|
||||
- **手动提交**:消费完成后显式调用 `commitSync()` 或 `commitAsync()`。更可靠,是生产环境推荐做法。Offset 存储在 `__consumer_offsets` 内部 Topic 中。
|
||||
|
||||
```go
|
||||
// 使用 confluent-kafka-go 创建 Producer
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/confluentinc/confluent-kafka-go/v2/kafka"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 创建 Producer 实例,指定 Broker 地址
|
||||
p, err := kafka.NewProducer(&kafka.ConfigMap{
|
||||
"bootstrap.servers": "localhost:9092",
|
||||
"acks": "all", // 等待所有 ISR 副本确认
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
topic := "my-topic"
|
||||
// 发送消息
|
||||
err = p.Produce(&kafka.Message{
|
||||
TopicPartition: kafka.TopicPartition{
|
||||
Topic: &topic,
|
||||
Partition: kafka.PartitionAny, // 自动选择分区
|
||||
},
|
||||
Value: []byte("hello kafka"),
|
||||
}, nil)
|
||||
if err != nil {
|
||||
fmt.Printf("produce failed: %v\n", err)
|
||||
}
|
||||
|
||||
// 确保所有消息发送完成
|
||||
p.Flush(5000)
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// 使用 confluent-kafka-go 创建 Consumer
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/confluentinc/confluent-kafka-go/v2/kafka"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 创建 Consumer 实例,加入 Consumer Group
|
||||
c, err := kafka.NewConsumer(&kafka.ConfigMap{
|
||||
"bootstrap.servers": "localhost:9092",
|
||||
"group.id": "my-group",
|
||||
"auto.offset.reset": "earliest", // 从最早消息开始消费
|
||||
"enable.auto.commit": false, // 关闭自动提交,手动控制
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
// 订阅 Topic
|
||||
c.SubscribeTopics([]string{"my-topic"}, nil)
|
||||
|
||||
for {
|
||||
// 轮询拉取消息,超时 100ms
|
||||
msg, err := c.ReadMessage(100e6)
|
||||
if err != nil {
|
||||
continue // 超时或错误,继续轮询
|
||||
}
|
||||
fmt.Printf("received: %s (partition=%d, offset=%d)\n",
|
||||
string(msg.Value), msg.TopicPartition.Partition, msg.TopicPartition.Offset)
|
||||
|
||||
// 处理完成后手动提交 Offset
|
||||
if _, err := c.CommitMessage(msg); err != nil {
|
||||
fmt.Printf("commit failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Kafka 生态
|
||||
|
||||
Kafka 不只是一个消息队列,而是一个完整的流处理生态:
|
||||
|
||||
| 组件 | 作用 |
|
||||
|------|------|
|
||||
| **Kafka Connect** | 数据集成框架,通过 Source/Sink Connector 在 Kafka 与外部系统(MySQL、Elasticsearch、S3 等)之间搬运数据,无需写代码 |
|
||||
| **Kafka Streams** | 客户端流处理库,直接嵌入应用中,支持窗口、Join、聚合等操作,无需独立的流处理集群 |
|
||||
| **Schema Registry** | 管理消息 Schema(Avro/Protobuf/JSON Schema),保证生产者和消费者的 Schema 兼容性,防止数据格式不匹配 |
|
||||
| **ksqlDB** | 用 SQL 语法实时查询和处理 Kafka 流数据,降低流处理门槛 |
|
||||
|
||||
### 7. KRaft 模式
|
||||
|
||||
Kafka 早期依赖 ZooKeeper 做元数据管理和 Leader 选举,但 ZooKeeper 带来了额外的运维负担(独立集群、脑裂风险、元数据瓶颈)。KRaft(Kafka Raft)模式将元数据管理内置到 Kafka 自身:
|
||||
|
||||
- Controller 节点通过 Raft 协议选举,维护元数据日志。
|
||||
- Broker 从 Controller 拉取元数据,不再依赖 ZooKeeper。
|
||||
- 启动更快、集群规模上限更高(支持百万级 Partition)、运维更简单。
|
||||
- Kafka 3.3 起 KRaft 进入生产就绪状态,4.0 版本已完全移除 ZooKeeper 支持。
|
||||
|
||||
> [!question] KRaft 用 Raft 管理元数据,那 Kafka 本身的数据副本同步为什么不用 Raft?
|
||||
> Kafka 的数据副本同步是 Leader-Follower 模型 + ISR 机制,本质是"主从异步复制 + 可配置同步确认"。Raft 更适合小数据量的元数据共识,而 Kafka 的 Partition 日志数据量巨大,ISR 模型通过 HW 机制实现了类似 Raft 的"多数派确认"语义,同时保持了高吞吐的顺序写优势。
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[01-基础概念/1-MQ-基础概念|MQ 基础概念]]
|
||||
- [[02-消息模型/3-MQ-消息模型|MQ 消息模型]]
|
||||
- [[04-存储引擎/8-MQ-存储引擎设计|MQ 存储引擎设计]]
|
||||
- [[05-可靠性保障/12-MQ-消息确认与持久化|MQ 消息确认与持久化]]
|
||||
- [[05-可靠性保障/15-MQ-顺序性保障|MQ 顺序性保障]]
|
||||
- [[06-高级特性/21-MQ-消息压缩与批处理|MQ 消息压缩与批处理]]
|
||||
- [[09-流处理与事件驱动/33-MQ-与流处理|MQ 与流处理]]
|
||||
- [[12-架构与实战/45-MQ-跨集群复制与容灾|MQ 跨集群复制与容灾]]
|
||||
- [[07-主流MQ对比/27-MQ-选型对比|MQ 选型对比]]
|
||||
@@ -0,0 +1,214 @@
|
||||
---
|
||||
tags: [MQ, RabbitMQ, 消息队列, AMQP]
|
||||
create time: 2026-05-24 19:52
|
||||
---
|
||||
|
||||
# RabbitMQ
|
||||
|
||||
## 概述
|
||||
|
||||
RabbitMQ 是基于 Erlang/OTP 实现的开源消息代理,实现了 AMQP 0-9-1 协议。它以灵活的路由能力、丰富的协议支持和成熟的插件生态著称,是企业级消息中间件的经典选择。
|
||||
|
||||
## 正文
|
||||
|
||||
### 1. 架构与 AMQP 协议
|
||||
|
||||
RabbitMQ 使用 Erlang 语言编写,天然具备高并发和软实时特性。其核心概念围绕 AMQP 0-9-1 协议展开:
|
||||
|
||||
- **Connection**:TCP 长连接,客户端与 Broker 之间的一条物理连接,包含认证和 TLS 握手。
|
||||
- **Channel**:在 Connection 上的多路复用虚拟连接。多个 Channel 共享同一个 TCP 连接,避免频繁创建/销毁 TCP 的开销。一个线程对应一个 Channel。
|
||||
- **Virtual Host(vhost)**:逻辑隔离单元,类似数据库中的 schema,不同 vhost 的 Exchange 和 Queue 完全隔离。
|
||||
- **Exchange**:接收 Producer 发来的消息,根据路由规则分发到 Queue。
|
||||
- **Queue**:消息的最终存储位置,Consumer 从 Queue 拉取或被推送消息。
|
||||
- **Binding**:连接 Exchange 和 Queue 的规则,定义路由条件。
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
Producer["Producer"] -->|"publish"| Exchange["Exchange"]
|
||||
Exchange -->|"binding rule"| Q1["Queue A"]
|
||||
Exchange -->|"binding rule"| Q2["Queue B"]
|
||||
Q1 -->|"consume"| C1["Consumer A"]
|
||||
Q2 -->|"consume"| C2["Consumer B"]
|
||||
|
||||
style Exchange fill:#4A90D9,color:#fff
|
||||
```
|
||||
|
||||
### 2. 四种 Exchange 类型
|
||||
|
||||
Exchange 的类型决定了消息如何路由到 Queue,这是 RabbitMQ 最灵活的设计:
|
||||
|
||||
**Direct Exchange**:精确匹配。消息的 Routing Key 与 Binding Key 完全一致时才路由。适合点对点定向投递。
|
||||
|
||||
**Fanout Exchange**:广播模式。忽略 Routing Key,将消息投递到所有绑定的 Queue。适合事件通知、广播场景。
|
||||
|
||||
**Topic Exchange**:通配符匹配。Routing Key 和 Binding Key 支持 `*`(匹配一个词)和 `#`(匹配零或多个词)。适合按主题分类订阅,如 `order.*.created`。
|
||||
|
||||
**Headers Exchange**:基于消息 Header 属性匹配(而非 Routing Key)。支持 `x-match=all`(所有条件匹配)和 `x-match=any`(任一条件匹配)。灵活但性能不如前三种。
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
P["Producer"] --> EX_D["Direct Exchange<br/>routing_key = order"]
|
||||
P --> EX_F["Fanout Exchange<br/>广播"]
|
||||
P --> EX_T["Topic Exchange<br/>order.*.created"]
|
||||
|
||||
EX_D -->|"rk = order"| QA["Queue A"]
|
||||
EX_F --> QA
|
||||
EX_F --> QB["Queue B"]
|
||||
EX_F --> QC["Queue C"]
|
||||
EX_T -->|"order.pay.created"| QB
|
||||
EX_T -->|"order.ship.created"| QD["Queue D"]
|
||||
|
||||
style EX_D fill:#4A90D9,color:#fff
|
||||
style EX_F fill:#7B68EE,color:#fff
|
||||
style EX_T fill:#F5A623,color:#fff
|
||||
```
|
||||
|
||||
> [!question] 生产环境中,什么时候该用 Topic Exchange 而不是 Direct Exchange?
|
||||
> 当消费者需要按模式订阅一类消息时用 Topic。比如日志系统中,`log.error.*` 可以匹配所有 error 级别的日志,而 Direct 只能精确匹配一个 routing key。如果你的路由规则是固定的、一一对应的,Direct 更简单高效。
|
||||
|
||||
### 3. 高级特性
|
||||
|
||||
**TTL(Time-To-Live)**:
|
||||
- **消息 TTL**:通过 `x-message-ttl` 设置队列级别 TTL,或在发布时通过 `expiration` 属性设置单条消息 TTL。过期消息被丢弃或进入死信队列。
|
||||
- **队列 TTL**:通过 `x-expires` 设置,队列在空闲(无消费者、无声明)超过指定时间后自动删除。适合临时队列。
|
||||
|
||||
**死信队列(DLX, Dead Letter Exchange)**:消息在以下情况会成为"死信":被消费者拒绝(reject/nack 且 requeue=false)、消息 TTL 到期、队列达到最大长度。死信会被路由到配置的 DLX 对应的队列,实现延迟重试、异常消息归档等模式。
|
||||
|
||||
**延迟队列**:RabbitMQ 原生不支持延迟消息(不像 RocketMQ 有延迟级别)。社区提供了 `rabbitmq_delayed_message_exchange` 插件,消息在 Exchange 中暂存,到期后再投递到目标队列。常用于订单超时取消、定时提醒等场景。
|
||||
|
||||
> [!question] 死信队列和延迟队列有什么关系?
|
||||
> 延迟队列的一种经典实现方式就是"利用消息 TTL + 死信队列":将消息发到一个没有消费者的队列并设置 TTL,到期后消息变成死信,被路由到 DLX 绑定的真正消费队列。但这有个缺点——队列头部消息未过期会阻塞后面的消息(因为 RabbitMQ 只检查队头)。延迟消息插件则用定时器解决这个问题。
|
||||
|
||||
### 4. 集群模式
|
||||
|
||||
RabbitMQ 支持多种集群模式,可靠性逐步递增:
|
||||
|
||||
**普通集群**:所有节点共享元数据(Exchange、Binding 等),但 Queue 的数据只存在于声明它的那个节点。其他节点收到消息后需要跨节点转发,存在单点风险。
|
||||
|
||||
**镜像队列(Mirrored Queue)**:在普通集群基础上,将 Queue 数据同步到多个节点。一个 Master + 若干 Slave,所有读写都经过 Master。缺点是:所有操作都由 Master 串行处理,性能受限于 Master 节点;同步方式是"发一份拷贝",网络开销大;Slave 只是热备,不承担读流量。**这就是镜像队列性能差的根本原因——单 Master 瓶颈。**
|
||||
|
||||
**Quorum Queue**:基于 Raft 共识协议的队列类型,是镜像队列的现代替代方案。Leader 负责接收写入,消息被复制到多数派 Follower 后才确认。Follower 可以分担读流量(`x-queue-leader-locator=balanced`),Leader 故障后自动选举新 Leader。**Quorum Queue 的改进在于:Raft 共识比简单的主从复制更可靠,Follower 可以参与读操作,且日志复制有明确的多数派确认语义。**
|
||||
|
||||
> [!question] 思考题:RabbitMQ 的镜像队列为什么性能不好?Quorum Queue 是如何改进的?
|
||||
>
|
||||
> **提示**:镜像队列的核心问题是"所有流量都走 Master"——写入要 Master 确认,读取也要 Master 响应,Slave 只是被动同步。当 Master 成为瓶颈时,加 Slave 不能提升吞吐。Quorum Queue 用 Raft 日志复制替代简单的消息拷贝,写入由多数派确认(而非 Master 独占),且支持从 Follower 读取,分散了压力。
|
||||
|
||||
### 5. RabbitMQ Stream
|
||||
|
||||
Stream 是 RabbitMQ 3.9 引入的全新队列类型,对标 Kafka 的流式存储模型:
|
||||
|
||||
- 消息以日志形式持久化,支持多次回放(不像普通队列消费即删除)。
|
||||
- 使用 offset 而非 ACK 来跟踪消费进度,支持从任意位置开始消费。
|
||||
- 性能远超传统队列,适合高吞吐的日志/事件流场景。
|
||||
- 通过 AMQP 1.0 或专用 Stream 协议访问。
|
||||
|
||||
Stream 本质上让 RabbitMQ 同时具备了"传统消息队列"和"流处理平台"两种能力。
|
||||
|
||||
### 6. 插件生态
|
||||
|
||||
RabbitMQ 的可扩展性通过插件机制实现:
|
||||
|
||||
| 插件 | 作用 |
|
||||
|------|------|
|
||||
| **Management UI** | Web 管理界面,监控队列、Exchange、连接,管理策略和权限 |
|
||||
| **Prometheus 插件** | 暴露 Prometheus 格式的监控指标,配合 Grafana 看板 |
|
||||
| **Shovel** | 单向消息搬运,将消息从一个 Broker 转发到另一个,适合跨机房同步 |
|
||||
| **Federation** | 跨集群消息联邦,支持 Exchange/Queue 级别的联邦,比 Shovel 更灵活,支持按需连接 |
|
||||
|
||||
### 7. 消息确认机制
|
||||
|
||||
RabbitMQ 提供了完整的可靠投递保证:
|
||||
|
||||
**Publisher Confirm(发布确认)**:Producer 将 Channel 设置为 confirm 模式后,Broker 成功将消息写入所有镜像(或 Quorum 多数派)后返回确认。支持同步 confirm 和异步 confirm(批量/单条)。注意区分 confirm 和事务——confirm 更轻量,性能更好。
|
||||
|
||||
**Consumer ACK(消费确认)**:消费者处理完消息后显式调用 `basicAck`,Broker 才从队列中删除消息。如果消费者崩溃未 ACK,消息会被重新投递。支持 `basicNack` / `basicReject` 拒绝消息并决定是否 requeue。
|
||||
|
||||
**Return 机制**:当消息无法路由到任何 Queue(没有匹配的 Binding),且 `mandatory=true` 时,Broker 通过 Return 回调通知 Producer。配合 `alternate-exchange` 可以将无法路由的消息转入备用 Exchange。
|
||||
|
||||
```go
|
||||
// 使用 amqp091-go 的完整发布确认 + 手动 ACK 示例
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
amqp "github.com/rabbitmq/amqp091-go"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 建立连接
|
||||
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
ch, err := conn.Channel()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer ch.Close()
|
||||
|
||||
// 声明 Quorum Queue(生产环境推荐)
|
||||
_, err = ch.QueueDeclare("order-queue", true, false, false, false, amqp.Table{
|
||||
"x-queue-type": "quorum",
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// ===== 发布确认 =====
|
||||
// 将 Channel 设置为 confirm 模式
|
||||
if err := ch.Confirm(false); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// 获取确认通知 channel
|
||||
confirms := ch.NotifyPublish(make(chan amqp.Confirmation, 1))
|
||||
|
||||
// 发布消息
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
err = ch.PublishWithContext(ctx, "", "order-queue", false, false, amqp.Publishing{
|
||||
ContentType: "application/json",
|
||||
Body: []byte(`{"order_id": "1001", "amount": 99.9}`),
|
||||
DeliveryMode: amqp.Persistent, // 持久化消息
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// 等待 Broker 确认
|
||||
conf := <-confirms
|
||||
if !conf.Ack {
|
||||
log.Fatal("message was nacked by broker")
|
||||
}
|
||||
fmt.Println("publish confirmed")
|
||||
|
||||
// ===== 消费端手动 ACK =====
|
||||
msgs, err := ch.Consume("order-queue", "", false, false, false, false, nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for msg := range msgs {
|
||||
fmt.Printf("received: %s\n", msg.Body)
|
||||
// 处理完成后手动确认,false 表示只确认当前消息
|
||||
if err := msg.Ack(false); err != nil {
|
||||
log.Printf("ack failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[03-协议与标准/5-AMQP-协议|AMQP 协议]]
|
||||
- [[04-存储引擎/11-RabbitMQ-消息存储|RabbitMQ 消息存储]]
|
||||
- [[05-可靠性保障/12-MQ-消息确认与持久化|MQ 消息确认与持久化]]
|
||||
- [[05-可靠性保障/16-MQ-死信队列与消息回溯|MQ 死信队列与消息回溯]]
|
||||
- [[06-高级特性/17-MQ-延迟消息与定时消息|MQ 延迟消息与定时消息]]
|
||||
- [[12-架构与实战/44-MQ-高可用架构|MQ 高可用架构]]
|
||||
- [[07-主流MQ对比/27-MQ-选型对比|MQ 选型对比]]
|
||||
@@ -0,0 +1,140 @@
|
||||
---
|
||||
tags: [MQ, RocketMQ, 消息队列, 分布式事务]
|
||||
create time: 2026-05-24 19:52
|
||||
---
|
||||
|
||||
# RocketMQ
|
||||
|
||||
## 概述
|
||||
|
||||
RocketMQ 是阿里巴巴开源的分布式消息中间件,最初为支撑淘宝双十一的万亿级消息流转而设计。它以高可用、高吞吐、低延迟著称,原生支持事务消息和延迟消息,在金融支付、电商交易、物流跟踪等场景中被广泛使用。本文从架构设计、核心特性、存储机制、集群部署到 5.0 新特性,系统梳理 RocketMQ 的知识体系。
|
||||
|
||||
## 正文
|
||||
|
||||
### 整体架构
|
||||
|
||||
RocketMQ 的架构由四个核心角色组成,各司其职:
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
Producer["Producer 生产者"] -->|"发送消息"| NameServer
|
||||
NameServer["NameServer 路由中心"] -->|"路由发现"| Consumer["Consumer 消费者"]
|
||||
Producer -->|"写入消息"| Broker["Broker 消息代理"]
|
||||
Consumer -->|"拉取消息"| Broker
|
||||
Broker -->|"注册路由 + 心跳"| NameServer
|
||||
|
||||
style NameServer fill:#4A90D9,color:#fff
|
||||
style Broker fill:#F5A623,color:#fff
|
||||
style Producer fill:#6EC1E0,color:#fff
|
||||
style Consumer fill:#6EC1E0,color:#fff
|
||||
```
|
||||
|
||||
- **NameServer**:无状态的路由注册中心。Broker 启动时向所有 NameServer 注册路由信息(Topic 分布、Broker 地址等),Producer 和 Consumer 从 NameServer 拉取路由表。它不做任何消息中转,节点之间互不通信,水平扩展极为简单。
|
||||
- **Broker**:消息存储和转发的核心。负责接收 Producer 发来的消息、持久化存储、响应 Consumer 的拉取请求。一个 Broker 可管理多个 Topic,每个 Topic 可设置多个 MessageQueue(类似 Kafka 的 Partition)。
|
||||
- **Producer**:消息生产者,支持同步发送、异步发送和单向发送(Oneway)。
|
||||
- **Consumer**:消息消费者,支持集群消费和广播消费两种模式。
|
||||
|
||||
> [!question] RocketMQ 的 NameServer 和 Kafka 的 ZooKeeper/KRaft 在职责上有什么本质区别?
|
||||
> NameServer 是一个纯粹的路由注册表,只存储 Broker 的元数据,不做 Leader 选举、不做分区分配。而 ZooKeeper/KRaft 在 Kafka 中还承担 Controller 选举、Partition 副本分配、ISR 管理等职责。NameServer 的无状态设计让 RocketMQ 集群运维更简单,但路由信息的一致性是"最终一致"的(依赖 Broker 心跳),而非强一致。
|
||||
|
||||
### 核心特性
|
||||
|
||||
**事务消息**是 RocketMQ 最具辨识度的能力之一。它采用"半消息 + 本地事务 + 状态回查"三阶段机制:Producer 先发送一条半消息(Half Message)到 Broker,此时消息对 Consumer 不可见;Producer 执行本地事务后,根据结果提交(Commit)或回滚(Rollback);如果 Broker 长时间未收到确认,会主动回查 Producer 的本地事务状态。这套机制天然适合分布式事务的最终一致性场景。
|
||||
|
||||
**延迟消息**支持 18 个固定延迟级别(1s/5s/10s/30s/1m/2m...2h),底层通过定时任务 + 延迟队列实现。5.0 版本后新增任意时间精度的延迟消息,通过时间轮算法支撑更灵活的定时投递需求。
|
||||
|
||||
**消息过滤**分两层:Tag 过滤在 Broker 端完成,效率高但表达能力有限;SQL92 表达式过滤支持对消息属性做复杂条件判断(如 `amount > 100 AND region = 'CN'`),由 Consumer 端的 FilterServer 执行。
|
||||
|
||||
**消息回溯**允许 Consumer 按时间戳重置消费位点,重新消费历史消息,这在数据修复和问题排查中非常实用。
|
||||
|
||||
### 存储设计
|
||||
|
||||
RocketMQ 的存储模型采用三层结构:**CommitLog**(所有消息顺序追加写入一个大文件)+ **ConsumeQueue**(按 Topic-Queue 维度的逻辑索引,固定 20 字节/条)+ **IndexFile**(按消息 Key 的哈希索引,支持按 Key 查询消息)。
|
||||
|
||||
这种"先写大文件,再异步构建索引"的设计,将随机写转化为顺序写,最大化磁盘 IO 性能。ConsumeQueue 体积小,可常驻 PageCache,消费时几乎零磁盘 IO。
|
||||
|
||||
> [详细存储原理见 [[04-存储引擎/10-RocketMQ-CommitLog|RocketMQ CommitLog]]]
|
||||
|
||||
### 集群部署模式
|
||||
|
||||
RocketMQ 支持两种主从部署模式:
|
||||
|
||||
- **普通主从模式**:Master 接收读写请求,Slave 异步或同步复制数据。同步复制(SYNC_MASTER)保证数据不丢但延迟较高,异步复制(ASYNC_MASTER)延迟低但主节点宕机可能丢少量消息。
|
||||
- **DLedger 模式**(推荐):基于 Raft 协议的自动选主模式。每个 Broker 组内 3 个节点,Leader 由 Raft 选举产生,日志通过 Raft 复制到多数节点后才提交。解决了普通主从模式下 Master 宕机需要人工介入的问题。
|
||||
|
||||
> [!question] 生产环境选同步复制还是异步复制?
|
||||
> 看业务对数据可靠性的要求。金融交易场景选 SYNC_MASTER + SYNC_FLUSH(同步复制+同步刷盘),最大化数据安全;对延迟敏感但允许极少量消息丢失的场景选 ASYNC_MASTER + ASYNC_FLUSH。
|
||||
|
||||
### 消费模式
|
||||
|
||||
RocketMQ 的消费模式有两组维度:
|
||||
|
||||
| 维度 | 模式 | 说明 |
|
||||
|------|------|------|
|
||||
| 消费方式 | 集群消费(Clustering) | 同一 ConsumerGroup 内的实例分摊消息,每条消息只被消费一次 |
|
||||
| | 广播消费(Broadcasting) | 每个 Consumer 实例都收到全量消息,适用于本地缓存刷新等场景 |
|
||||
| 并发策略 | 并发消费 | 多线程同时消费,吞吐高但不保证顺序 |
|
||||
| | 顺序消费 | 同一 MessageQueue 内的消息严格按顺序消费,通过 MessageListenerOrderly 实现 |
|
||||
|
||||
### RocketMQ 5.0 新特性
|
||||
|
||||
RocketMQ 5.0 带来了几项重要演进:
|
||||
|
||||
- **gRPC 协议**:替代自定义 RemotingCommand 协议,使用标准 gRPC 通信,多语言客户端支持更友好。
|
||||
- **Pop 消费模式**:消费者不再需要维护 MessageQueue 的本地锁,由 Broker 端分配消息,更适应弹性伸缩和 Serverless 场景。
|
||||
- **逻辑队列**:将物理 MessageQueue 抽象为逻辑队列,支持更灵活的队列映射和负载均衡策略。
|
||||
|
||||
### Go 代码示例
|
||||
|
||||
使用 `rocketmq-client-go` 发送和消费消息:
|
||||
|
||||
```go
|
||||
// Producer: 同步发送消息
|
||||
p, _ := rocketmq.NewProducer(
|
||||
producer.WithNameServer([]string{"127.0.0.1:9876"}),
|
||||
producer.WithGroupName("my-producer-group"),
|
||||
)
|
||||
p.Start()
|
||||
defer p.Shutdown()
|
||||
|
||||
msg := rocketmq.NewMessage("OrderTopic", []byte(`{"orderId":"1001","amount":99.9}`))
|
||||
msg.WithTag("order-create") // Tag 过滤标签
|
||||
result, err := p.SendSync(context.Background(), msg)
|
||||
fmt.Println("发送结果:", result.MessageID)
|
||||
```
|
||||
|
||||
```go
|
||||
// Consumer: 集群消费模式
|
||||
c, _ := rocketmq.NewPushConsumer(
|
||||
consumer.WithNameServer([]string{"127.0.0.1:9876"}),
|
||||
consumer.WithGroupName("my-consumer-group"),
|
||||
consumer.WithConsumeFromWhere(consumer.ConsumeFromLastOffset), // 从最新位点开始
|
||||
)
|
||||
c.Subscribe("OrderTopic", consumer.MessageSelector{
|
||||
Type: consumer.TAG,
|
||||
Expression: "order-create", // 只消费 Tag 为 order-create 的消息
|
||||
}, func(ctx context.Context, msgs ...*ext.Message) (consumer.ConsumeResult, error) {
|
||||
for _, msg := range msgs {
|
||||
fmt.Println("收到消息:", string(msg.Body))
|
||||
}
|
||||
return consumer.ConsumeSuccess, nil
|
||||
})
|
||||
c.Start()
|
||||
defer c.Shutdown()
|
||||
// 阻塞等待消费
|
||||
select {}
|
||||
```
|
||||
|
||||
代码解析:Producer 通过 `SendSync` 同步发送消息到 `OrderTopic`,并通过 `WithTag` 设置 Tag。Consumer 通过 `Subscribe` 订阅同一 Topic,使用 Tag 表达式做过滤,消费成功返回 `ConsumeSuccess`。`ConsumeFromLastOffset` 表示新加入的消费者从最新消息开始消费,避免历史消息堆积。
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[04-存储引擎/8-MQ-存储引擎设计|MQ 存储引擎设计]]
|
||||
- [[04-存储引擎/10-RocketMQ-CommitLog|RocketMQ CommitLog]]
|
||||
- [[06-高级特性/18-MQ-事务消息|MQ 事务消息]]
|
||||
- [[06-高级特性/17-MQ-延迟消息与定时消息|MQ 延迟消息与定时消息]]
|
||||
- [[06-高级特性/19-MQ-消息过滤与路由|MQ 消息过滤与路由]]
|
||||
- [[05-可靠性保障/15-MQ-顺序性保障|MQ 顺序性保障]]
|
||||
- [[12-架构与实战/44-MQ-高可用架构|MQ 高可用架构]]
|
||||
- [[12-架构与实战/45-MQ-跨集群复制与容灾|MQ 跨集群复制与容灾]]
|
||||
- [[07-主流MQ对比/27-MQ-选型对比|MQ 选型对比]]
|
||||
@@ -0,0 +1,153 @@
|
||||
---
|
||||
tags: [MQ, Pulsar, 消息队列, 计算存储分离]
|
||||
create time: 2026-05-24 19:52
|
||||
---
|
||||
|
||||
# Apache Pulsar
|
||||
|
||||
## 概述
|
||||
|
||||
Apache Pulsar 是 Yahoo 于 2016 年开源、后捐赠给 Apache 基金会的云原生消息流平台。它最显著的设计特点是**计算存储分离**——Broker 只负责消息路由和计算,数据持久化交给专门的 BookKeeper 存储层。这种架构让 Pulsar 在弹性扩缩容、多租户支持和分层存储方面天然优于传统 MQ。本文深入解析 Pulsar 的架构设计、存储模型和独特能力。
|
||||
|
||||
## 正文
|
||||
|
||||
### 设计哲学:计算存储分离
|
||||
|
||||
传统 MQ(如 Kafka、RocketMQ)将计算和存储绑定在同一节点上。一个 Broker 既负责消息路由,又管理本地磁盘上的日志文件。这带来一个实际问题:扩容时新节点需要从其他节点迁移数据(Rebalance),数据量越大迁移越慢,可能需要数小时甚至数天。
|
||||
|
||||
Pulsar 的回答是:**把计算和存储拆开**。Broker 是无状态的,可以秒级扩缩容;数据存储在 BookKeeper 集群中,新 Broker 加入后直接开始服务,无需数据迁移。存储层也可以独立扩展——磁盘不够了加 BookKeeper 节点,流量大了加 Broker 节点,两者互不影响。
|
||||
|
||||
> [!question] Pulsar 的计算存储分离在扩缩容时比 Kafka 快,具体快在哪里?
|
||||
> Kafka 扩容 Broker 时,需要把部分 Partition 的数据从旧节点复制到新节点(Partition Rebalance),数据量可达 TB 级,耗时很长。Pulsar 扩容 Broker 时,新 Broker 直接从 BookKeeper 读取数据,无需数据迁移。扩容 BookKeeper 时,新节点只接收新写入的 Ledger,存量数据不会迁移。整个过程是"加法"而非"搬运"。
|
||||
|
||||
### 架构详解
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
Producer["Producer"] -->|"发送消息"| Broker1["Broker 1"]
|
||||
Producer -->|"发送消息"| Broker2["Broker 2"]
|
||||
Consumer["Consumer"] -->|"拉取消息"| Broker1
|
||||
Consumer -->|"拉取消息"| Broker2
|
||||
|
||||
Broker1 -->|"写入数据"| BK1["Bookie 1"]
|
||||
Broker1 -->|"写入数据"| BK2["Bookie 2"]
|
||||
Broker1 -->|"写入数据"| BK3["Bookie 3"]
|
||||
Broker2 -->|"写入数据"| BK1
|
||||
Broker2 -->|"写入数据"| BK2
|
||||
Broker2 -->|"写入数据"| BK3
|
||||
|
||||
Broker1 -->|"元数据"| ZK["ZooKeeper"]
|
||||
Broker2 -->|"元数据"| ZK
|
||||
BK1 -->|"元数据"| ZK
|
||||
BK2 -->|"元数据"| ZK
|
||||
BK3 -->|"元数据"| ZK
|
||||
|
||||
style Broker1 fill:#F5A623,color:#fff
|
||||
style Broker2 fill:#F5A623,color:#fff
|
||||
style BK1 fill:#4A90D9,color:#fff
|
||||
style BK2 fill:#4A90D9,color:#fff
|
||||
style BK3 fill:#4A90D9,color:#fff
|
||||
style ZK fill:#6EC1E0,color:#fff
|
||||
style Producer fill:#999,color:#fff
|
||||
style Consumer fill:#999,color:#fff
|
||||
```
|
||||
|
||||
三层架构各司其职:
|
||||
|
||||
- **Broker(计算层)**:无状态,负责消息路由、协议处理、消息分发。Topic 的所有权可以在 Broker 之间快速转移(Ownership Transfer),一旦某个 Broker 宕机,它持有的 Topic 会被秒级重新分配给其他 Broker。
|
||||
- **BookKeeper / Bookie(存储层)**:分布式日志存储系统。每个 Bookie 节点独立管理本地磁盘上的数据片段(Ledger Segment)。数据写入时,Broker 选择多个 Bookie 并行写入,通过 Quorum 机制保证持久性。
|
||||
- **ZooKeeper(元数据层)**:管理 Broker 和 Bookie 的集群成员信息、Topic 元数据、Cursor(消费位点)等。
|
||||
|
||||
### 存储模型:Segment 分片
|
||||
|
||||
Pulsar 的存储单元是 **Ledger**,每个 Ledger 是一个只追加的日志文件。一个 Topic 的数据被切分为多个 Ledger,每个 Ledger 的数据段(Segment)分散存储在不同的 Bookie 上。
|
||||
|
||||
这种 Segment 级别的分片带来了几个好处:数据天然分散在多台机器上,单个 Topic 不会成为热点;新写入的数据分配到新的 Segment,不涉及旧数据迁移;旧 Ledger 可以被独立清理或下沉到冷存储。
|
||||
|
||||
### 多租户
|
||||
|
||||
Pulsar 原生支持多租户,采用三级命名空间:
|
||||
|
||||
```
|
||||
Tenant(租户) → Namespace(命名空间) → Topic(主题)
|
||||
```
|
||||
|
||||
每个 Tenant 是一个逻辑隔离单元,有独立的认证和授权策略。Namespace 是策略管理的最小单元(消息 TTL、保留策略、卸载策略都在 Namespace 级别配置)。同一个 Namespace 下的 Topic 共享配置。这种层级结构天然适合 SaaS 平台按租户隔离消息流量。
|
||||
|
||||
### Pulsar 特有功能
|
||||
|
||||
**分层存储(Tiered Storage)**:Pulsar 支持将过期的 Ledger 段自动下沉到廉价对象存储(如 S3、GCS、HDFS)。消息仍然可以通过 API 读取,但存储成本大幅降低。这对需要长期保留消息(如审计日志、事件溯源)的场景非常有吸引力——热数据在 BookKeeper SSD 上,冷数据在 S3 上,对应用完全透明。
|
||||
|
||||
**Pulsar Functions**:轻量级的流处理计算框架。用几行代码(Java/Python/Go)编写处理逻辑,提交到 Pulsar 集群后自动运行,无需部署 Flink/Spark 集群。适合简单的消息转换、过滤、路由等场景。
|
||||
|
||||
**Pulsar IO(Connector)**:预构建的数据连接器生态,包括 Kafka Source/Sink、Elasticsearch Sink、JDBC Sink、文件系统 Sink 等。将 Pulsar 与外部系统打通,降低集成成本。
|
||||
|
||||
### Pulsar vs Kafka 架构对比
|
||||
|
||||
| 维度 | Pulsar | Kafka |
|
||||
|------|--------|-------|
|
||||
| 存储模型 | 计算存储分离,Segment 存储在 BookKeeper | 计算存储耦合,Partition Log 本地磁盘 |
|
||||
| 扩展性 | Broker 和 Bookie 独立扩展,秒级加入 | 扩容需 Partition Rebalance,数据量大时耗时长 |
|
||||
| 多租户 | 原生支持 Tenant → Namespace → Topic | 无原生支持,需通过 ACL + Topic 命名约定模拟 |
|
||||
| 运维复杂度 | 需维护 Broker + BookKeeper + ZooKeeper 三套组件 | 只需 Broker 集群(KRaft 模式可去掉 ZK) |
|
||||
| 消息保留 | 天然支持,分层存储可下沉 S3 | 依赖 Log Compaction 和 retention 配置 |
|
||||
| 协议兼容 | 原生协议 + Kafka Protocol Handler | Kafka 自有协议 |
|
||||
| 生态成熟度 | 成长期,社区活跃但不如 Kafka 庞大 | 非常成熟,几乎所有大数据组件都有 Kafka 集成 |
|
||||
|
||||
> [!question] 运维复杂度更高是不是 Pulsar 的致命弱点?
|
||||
> 确实,Pulsar 需要维护三套组件,初期学习和运维成本更高。但随着 Kubernetes Operator(如 StreamNative 的 pulsar-helm-chart)的成熟,部署复杂度已大幅降低。而且计算存储分离带来的弹性优势在大规模场景下会反过来降低运维负担——扩缩容不再需要数小时的数据迁移。技术选型没有银弹,关键看规模和团队能力。
|
||||
|
||||
### Go 代码示例
|
||||
|
||||
使用 `pulsar-client-go` 实现消息的发送和消费:
|
||||
|
||||
```go
|
||||
// Producer: 发送消息
|
||||
client, _ := pulsar.NewClient(pulsar.ClientOptions{
|
||||
URL: "pulsar://localhost:6650",
|
||||
})
|
||||
defer client.Close()
|
||||
|
||||
producer, _ := client.CreateProducer(pulsar.ProducerOptions{
|
||||
Topic: "my-topic",
|
||||
})
|
||||
defer producer.Close()
|
||||
|
||||
_, err := producer.Send(context.Background(), &pulsar.ProducerMessage{
|
||||
Payload: []byte(`{"event":"user-signup","userId":"u1001"}`),
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Println("发送失败:", err)
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// Consumer: 订阅消费
|
||||
consumer, _ := client.Subscribe(pulsar.ConsumerOptions{
|
||||
Topic: "my-topic",
|
||||
SubscriptionName: "my-sub", // 订阅名称,用于持久化消费位点
|
||||
Type: pulsar.Shared, // 共享模式,多消费者轮询
|
||||
})
|
||||
defer consumer.Close()
|
||||
|
||||
for {
|
||||
msg, err := consumer.Receive(context.Background())
|
||||
if err != nil {
|
||||
fmt.Println("接收失败:", err)
|
||||
continue
|
||||
}
|
||||
fmt.Println("收到消息:", string(msg.Payload()))
|
||||
consumer.Ack(msg) // 确认消费成功
|
||||
}
|
||||
```
|
||||
|
||||
代码解析:Producer 通过 `Send` 同步发送消息到 `my-topic`。Consumer 通过 `Subscribe` 创建订阅,`SubscriptionName` 是 Pulsar 持久化消费位点的关键标识——即使 Consumer 下线重连,也能从上次的位置继续消费。`pulsar.Shared` 模式允许多个 Consumer 分摊消息,类似 RocketMQ 的集群消费。
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[07-主流MQ对比/22-Kafka|Kafka]]
|
||||
- [[07-主流MQ对比/24-RocketMQ|RocketMQ]]
|
||||
- [[04-存储引擎/8-MQ-存储引擎设计|MQ 存储引擎设计]]
|
||||
- [[12-架构与实战/44-MQ-高可用架构|MQ 高可用架构]]
|
||||
- [[07-主流MQ对比/27-MQ-选型对比|MQ 选型对比]]
|
||||
- [[10-监控与运维/39-MQ-容器化与-K8s-部署|MQ 容器化与 K8s 部署]]
|
||||
@@ -0,0 +1,173 @@
|
||||
---
|
||||
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 与微服务]]
|
||||
@@ -0,0 +1,117 @@
|
||||
---
|
||||
tags: [MQ, 消息队列, 选型, 技术对比]
|
||||
create time: 2026-05-24 19:52
|
||||
---
|
||||
|
||||
# MQ 选型对比
|
||||
|
||||
## 概述
|
||||
|
||||
消息队列选型没有"银弹",只有最匹配业务场景的选择。本文从吞吐量、延迟、可靠性、消息模型、运维复杂度、社区生态、云原生支持七个维度,横向对比 Kafka、RabbitMQ、RocketMQ、Pulsar 和 NATS,并给出场景化选型建议和常见误区。
|
||||
|
||||
## 正文
|
||||
|
||||
### 选型维度说明
|
||||
|
||||
选型不是看"谁最强",而是看"谁最适合"。以下是七个核心维度:
|
||||
|
||||
- **吞吐量**:单位时间能处理多少消息,决定系统上限。
|
||||
- **延迟**:消息从生产到被消费的端到端时间。
|
||||
- **可靠性**:消息不丢、不重的能力,以及故障恢复速度。
|
||||
- **消息模型**:支持的消息模式(队列、发布订阅、分区有序等)。
|
||||
- **运维复杂度**:部署、扩缩容、升级、故障排查的难度。
|
||||
- **社区生态**:客户端语言支持、插件、文档、社区活跃度。
|
||||
- **云原生支持**:K8s Operator、Helm Chart、弹性伸缩能力。
|
||||
|
||||
> [!question]
|
||||
> 你们当前的系统最在意哪个维度?是吞吐量优先,还是延迟敏感?还是运维团队人力有限,需要低运维成本?
|
||||
|
||||
### 核心对比表
|
||||
|
||||
| 维度 | Kafka | RabbitMQ | RocketMQ | Pulsar | NATS |
|
||||
|------|-------|----------|----------|--------|------|
|
||||
| **吞吐量** | 极高(百万级/s) | 中等(万级/s) | 高(十万级/s) | 极高(百万级/s) | 极高(千万级/s,轻量消息) |
|
||||
| **延迟** | 毫秒~十毫秒 | 微秒~毫秒 | 毫秒级 | 毫秒级 | 微秒级 |
|
||||
| **可靠性** | 高(ISR + 副本) | 高(镜像队列) | 高(同步双写) | 高(BookKeeper) | 中(JetStream 增强) |
|
||||
| **消息模型** | 发布订阅 + 分区有序 | 队列 + 交换机路由 | 队列 + 发布订阅 + 事务 | 发布订阅 + 分区 + 多租户 | 发布订阅 + 队列组 |
|
||||
| **运维复杂度** | 中高(ZK/KRaft) | 低 | 中(NameServer) | 高(Broker + BookKeeper) | 极低 |
|
||||
| **社区生态** | 极丰富(Confluent 生态) | 极丰富(插件机制) | 丰富(阿里主导) | 快速成长 | 活跃(CNCF) |
|
||||
| **云原生支持** | 中(Strimzi Operator) | 中(RabbitMQ Operator) | 中(RocketMQ Operator) | 原生支持(计算存储分离) | 原生支持(极轻量) |
|
||||
|
||||
### 场景化选型建议
|
||||
|
||||
#### 日志收集 / 大数据管道 → Kafka
|
||||
|
||||
Kafka 是大数据生态的事实标准。配合 Kafka Connect、Kafka Streams、Schema Registry,可以构建完整的数据管道。如果你的下游是 Flink、Spark、ClickHouse,Kafka 几乎是唯一选择。
|
||||
|
||||
#### 企业级消息中间件 → RabbitMQ
|
||||
|
||||
RabbitMQ 的 AMQP 协议和灵活的 Exchange 路由(Direct、Fanout、Topic、Headers)让它成为企业应用集成的首选。对 Java/C#/.NET 生态友好,插件丰富,上手门槛低。
|
||||
|
||||
#### 电商 / 金融事务消息 → RocketMQ
|
||||
|
||||
RocketMQ 原生支持事务消息、延迟消息、消息回溯,在阿里巴巴双十一经历过极端流量验证。如果你的场景涉及分布式事务或严格的顺序消费,RocketMQ 是首选。
|
||||
|
||||
#### 多租户 / 云原生 → Pulsar
|
||||
|
||||
Pulsar 的计算存储分离架构(Broker 无状态 + BookKeeper 持久化)天然适合云原生部署。原生多租户支持(Tenant / Namespace)让平台团队可以为不同业务线隔离资源。
|
||||
|
||||
#### 微服务轻量通信 → NATS
|
||||
|
||||
NATS 的核心优势是极致轻量——单个二进制文件、无外部依赖、微秒级延迟。NATS JetStream 提供了持久化和消费确认,适合微服务间的事件通知和命令分发。
|
||||
|
||||
> [!question]
|
||||
> 如果你的团队同时有日志收集和微服务通信两种需求,应该选一个 MQ 统一方案,还是两个 MQ 各司其职?
|
||||
|
||||
### 选型决策树
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
Start["开始选型"] --> Q1{"需要大数据生态集成?"}
|
||||
Q1 -->|"是"| Kafka["选择 Kafka"]
|
||||
Q1 -->|"否"| Q2{"需要事务消息或延迟消息?"}
|
||||
Q2 -->|"是"| RocketMQ["选择 RocketMQ"]
|
||||
Q2 -->|"否"| Q3{"需要灵活路由和协议兼容?"}
|
||||
Q3 -->|"是"| RabbitMQ["选择 RabbitMQ"]
|
||||
Q3 -->|"否"| Q4{"需要多租户和云原生?"}
|
||||
Q4 -->|"是"| Pulsar["选择 Pulsar"]
|
||||
Q4 -->|"否"| Q5{"需要极致轻量和低延迟?"}
|
||||
Q5 -->|"是"| NATS["选择 NATS"]
|
||||
Q5 -->|"否"| Q6{"团队技术栈?"}
|
||||
Q6 -->|"Java 为主"| RocketMQ2["考虑 RocketMQ"]
|
||||
Q6 -->|"Go / 云原生"| NATS2["考虑 NATS"]
|
||||
Q6 -->|"混合栈"| Kafka2["考虑 Kafka"]
|
||||
|
||||
style Start fill:#4A90D9,color:#fff
|
||||
style Kafka fill:#D0021B,color:#fff
|
||||
style RocketMQ fill:#D0021B,color:#fff
|
||||
style RabbitMQ fill:#D0021B,color:#fff
|
||||
style Pulsar fill:#D0021B,color:#fff
|
||||
style NATS fill:#D0021B,color:#fff
|
||||
```
|
||||
|
||||
### 常见选型误区
|
||||
|
||||
**误区一:盲目追求高吞吐**
|
||||
|
||||
Kafka 吞吐确实高,但如果你的业务日均消息量只有几十万,RabbitMQ 完全够用,还省去了运维 Kafka 集群的成本。高吞吐的代价是更高的运维复杂度和资源消耗。
|
||||
|
||||
**误区二:忽视运维成本**
|
||||
|
||||
Pulsar 架构先进,但 Broker + BookKeeper + ZooKeeper 的组件数量意味着更多的运维负担。如果团队只有 2-3 个后端开发,选 NATS 或 RabbitMQ 可能更务实。
|
||||
|
||||
**误区三:忽略团队技术栈**
|
||||
|
||||
团队全是 Java 开发,选 RocketMQ 的学习成本最低;团队主力是 Go,NATS 的 Go 客户端体验最好。技术选型要尊重团队现状,而不是追逐"最优解"。
|
||||
|
||||
> [!question]
|
||||
> 你们团队的技术栈是 Java,但业务需要高吞吐日志收集,应该选 RabbitMQ 还是 Kafka?为什么?
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[07-主流MQ对比/22-Kafka|Kafka]]
|
||||
- [[07-主流MQ对比/23-RabbitMQ|RabbitMQ]]
|
||||
- [[07-主流MQ对比/24-RocketMQ|RocketMQ]]
|
||||
- [[07-主流MQ对比/25-Apache-Pulsar|Apache Pulsar]]
|
||||
- [[07-主流MQ对比/26-NATS-NSQ-Redis-Streams|NATS / NSQ / Redis Streams]]
|
||||
- [[01-基础概念/2-MQ-适用场景与选型原则|MQ 适用场景与选型原则]]
|
||||
Reference in New Issue
Block a user