Files
cs-note/hhs/MQ/07-主流MQ对比/25-Apache-Pulsar.md
T
2026-05-24 20:51:06 +08:00

154 lines
8.3 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, 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 部署]]