查看原文
其他

译文|LogDevice 与 Apache Pulsar 之间的对比

Ivan Kelly ApachePulsar 2021-10-18

本文为《Comparing LogDevice and Apache Pulsar》中文翻译版本。 
原文链接: https://www.splunk.com/en_us/blog/it/comparing-logdevice-and-apache-pulsar.html 

阅读本文需要大约 8 分钟。

Facebook 已经发布开源 LogDevice[1]。考虑到 LogDevice 目标用例之间的相似性,自然会有人问到 LogDevice 与 Apache Pulsar 之间是否也有相似之处。本文将对这一问题进行解答。对比 LogDevice 和 Pulsar 并不简单,LogDevice 的操作级别要比 Pulsar 低。LogDevice 与 Twitter 的 DistributedLog 更相似。二者都只关注日志原语,而不关注 schema 管理、多租户、光标管理等高级功能。这些高级功能将留给用户基于 LogDevice 去实现。本文将讨论 LogDevice 和 Pulsar 中都有的基本元素:分布式日志。

架构

LogDevice 向用户显示一个日志原语。写入客户端将 entry 写入 sequencer 节点。该节点将日志序列号(LSN)分配给所有 entry,然后将 entry 写入已经分配给日志的较大节点集的一个子集(副本集)。LogDevice 的 sequencer 类似于 Pulsar 中的 broker,在 Pulsar 中,由 broker 分配消息 ID 并发送消息到 Apache BookKeeper 进行存储。

LogDevice 和 Pulsar 在架构方面有许多相同之处,比如它们都将计算与存储分离。与单片架构相比,这种架构具有以下优势:

•单个日志可以无限增长•出现节点故障时,可以进行无缝恢复•集群扩展简单•读写具有独立可扩展性

比较 Pulsar 和 Kafka:基于分片的架构如何提升整体性能、延展性与弹性[2]一文中详细说明了这种架构的优势。Pulsar 和 LogDevice 都具备这些优势。

LogDevice 和 Pulsar 读取数据的方式有所不同。在 Pulsar 中,读客户端在 broker 上订阅 topic,并从 broker 上接收消息;而在 LogDevice 中,读客户端直接与存储节点相连。

像 LogDevice 这样直接从存储节点读取数据,允许读操作有更大程度的扇出。也就是说,由于读取器不需要访问同一节点,系统可以在单个 topic 上支持更多读取器。

但是,在需要保证日志的一致性时,直接从存储节点读取数据会加长延迟。如果写入器没有确认写入的 entry,读取器无法读取该 entry。从存储节点读取数据时,需要以某种方式通知存储节点 entry 已经被复制到足够多的节点上,并将 ack 发送到写入器,在此之前 entry 不可读。

在 Pulsar 中,客户端通过 broker 进行读写。这种读写方式在延迟和性能上都有优势。由于在同一个节点上进行读写,所以在将 entry ack 发送到写入器时,该 entry 立即可读。Pulsar 通过在 broker 上控制读写,得以支持更复杂的订阅模型,如共享订阅、Failover 订阅[3]等。

一致性、多副本、Failover

LogDevice 和 Pulsar 采用相似的技术实现全局顺序广播协议(TOAB)[4]。将日志分为不同 epoch,每个节点(leader)可以决定该 epoch 中 entry 的序列号,并且相应机制保证不会写入以前的 epoch。

LogDevice 和 Pulsar 都使用 ZooKeeper 决定 leader。

在 LogDevice 中,leader 也称作 sequencer。每个日志都有一个 sequencer,每个 sequencer 都被(从 ZookKeeper)分配了一个“epoch”号。LSN 由 epoch 和一个局部单调递增组件组成,sequencer 决定每个 entry 的 LSN,并将 epoch 中的 entry 转发到一组存储节点上。当足够多的存储节点 ack entry 时,sequencer 将 ack 发送到发起写请求的客户端。在 sequencer 出现故障时,就会有新的 sequencer 获取新的 epoch,并可以立即服务于写入操作。在后台启动一个“封闭”前一个 epoch 的操作,则会禁止从新 epoch 进行读取的操作,直到前一个 epoch 被封。“封闭”操作涉及从节点集向足够多的存储节点通知新 epoch 的存在,因此写入操作就不会有足够多的 ack 来向客户端 ack 写入操作。

对于 Pulsar 来说,epoch 就是 BookKeeper ledger。每个 topic 都有 BookKeeper ledger 列表,这些列表组成了 topic 的全部日志。当一个 Pulsar broker 崩溃时,另一个 broker 会接管这一 topic,由此保证封闭前一个 broker 中的 ledger,创建自己的 ledger,并将其添加至 topic 的 ledger 列表中。最后三个操作涉及到了 ZooKeeper。一旦更新了 topic 的 ledger 列表,broker 就可以开始为 topic 上的读写提供支持。所有向 topic 写入的数据在被 ack 并且对读取器可见之前都会持久化到 BookKeeper ledger 上,存储在一组存储节点中。

对于 LogDevice 和 Pulsar(采用 BookKeeper)来说,entry 持久化只需要一个 entry 命中一个节点子集,因此在有缓慢的或故障的存储节点时,可以保持低延迟的写入。

在检测到 leader 出现故障时,LogDevice 可以在发现故障后很快地提供写服务,只需要两次往返 ZooKeeper 来选择一个 sequencer。在 Pulsar 中,再次写入前,需要先恢复之前的 ledger,恢复操作包括与一些存储节点对话、向 ZooKeeper 进行新的写入等。另外,在 Pulsar 中,可以同时恢复读写,而在 LogDevice 中,读取之前,必须进行“封闭”操作,类似于 ledger 的恢复操作。

我们猜测 LogDevice 不允许在上一个 epoch “封闭”之前写入,因其读操作不协调,与性能无关。不管是否封闭前一个 epoch,检测到 sequencer 发生故障会占用恢复时间。允许写入前的封闭操作需要 sequencer 与读取器进行协调,这样收效甚微,但却会增加复杂性。在 Pulsar 中,由于是在 broker 上进行读取,在写入前恢复之前的 ledger 就很容易。

存储

LogDevice 存储节点将 entry 存储在 RocksDB 中。Entry 存储在按时间顺序排列的列族集合中,entry 由日志 ID 和 entry 的 LSN 组合进行键控。简单来说,每个存储节点都有许多按时间顺序排列的 RocksDB 实例,只向最新的 RocksDB 实例写入。这些 RocksDB 实例尽量确保只进行少量压缩,以避免写入放大。

Pulsar 存储节点(BookKeeper)上有一个日志、一个 entry 日志和一个索引。日志有专用磁盘。当向 bookie 写入 entry 时,其实是在向日志磁盘写入,并向写入器 ack。然后,将 entry 放入一个暂存区域,当此区域内有足够多的 entry 时,就通过 ledger ID 和 entry ID 进行存储,flush 到 entry 日志。此时,entry 日志中的每条 entry 都已写入索引,索引正是一个 RocksDB 实例。

在有许多并发活跃日志时,LogDevice 和 Pulsar 存储层都可以实现低延迟写入。将多个日志的 entry 交叉放在几个文件中,可以最小化随机写入。这样对旋转磁盘的影响较大,在旋转磁盘上写入多个文件意味着磁头必须物理移动多次,但即使在固态磁盘上,优先顺序写入比随机写入在性能上有更多优势。

但是,交错写入也意味着要进行更多读取。

日志系统中的读取通常分为两类,追尾读和追赶读。对于追尾读,LogDevice 和 Pulsar 都不太可能命中磁盘,因为所需数据在某种程度上仍然应该存储在内存缓存中;而追赶读最终都会命中磁盘。吞吐量通常比追赶读延迟更重要,LogDevice 和 Pulsar 的设计都与此相符。

虽然大多数读取应该是连续的,但是 LogDevice 需要读取许多 SST 文件来进行追赶读。因为 RocksDB 在将 entry flush 到磁盘前会按键排序。这样就会分不清楚是否在同一磁盘读写。如果是,追赶读可能会影响系统的写入性能。

RocksDB 允许配置多个路径,将旧 SST 文件与新 SST 文件分开存储。

由于 Pulsar 将写入的关键路径保存在单独的磁盘上,读取操作完全独立。读取通常也是有序的,因为 entry 日志中的数据在 flush 到磁盘前按照 ledger 和 entry ID 排序。

LogDevice 尽量避免压缩,因此会放大写入。这对于日志系统说得通,因为不需要读取写入的大部分数据,但会影响数据保留。不能删除单个日志,因此系统中所有日志的保存时间都必须由保留时间决定。集群内的所有日志都不能永久保存,有些甚至只能保存几个小时。

在 Pulsar 中,要从存储节点删除日志,就需要先从索引中删除。因此,索引会经常压缩,但是由于 entry 数据本身不在索引中,这也影响不大。存储节点监听索引引用每个 entry 日志的百分比。一旦一个 entry 日志低于某个阈值,则复制活跃数据到新的“压缩” entry 日志中,更新索引,并删除原 entry 日志。

总结

LogDevice 是对分布式日志空间的有趣补充。Pulsar 不仅是分布式日志系统,更是一个完整的消息平台,因此不能将 LogDevice 直接与 Pulsar 进行比较,但很高兴看到 LogDevice 团队决定采用与 Pulsar 类似的架构。现在 LogDevice 已经开源,十分期待它的使用。

相关阅读

 • 《Pulsar 与 Kafka 全方位对比(上篇):功能、性能、用例》

 • 《Pulsar 与 Kafka 全方位对比(下篇):案例、特性、社区》

引用链接

[1] LogDevice: https://code.fb.com/core-data/open-sourcing-logdevice-a-distributed-data-store-for-sequential-data/
[2] 比较 Pulsar 和 Kafka:基于分片的架构如何提升整体性能、延展性与弹性: https://www.splunk.com/en_us/blog/it/comparing-pulsar-and-kafka-how-a-segment-based-architecture-delivers-better-performance-scalability-and-resilience.html
[3] 共享订阅、Failover 订阅: https://pulsar.apache.org/docs/en/concepts-messaging/#subscription-modes
[4] 全局顺序广播协议(TOAB): https://streaml.io/blog/bookkeeper-toab/


点击「阅读原文」进入 Pulsar 世界!
: . Video Mini Program Like ,轻点两下取消赞 Wow ,轻点两下取消在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存