【Pulsar学习笔记】架构初识
本篇主要介绍下Pulsar的分层架构及其优势
Pulsar 是基于云原生基础架构设计的一款消息队列,是Apache 软件基金会顶级项目。拥有诸多云原生应用特性,如无状态计算层、计算与存储分离,可以很好地利用云的弹性(伸缩能力),保证具有足够的可扩容性和容错性,能够很好地在容器化环境中运行。
一、架构概述
在一个Pulsar集群中:
单个Pulsar集群由以下三部分组成:
Broker
:无状态服务层,负责接收和传递消息,集群负载均衡等工作,Broker 不会持久化保存元数据,因此可以快速的上、下线。BookKeeper
:有状态持久层,由一组名为 Bookie 的存储节点组成,持久化地存储消息。zookeeper
集群,用来处理Pulsar集群之间的协调任务,并存储broker和bookie的元数据。
与传统的消息系统相比,Apache Pulsar 在架构设计上采用了计算与存储分离的模式,Pub/Sub 相关的计算逻辑在 Broker 上完成,数据存储在 Apache BookKeeper 的 Bookie 节点上。
1.1 Brokers
Pulsar的broker是一个无状态组件, 主要由两个组件组成:
- 一个HTTP服务器
service discovery
,提供REST API用于管理任务和生产者、消费者的主题查找。生产者连接到代理以发布消息,消费者连接到代理以消费消息。 - 一个调度程序
dispatcher
,它是一个基于自定义二进制协议的异步TCP服务器,用于所有数据传输。
消息通常从Managed Ledger
缓存中读取,除非读取的数据超过缓存大小,则从BookKeeper那里读取Entries
(Entry是BookKeeper中的一条记录)
为了支持全局Topic异地复制,Broker会控制Replicators追踪本地发布的Entries,并把这些Entries用Java客户端重新发布到其他区域.
1.2 Zookeeper
Pulsar使用Apache ZooKeeper
来进行元数据存储、集群配置和协调。元数据包括主题元数据、模式(schema)、代理负载数据等。Pulsar元数据存储可以部署在单独的ZooKeeper集群上,也可以部署在现有的ZooKeeper集群上。
Pulsar还支持其他元数据后端服务,包括etcd和RocksDB(仅适用于独立部署的Pulsar)。
在一个Pulsar实例中:
- 配置存储: 存储租户,命名域和其他需要全局一致的配置项
- 每个集群有自己独立的ZooKeeper保存集群内部配置和协调信息,例如归属信息、broker负载报告、BookKeeper ledger信息
1.3 Bookeeper
Pulsar使用一个名为Apache BookKeeper的系统来进行持久消息存储。BookKeeper是一个分布式的预写日志(Write-Ahead Log,WAL)系统,为Pulsar提供了一些重要的优势:
- 独立的分布式日志文件,称为账本(
ledgers
)。可以随着时间的推移为主题创建多个账本。 - 非常高效的有序存储,在各种系统故障的情况下,可以处理
entry
(条目)数据复制。 - 读取一致性。
- 均匀分配I/O。
- 水平可扩展性。通过向集群添加更多的书记(bookie),可以立即增加容量。
- Bookies被设计成可以承载数千的并发读写的ledgers,且读写IO分离。 使用多个磁盘设备(一个用于日志,另一个用于一般存储)),这样Bookies可以将读操作的影响和对于写操作的延迟分隔开。
- 除了消息数据,游标(Cursors)也被持久地存储在BookKeeper中。游标是消费者的订阅位置。BookKeeper使得Pulsar能够以可扩展的方式存储消费者的位置。
1.3.1 ledgers
账本(Ledger)是一种只追加的数据结构,单进程写入,分配给多个BookKeeper存储节点(也称为bookie)。Ledger entries
被复制到多个bookie上。Ledger本身具有非常简单的语义:
- Pulsar代理可以创建Ledger,向账本追加entry,并关闭账本。
- 在Ledger关闭后(无论是显式关闭还是因写入进程崩溃),只能以只读模式打开Ledger。
- 当ledger中的Entry不再有用的时候,可以将整个账本从系统中删除(跨所有bookie)。
1.3.2 Managed ledgers
由于BookKeeper ledgers 提供了统一的日志抽象,因此在ledgers之上开发了一个名为managed ledger
的库,它代表单个主题的存储层。一个managed ledger代表了一个消息流的抽象,其中包含一个单一的写入者在流的末尾追加消息,以及多个消费者游标,每个游标都有其关联的位置。
在内部,单个managed ledger使用多个ledgers来存储数据。有两个原因使用多个账本:
- 在发生故障后,一个ledger将无法继续写入,需要创建一个新的ledger。
- 当所有的游标都消费完ledger中的消息时,可以删除ledger。这允许定期对ledger进行切换。
1.3.3 Journal storage
在BookKeeper中,journal
文件包含了BookKeeper的事务日志。在对ledger进行更新之前,bookie需要确保描述该更新的事务被写入持久(非易失性)存储中。一旦bookie启动或者旧的日志文件达到了日志文件大小阈值(通过journalMaxSizeMB
参数进行配置),就会创建一个新的journal
文件。
1.4 Pulsar Proxy
Pulsar客户端和Pulsar集群交互的一种方式就是直连Pulsar brokers。有时这种直连既不可行也不可取,因为客户端并不知道broker的地址
Pulsar proxy为所有的broker提供了一个网关, 如果选择运行了Pulsar Proxy,所有的客户都会通过这个代理而不是直接与brokers通信
二、架构优势
2.1 分片存储
除了存储、计算解耦分离的设计之外,Apache Pulsar 在存储设计上也不同于传统 MQ 的分区数据本地存储的模式,采用的是分片存储的模式,存储粒度比分区更细化、存储负载更均衡。
Apache Pulsar 中的每个 Topic 分区本质上都是存储在 Apache BookKeeper 中的分布式日志。Topic 可以有多个分区,分区数据持久化时,分区是逻辑上的概念,实际存储的单位是分片(Segment)的,如图,一个分区 Topic1-Part2 的数据由多个 Segment 组成, 每个 Segment 作为 Apache BookKeeper 中的一个 Ledger,均匀分布并存储在 Apache BookKeeper 群集中的多个 Bookie 节点中, 每个 Segment 具有 3 个副本。
那么它与传统的紧耦合分区架构而言,将带来一个很大的优势就是,超高的可扩展性以及故障容错性。
2.2 高可用及横向拓展
由于消息服务层和持久存储层是分开的,因此 Apache Pulsar 可以独立地扩展存储层和服务层。
2.2.1 可扩展性
Broker 扩展
在 Pulsar 中 Broker 是无状态的,可以通过增加节点的方式实现快速扩容。当需要支持更多的消费者或生产者时,可以简单地添加更多的 Broker 节点来满足业务需求。Pulsar 支持自动的分区负载均衡,在 Broker 节点的资源使用率达到阈值时,会将负载迁移到负载较低的 Broker 节点,这个过程中分区也将在多个 Broker 节点中做平衡迁移,一些分区的所有权会转移到新的 Broker 节点。
Bookie 扩展
存储层的扩容,通过增加 Bookie 节点来实现。通过资源感知和数据放置策略,流量将自动切换到新的 Bookie 节点中,整个过程不会涉及到不必要的数据搬迁,即不需要将旧数据从现有存储节点重新复制到新存储节点。
如图所示,起始状态有四个存储节点,Bookie1, Bookie2, Bookie3, Bookie4,以 Topic1-Part2 为例,当这个分区的最新的存储分片是 SegmentX 时,对存储层扩容,添加了新的 Bookie 节点,BookieX,BookieY,那么在存储分片滚动之后,新生成的存储分片, SegmentX+1,SegmentX+2,会优先选择新的 Bookie 节点(BookieX,BookieY)来保存数据。
2.2.2 容错性
得益于计算与存储分离以及分片存储的设计,Pulsar 可以实现独立、灵活的容错。
Broker 容错
当 Broker 节点失败时, 以图为例,当存储分片滚动到 SegmentX 时,Broker2 节点失败,此时生产者和消费者向其他的 Broker 发起请求,这个过程会触发分区的所有权转移,即将 Broker2 拥有的分区 Topic1-Part2 的所有权转移到其他的 Broker (Broker3)。在 Apache Pulsar 中数据存储和数据服务分离,所以新 Broker 接管分区的所有权时,它不需要复制 Partiton 的数据。新的分区 Owner(Broker3)会产生一个新的分片 SegmentX+1, 如果有新数据到来,会存储在新的分片 Segment x+1 上,不会影响分区的可用性。
Bookie 容错
当 Bookie 节点失败时,如图所示, 假设 Bookie 2 上的 Segment 4 损坏。Apache BookKeeper Auditor 会检测到这个错误并进行复制修复。Apache BookKeeper 中的副本修复是 Segment 级别的多对多快速修复,BookKeeper 可以从 Bookie 3 和 Bookie 4 读取 Segment 4 中的消息,并在 Bookie 1 处修复 Segment 4。如果是 Bookie 节点故障,这个 Bookie 节点上所有的 Segment 会按照上述方式复制到其他的 Bookie 节点。所有的副本修复都在后台进行,对 Broker 和应用透明,Broker 会产生新的 Segment 来处理写入请求,不会影响分区的可用性。
分片存储解决了分区容量受单节点存储空间限制的问题,当容量不够时,可以通过扩容 Bookie 节点的方式支撑更多的分区数据,也解决了分区数据倾斜问题,数据可以均匀的分配在 Bookie 节点上。Broker 和 Bookie 灵活的容错以及无缝的扩容能力让 Apache Pulsar 具备非常高的可用性。
2.3 读写分离
Pulsar 另外一个有吸引力的特性是提供了读写分离的能力,读写分离保证了在有大量滞后消费(磁盘 IO 会增加)时,不会影响服务的正常运行,尤其是不会影响到数据的写入。读写分离的能力由 Apache BookKeeper 提供,简单说一下 Bookie 存储涉及到的概念:
- Journals:Journal 文件包含了 BookKeeper 事务日志,在 Ledger 更新之前,Journal 保证描述更新的事务写入到 Non-volatile 的存储介质上。
- Entry logs:Entry 日志文件管理写入的 Entry,来自不同 ledger 的 entry 会被聚合然后顺序写入。
- Index files:每个 Ledger 都有一个对应的索引文件,记录数据在 Entry 日志文件中的 Offset 信息。
Entry 的读写入过程如图所示,
数据的写入流程:
- 数据首先会写入 Journal,写入 Journal 的数据会实时落到磁盘。
- 然后,数据写入到 Memtable ,Memtable 是读写缓存。
- 写入 Memtable 之后,对写入请求进行响应。
- Memtable 写满之后,会 Flush 到 Entry Logger 和 Index cache,Entry Logger 中保存了数据,Index cache 保存了数据的索引信息,然后由后台线程将 Entry Logger 和 Index cache 数据落到磁盘。
数据的读取流程:
- 如果是 Tailing read 请求,直接从 Memtable 中读取 Entry。
- 如果是 Catch-up read(滞后消费)请求,先读取 Index 信息,然后索引从 Entry Logger 文件读取 Entry。
一般在进行 Bookie 的配置时,会将 Journal 和 Ledger 存储磁盘进行隔离,减少 Ledger 对于 Journal 写入的影响,并且推荐 Journal 使用性能较好的 SSD 磁盘,读写分离主要体现在:
- 写入 Entry 时,Journal 中的数据需要实时写到磁盘,Ledger 的数据不需要实时落盘,通过后台线程批量落盘,因此写入的性能主要受到 Journal 磁盘的影响。
- 读取 Entry 时,首先从 Memtable 读取,命中则返回;如果不命中,再从 Ledger 磁盘中读取,所以对于 Catch-up read 的场景,读取数据会影响 Ledger 磁盘的 IO,对 Journal 磁盘没有影响,也就不会影响到数据的写入。
所以,数据写入是主要是受 Journal 磁盘的负载影响,不会受 Ledger 磁盘的影响。另外,Segment 存储的多个副本都可以提供读取服务,相比于主从副本的设计,Apache Pulsar 可以提供更好的数据读取能力。通过以上分析,Apache Pulsar 使用 Apache BookKeeper 作为数据存储,可以带来下列的收益:
- 支持将多个 Ledger 的数据写入到同一个 Entry logger 文件,可以避免分区膨胀带来的性能下降问题。
- 支持读写分离,可以在滞后消费场景导致磁盘 IO 上升时,保证数据写入的不受影响。
- 支持全副本读取,可以充分利用存储副本的数据读取能力。
思考
对比kafka的零拷贝机制,pulsar的IO隔离,可以规避哪些问题?
转载自:https://juejin.cn/post/7249925306357973047