数据一致性

数据一致性,指数据在多个副本之间能否保持一致的特性。

之前的文章中中有提到 Ledger 的 核心参数:Ensemble/Write Quorum/Ack Quorum,其中 Ack Quorum(AQ)规定了一次写入请求需要等待的副本 ack 数,以此保证已提交的 Entry 在(AQ-1)个节点永久丢失后也能幸存下来。
所以,pulsar的数据一致性,主要通过 BookKeeper 来体现。

1. LAC

Last Add Confirm(LAC)表示了满足 ack quorum 条件的最大消息offset。只有 LAC 之前的数据才会对读取可见。

image.png

LAC 是每个 topic 分区的一个元数据,正常读取流程会判断读取的位置是否大于 LAC,如果大于LAC,说明没有可读的消息。

因为 pulsar broker 是无状态的,LAC 需要在 Broker 故障之后恢复到新的 topic owner上,所以在将数据写入到 Bookie 时,数据中携带了 LAC 信息。

image.png

从这个流程中可以看到,LAC 写入到 Bookie 是有滞后的,所以 topic 或者分区发生 ownership 迁移时,需要恢复真实的 LAC。

2. 恢复

topic ownership 迁移示意图。

image.png

2.1 防止脑裂

一个 Broker(BrokerA) 断开 和 Zookeeper 的连接,那另一个 Broker(BrokerB) 会接管 并开始 ledger 修复流程。但是此时 BrokerA 可能仍在正常运行,它可以正常的连接到 BookKeeper 集群,于是就会出现两个客户端试图同时操作同一个 ledger,这种情况就属于脑裂。

BookKeeper 通过 fence 机制来防止脑裂的发生。当第二个 BrokerB 试图开始 ledger 修复流程时,会先将 ledger 设置为 fence 状态,在这个状态下 ledger 会拒接所有新的写入请求。当足够多的 bookie 节点将这个 ledger 状态设置为 fence 时(剩余没有fence的节点数 < AQ),就算 BrokerA 仍然处于正常运行状态,它也不能再进行任何新的写入操作。

2.2 恢复 Ledger

BrokerB 从 Bookie 读取最后写入的数据,并且从中解析出 LAC,然后从 LAC + 1的位置开始恢复数据,恢复过程会不断读取 Entry,并且以下两个情况数据决定是否将 Entry 重新写回到 Ledger:

  • 已确认 :收到 Ack Quorum 数量的成功响应

  • 未确认 :收到 Quorum Coverage 数量的数据不存在响应 (已写入这个 entry 数据的 bookie 节点数量未达到 Ack Quorum),中止恢复。

image.png

一旦明确了所有已确认的 entry ,且这些 entry 复制了足够多的副本数,客户端就会关闭 ledger。关闭 ledger 的操作主要是对 ZooKeeper 上 ledger 元数据的更新,将状态设置为 CLOSED,并将 Last Entry Id 设置为最新的已确认的 entry ID。

2.3 创建新 Ledger

BrokerB 在执行完 Ledger 恢复之后,会创建新一个新的 Ledger 来供数据写入。

3. 总结

Pulsar 通过 BookKeeper 来提供数据一致性的保证,和 Raft 等算法不一样的是,所有的存储节点是对等的,没有 master/slave之分,而是有一个 writer,writer 可以保证唯一性。
在故障恢复时,不需要从 writer 获取每个副本的信息,而是从 LAC 开始恢复,然后根据副本的实际写入情况来恢复到最后写入的 Entry。这种方式的数据一致性恢复不需要保存每个副本的 index (复制进度)信息,实现上比较简介。
在 Ledger 恢复完成之后,ledger 会被 close,然后重新 open 一个 ledger 用于新数据的写入。