分布式理论系列(三)ZAB 协议
在学习了 Paxos 后,接下来学习 Paxos 在开源软件 Zookeeper 中的应用。
一、Zookeeper
Zookeeper 致力于提供一个高性能、高可用,且具有严格的顺序访问控制能力(主要是写操作的严格顺序性)的分布式协调服务。高性能使得 Zookeeper 能够应用于那些对系统吞吐有明确要求的大型分布式系统中,高可用使得分布式的单点问题得到了很好的解决,而严格的顺序访问控制使得客户端能够基于 Zookeeper 实现一些复杂的同步原语(Zxid)。
-
简单的数据模型:
Zookeeper 使得分布式程序能够通过一个共享的树形结构的名字空间来进行相互协调,即 Zookeeper 服务器内存中的数据模型由一系列被称为 ZNode 的数据节点组成,Zookeeper 将全量的数据存储在内存中,以此来提高服务器吞吐、减少延迟的目的。 -
可构建集群:
一个 Zookeeper 集群通常由一组机器构成,组成 Zookeeper 集群的而每台机器都会在内存中维护当前服务器状态,并且每台机器之间都相互通信。 -
顺序访问:
对于来自客户端的每个更新请求,Zookeeper 都会分配一个全局唯一的递增编号,这个编号反映了所有事务操作的先后顺序。 -
高性能:
Zookeeper 将全量数据存储在内存中,并直接服务于客户端的所有非事务请求,因此它尤其适用于以读操作为主的应用场景。
二、ZAB (原子消息广播协议)
ZAB (ZooKeeper Atomic Broadcast) 是 Zookeeper 数据一致性的核心算法,是 Zookeeper 实现分布式系统数据一致性。ZAB 协议专门为 Zookeeper 设计了一种支持崩溃恢复的原子广播协议。
ZAB 协议的核心:所有事务请求(写请求)必须由一个全局唯一的 Leader 服务器来协调处理 ,而余下的其他服务器则成为 Follower 服务器。 Leader 服务器负责将一个客户端事务请求转换成一个事务 proposal(提议),并将该 Proposal 分发给集群中所有的 Follower 服务器。之后 Leader 服务器需要等待所有 Follower 服务器的反馈,一旦超过半数的 Follower 服务器进行了正确的反馈后,那么 Leader 就会再次向所有的 Follower 服务器分 发 Commit 消息,要求其将前一个 proposal 进行提交。
ZAB 协议包括两种基本的模式,分别是 崩溃恢复和消息广播。
当整个服务框架在启动过程中,或是当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时, ZAB 协议就会进入恢复模式并选举产生新的 Leader 服务器。当选举产生了新的 Leader 服务器同时集群中已经有过半的机器与该 Leader 服务器完成了状态同步之后,ZAB 协议就会退出恢复模式。
当集群中已经有过半的 Follower 服务器完成了和 Leader 服务器的状态同步,那么整个服务框架就可以进入消息广播模式了。当一台同样遵守 ZAB 协议的服务器启动后加入到集群中时,如果此时集群中已经存在一个 Leader 服务器在负责进行消息广播, 那么新加入的服务器就会自觉地进入数据恢复模式:找到 Leader 所在的服务器,并与其进行数据同步,然后一起参与到消息广播流程中去。
下面重点讲解崩溃回复和消息广播的过程。
2.1 消息广播
ZAB 协议的消息广播过程使用的是一个原子广播协议,类似于一个二阶段提交过程。针对客户端的事务请求, Leader 服务器会为其生成对应的事务 Proposal ,并将其发送给集群中其余所有的机器,然后再分別收集各自的选票,最后进行事务提交。
在 ZAB 协议的二阶段提交过程中,移除了中断逻辑,所有的 Follower 服务器要么正常反馈 Leader 提出的事务 Proposal ,要么就抛弃 Leader 服务器。同时, ZAB 协议将二阶段提交中的中断逻辑移除意味着我们可以在过半的 Follower 服务器已经反馈 Ack 之后就开始提交事务 Proposal 了,而不需要等待集群中所有的 Follower 服务器都反馈响应。这种简化了的二阶段提交模型无法处理 Leader 服务器崩溃退出而带来的数据不一致问题,此时采用崩溃恢复模式来解决这个问题。
在整个消息广播过程中, Leader 服务器会为每个事务请求生成对应的 Proposal 来进行广播,并且在广播事务 Proposal 之前, Leader 服务器会首先为这个事务 Proposal 分配一个全局单调递增的唯一事务ID (即 ZXID)。
Leader 服务器会为每一个 Follower 服务器都各自分配一个单独的队列,然后将需要广播的事务 Proposal 依次放入这些队列中去,并且根据 FIFO 策略进行消息发送。每一个 Follower 服务器在接收到这个事务 Proposal 之后,都会首先将其以事务日志的形式写入到本地磁盘中去,并且在成功写入后反馈给 Leader 服务器一个 Ack 响应。当 Leader 服务器接收到超过半数 Follower 的 Ack 响应后,就会广播一个 Commit 消息给所有的 Follower 服务器以通知其进行事务提交,同时 Leader 自身也会完成对事务的提交。
2.2 崩溃恢复
Leader 服务器出现崩溃,或者说由于网络原因导致 Leader 服务器失去了与过半 Follower 的联系,那么就会进入崩溃恢复模式。Leader 选举算法不仅仅需要让 Leader 自己知道其自身已经被选举为 Leader ,同时还需要让集群中的所有其他机器也能够快速地感知到选举产生的新的 Leader 服务器。
ZAB 协议规定了如果一个事务 Proposal 在一台机器上被处理成功,那么应该在所有的机器上都被处理成功,哪怕机器出现故障崩溃。
下面介绍两种崩溃恢复中的场景和 ZAB 协议需要保证的特性:
-
ZAB 协议需要确保那些已经在 Leader 服务器上提交的事务最终被所有服务器都提交
假设一个事务在 Leader 服务器上被提交了,并且已经得到过半 Follower 服务器的 Ack 反馈,但是在它将 Commit 消息发送给所有 Follower 机器之前, Leader 服务器挂了,针对这种情况, ZAB 协议就需要确保该事务最终能够在所有的服务器上都被提交成功,否则将出现不一致。
-
ZAB 协议需要确保丢弃那些只在 Leader 服务器上被提出的事务
假设初始的 Leader 服务器 在提出了一个事务之后就崩溃退出了,导致集群中的其他服务器都没有收到这个事务,当该服务器恢复过来再次加入到集群中的时候 ,ZAB协议需要确保丢弃这个事务。
针对以上两点需求,ZAB 协议需要设计的选举算法应该满足:确保提交已经被 Leader 提交的事务 Proposal,同时丢弃已经被跳过的事务 Proposal 。
如果让 Leader 选举算法能够保证新选举出来的 Leader 服务器拥有集群中所有机器最高编号(即 ZXID 最大)的事务 Proposal,那么就可以保证这个新选举出来的 Leader 一定具有所有已经提交的提案。同时,如果让具有最高编号事务 Proposal 的机器来成为 Leader,就可以省去 Leader 服务器检查 Proposal 的提交和丢弃工作的这一步操作。
2.3 数据同步
Leader 服务器会为每一个 Follower 服务器都准备一个队列,并将那些没有被各 Follower 服务器同步的事务以 Proposal 消息的形式逐个发送给 Follower 服务器,并在每一个 Proposal 消息后面紧接着再发送一个 Commit 消息,以表示该事务已经被提交。等到 Follower 服务器将所有其尚未同步的事务 Proposal 都从 Leader 服务器上同步过来并成功应用到本地数据库中后, Leader 服务器就会将该 Follower 服务器加入到真正的可用 Follower 列表中,并开始之后的其他流程。
下面来看 ZAB 协议是如何处理那些需要被丢弃的事务 Proposal 的。在 ZAB 协议的事务编号 ZXID 设计中, ZXID 是一个 64 位的数字,低 32 位可以看作是一个简单的单调递增的计数器,针对客户端的每一个事务请求, Leader 服务器在产生一个新的事务 Proposal 的时候,都会对该计数器进行加 1 操作;高 32 位代表了 Leader 周期 epoch 的编号,每当选举产生一个新的 Leader 服务器,就会从这个 Leader 服务器上取出其本地日志中最大事务 Proposal 的 ZXID ,并从该 ZXID 中解析出对应的 epoch 值,然后再对其进行加 1 操作,之后就会以此编号作为新的 epoch, 并将低 32 位置 0 来开始生成新的 ZXID 。
基于这样的策略,当一个包含了上一个 Leader 周期中尚未提交过的事务 Proposal 的服务器启动加入到集群中,发现此时集群中已经存在 Leader,将自身以 Follower 角色连接上 Leader 服务器之后,Leader 服务器会根据自己服务器上最后被提交的 Proposal 来和 Follower 服务器的 Proposal 进行比对,发现 Follower 中有上一个 Leader 周期的事务 Proposal 时,Leader 会要求 Follower 进行一个回退操作-回退到一个确实已经被集群中过半机器提交的最新的事务 Proposal 。
2.4 ZAB 协议原理
ZAB 主要包括消息广播和崩溃恢复两个过程,进一步可以分为三个阶段,分别是发现(Discovery)、同步(Synchronization)、广播(Broadcast)阶段。ZAB 的每一个分布式进程会循环执行这三个阶段,称为主进程周期。
-
发现
即要求 zookeeper 集群必须选择出一个 Leader 进程,同时 Leader 会维护一个 Follower 可用列表。 -
同步
Leader 要负责将本身的数据与 Follower 完成同步,做到多副本存储。这样也是体现了 CAP 中高可用和分区容错。Follower 将队列中未处理完的请求消费完成后,写入本地事物日志中。 -
广播
Leader 可以接受客户端新的 proposal 请求,将新的 proposal 请求广播给所有的 Follower。
在正常运行过程中,ZAB 协议会一直运行于阶段三来反复进行消息广播流程,如果出现崩溃或其他原因导致 Leader 缺失,那么此时 ZAB 协议会再次进入发现阶段,选举新的 Leader。
每个进程都有可能处于如下三种状态之一:
- LOOKING:Leader 选举阶段。
- FOLLOWING:Follower 服务器和 Leader 服务器保持同步状态。
- LEADING:Leader 服务器作为主进程领导状态。
所有进程初始状态都是 LOOKING 状态,此时不存在 Leader,此时,进程会试图选举出一个新的 Leader,之后,如果进程发现已经选举出新的 Leader 了,那么它就会切换到 FOLLOWING 状态,并开始和 Leader 保持同步,处于 FOLLOWING 状态的进程称为 Follower,LEADING 状态的进程称为 Leader,当 Leader 崩溃或放弃领导地位时,其余的 Follower 进程就会转换到 LOOKING 状态开始新一轮的 Leader 选举。
一个 Follower 只能和一个 Leader 保持同步,Leader 进程和所有与所有的 Follower 进程之间都通过心跳检测机制来感知彼此的情况。若 Leader 能够在超时时间内正常收到心跳检测,那么 Follower 就会一直与该 Leader 保持连接,而如果在指定时间内 Leader 无法从过半的 Follower 进程那里接收到心跳检测,或者 TCP 连接断开,那么 Leader 会放弃当前周期的领导,比你转换到 LOOKING 状态,其他的 Follower 也会选择放弃这个 Leader,同时转换到 LOOKING 状态,之后会进行新一轮的 Leader 选举,并在选举产生新的 Leader 之后开始新的一轮主进程周期。
参考:
- 《Zookeeper 与 Paxos》:https://www.cnblogs.com/leesf456/p/6012777.html
- 从 Paxos 到 Zookeeper : 分布式一致性原理与实践
每天用心记录一点点。内容也许不重要,但习惯很重要!