算法简介
Zab是Zookeeper Atomic Broadcast协议的简称,是为分布式协调服务Zookeeper专门设计的一种支持崩溃恢复的原子广播协议。因为 Zookeeper 是一种主备结构( primary-backup scheme ),所以 Zab 协议具备将主机的状态更新按顺序的广播到备份机的功能,并且能够保证在主机崩溃后不会出现主备数据不一致情况。
正常情况下,在ZooKeeper中所有的事务请求都由一个主服务器也就是Leader来处理,其他服务器为Follower,Leader将客户端的事务请求转换为事务Proposal并为其分配事务id,并且将Proposal分发给集群中其他所有的Follower,然后Leader等待Follwer反馈,当有过半数(>=N/2+1)的Follower反馈信息后,Leader将再次向集群内Follower广播Commit信息,Commit为将之前的Proposal提交。
算法流程
ZAB协议主要包括消息广播和崩溃恢复两个过程,进一步可以细分为发现(discovery)、同步(sync)和广播(Broadcast)阶段。而每个 Zab 节点在任意某个时刻只会处于三个阶段中的某一个阶段,同时我们又称这三个阶段组成了一次 Zab 的迭代,在任何时候 Zab 节点都可能放弃当前的迭代,通过重新进入 Phase 1 开始新的迭代。在发现之前还有选主阶段,并没有在协议里体现,Zookeeper实现了FastLeaderElection算法。
在经过 Phase 1 后只能说该 Zab 节点根据 Leader Oracle 被选择成为了 Leader,但是此时它还没有完全建立起领导关系(LeaderShip),也就是它目前还不具备领导其它的 Zab 节点的能力,只有当它完成了 Phase 2 的同步后才真正建立了对其它 Zab 节点的领导关系,
步骤
- 当一个 Zab 节点发现 Leader Election 算法输出的 Leader 是它的时候,它会立即进入到 Phase 1 开启新的迭代;
- 在开始新的迭代后它首先会收集超过法定数量 Follower 中最新的 Epoch,然后将其递增后作为 NEWEPOCH 提议发送给刚刚超过法定数量的 Follower ,并从它们返回的 ACK 信息中再次收集最新的 Epoch 和最大的 zxid ;
- 最终当 Leader 从 Follower 返回的 ACK-E(f.a,hf) 信息中获取到具备最新 Epoch 和最大 zxid 的 Follower 的 hf 后它就完成了 Phase 1 ,也就是完成了新 Epoch 的确立;
- 进入到 Phase 2 后该 Leader 会通过发送 NEWLEADER(e,Ie) 提议的方式来提议它自己成为真正的 Leader ,其中 Ie 就是最新 Epoch e 的初始化数据;
- 当 Leader 一旦接收到超过法定数量的 Follower 对 NEWLEADER 的 ACK 确认信息就可以被确立为真正的 Leader 了,同时它会发送 COMMIT 提议给 Follower 让它们提交同步后的新提议;
- 而当 Follower 接收到确立后的新 Leader 的 COMMIT 提议后就会提交它们之前同步过但未提交的所有新提议,至此完成 Phase 2;
节点状态
Looking:系统刚启动时、leader与过半follower失去心跳时、Leader崩溃后其他follower切换的状态
Following:Follower节点所处的状态,Follower与Leader保持同步状态
Leading:Leader所处状态
关键交互报文
CEPOCH:Follower进程向准Leader发送自己处理过的最后一个事务Proposal的epoch值
NEWEPOCH:准Leader进程根据接收的各进程的epoch,来生成一个新一轮周期的epoch值
ACK-E:Follower进程反馈准Leader进程发来的NEWEPOCH消息
NEWLEADER:准Leader进程确定自己的领导地位,并发送NEWLEADER消息给各进程
ACK-LD:Follower进程反馈Leader进程发来的NEWLEADER消息
COMMIT-LD:要求Follower进程提交相应的历史Proposal
PROPOSAL:Leader进程生成一个针对客户端事务请求的Proposal
ACK:Follower进程反馈Leader进程发来的PROPOSAL消息
COMMIT:Leader发送COMMIT消息,要求所有进程提交事务PROPOSE
算法分析
事务编号 Zxid :Zxid 是一个 64 位的数字,其中低 32 位是一个简单的单调递增的计数器,针对客户端每一个事务请求,计数器加 1;而高 32 位则代表 Leader 周期 epoch 的编号,每个当选产生一个新的 Leader 服务器,就会从这个 Leader 服务器上取出其本地日志中最大事务的ZXID,并从中读取 epoch 值,然后加 1,以此作为新的 epoch。
epoch:类似Raft中的term,标明leader的版本,每次leader变更都会进行提升,这样可以用于fence之前老的leader崩溃恢复之后重新加入。
Phase 0: Leader election
节点在一开始都处于选举阶段,只要有一个节点得到超半数节点的票数,它就可以当选准 leader。只有到达 Phase 3 准 leader 才会成为真正的 leader。这一阶段的目的是就是为了选出一个准 leader,然后进入下一个阶段。协议并没有规定详细的选举算法。
Phase 1: Discovery
伪代码
zab的实现不是基于RPC的,而是面向socket的方式。选举开始后节点会建立与其他节点的连接,并发送自己的epoch和lastZxid,其他节点回复currentEpoch(cepoch)。准Leader收集Follower的cepoch,并根据max(cepoch)+1生成newEpoch,再通知给其他节点更新其acceptEpoch。newEpoch通知过程中,Follower会反馈其历史事务proposal集合。这样准Leader就可以判断出当前集群中多数节点中哪个节点拥有最新的数据。
其中,
history:当前节点接收到事务提议的 log
acceptedEpoch:follower 已经接受的 leader 更改epoch的 NEWEPOCH 提议
currentEpoch:当前所处的epoch
lastZxid:history 中最近接收到的提议的 zxid (最大的)
详细步骤
整个算法过程,关键的持久化数据有,
epoch :纪元,一个纪元相当于一个时代,一个时代仅存在唯一的一个 Leader 领导者;
f.p :Follower 最后确认的更新 Epoch 提议 NEWEPOCH(e’) 的纪元 e’(Last new epoch proposal follower f acknowledged);
f.a :Follower 最后确认的更新 Leader 提议 NEWLEADER(e’,T) 的纪元 e’(Last new leader proposal follower f acknowledged);
hf :Follower 的历史数据记录;
f.zxid :在 hf 中最后接受的事务标识符;
- 首先 Follower f 给未来预期的 Leader l 通过发送 CEPOCH(f.p) 来传递其确认过的最后一个更新 Epoch 提议 NEWEPOCH(e’) 的纪元信息 f.p;
- Leader l 一旦接收到达到法定数量的 Follower 发送的 CEPOCH(e) ,则立即发送 NEWEPOCH(e’) 给刚刚发送过 CEPOCH(e) 的 Follower,其中这里的 e’ 记为比刚刚接收到的所有 CEPOCH(e) 中的 e 都大的纪元(e’ = emax+1),并且此处开始将达到法定数量时发送过 CEPOCH(e) 的 Followers 记为集合 Q ;
- Follower 接收到 Leader l 发送的 NEWEPOCH(e’) 提议即开始进行判断,如果 f.p < e’ 则意味着新纪元 e’ 大于其最后接收到的更新纪元提议中的纪元,因此更新 f.p 为 e’ 表示其接收到的最后一个更新纪元提议的纪元为 e’ ,同时发送 ACK-E(f.a ,hf) 给 Leader l 确认 NEWEPOCH(e’) 提议,其中 f.a 表示其最后确认过的更新 Leader 提议 NEWLEADER(e’,T) 的纪元信息,而 hf 代表其的历史数据记录,到这里 Follower 就完成了 Phase 1 ;
- Leader l 一旦接收到达到法定数量的 Follower 对 NEWEPOCH(e’) 的确认,则立即从所有回复的 ACK-E(f.a ,hf) 中挑选合适的 hf 作为初始化数据 Ie’(这里也可以理解为从集合 Q 的 Follower 中挑选包含合适 hf 的 Follower ),具体的挑选规则为:对于 f1 和 f2 两个 Follower 回复的 ACK-E(f1.a ,hf1) 和 ACK-E(f2.a ,hf2) ,如果 f1.a < f2.a 则选择 hf2 ;若存在 f1.a == f2.a ,则当 f1.zxid <= f2.zxid 选择 hf2,总结一下也就是首先判断 ACK 中 f.a 的值,f.a 值大者获选,若两者的 f.a 相等则继续判断 f.zxid 的值,f.zxid 值大者获选,到这里 Leader l 就完成了 Phase 1;
Phase 2: Synchronization
伪代码
同步阶段主要是利用 leader 前一阶段获得的最新提议历史,同步集群中所有的副本。准Leader先与Discovery阶段发现的各个Follower节点的历史proposal集合,判断出最新数据节点,然后与其进行同步。
当准Leader同步完成最新数据之后,再通过NewLeader向其他节点通知其当选为新的Leader,并向其同步数据。只有当 quorum 都同步完成,准 leader 才会成为真正的 leader。follower 只会接收 zxid 比自己的 lastZxid 大的提议。
详细步骤
- 首先未来预期的 Leader l 会给 Phase 1 中 Q 集合里所有的 Follower 发送更新 Leader 的提议 NEWLEADER(e’,Ie’) ;
- Follower 接收到 Leader l 发送的 NEWLEADER(e’, Ie’) 提议后开始进行判断,如果 f.p ≠ e’ 则立即回到 Phase 1 的第一步开始新的轮回,如果 f.p == e’ 则开始执行下面的原子操作:首先更新 f.a 为 e’ ,然后接收 Ie’ 中所有纪元为 e’ 的数据,将自身的 hf 设置为 Ie’ ,完成上述原子操作最后回复确认 NEWLEADER(e’, Ie’) 提议的信息给 Leader l ;
- Leader l 一旦接收到达到法定数量 Follower 对 NEWLEADER(e’, Ie’) 提议的确认就会给所有回复的 Follower(也即 Phase 1 的 Q 集合中所有的 Follower )发送 commit 提议,到这里 Leader l 完成 Phase 2 ;
- 当 Follower 接收到 Leader l 发送的 commit 提议后会按照 Ie’ 中的顺序对接收到的 Ie’ 中的事务依次进行提交,到这里 Follower 完成 Phase 2 ;
Phase 3: Broadcast
伪代码
到了这个阶段, leader才能对外提供服务,可以进行消息广播。数据同步的过程类似一个2PC,Leader将client发过来的请求生成一个事务proposal,然后发送给Follower,多数Follower应答之后,Leader再发送Commit给全部的Follower让其进行提交。与2PC相比,只需要多数同意即可,而且也移除了中断逻辑。
详细步骤
- Leader l 按顺序递增 zxid 后给 Phase 2 中所有已确认关系的 Follower(也即 Phase 1 的 Q 集合中所有的 Follower )发送纪元为 e’ 的提议 (e’,<v,z>),这里因为在 Phase 2 中已经根据 Ie’ 完成了前面所有 e’ 纪元提议的 commit ,所以可以认为到目前为止对于纪元为 e’ 的提议的 zxid 都是顺序递增的;
- 首先如果 Follower 起初正处于 Leading 状态,则调用 ready(e’) ,否则 Follower 将按照 Leader l 发送的顺序接收提议,并将其添加到 hf 的尾部,最后回复 ACK 确认给 Leader l ;
- 当 Leader l 接收到达到法定数量 Follower 回复的对于提议 (e’,<v,z>) 的 ACK 确认信息后,就会向它们发送 COMMIT(e’,<v,z>) 提议指示 Follower 提交它们刚刚接收的提议 (e’,<v,z>) ;
- 当 Follower 接收到 Leader l 发送的 COMMIT(e’,<v,z>) 提议后就会通过调用 abdeliver(<v,z>) 方法来提交包括 (e’,<v,z>) 提议在内的和与其同一纪元且排在其前面的所有 (e’,<v’,z’>) 提议,其中存在 <v’,z’> ∈ hf 且 z’ < z;
- 同时在整个 Phase 3 中如果 Leader l 接收到来自 Follower 的 CEPOCH(e) 信息就会一次性的回复 NEWEPOCH(e’) 和 NEWLEADER(e’, Ie’ ◦ βe’) 两个提议来同步该 Follower ;
- 如果 Leader l 在发送完 NEWEPOCH(e’) 和 NEWLEADER(e’, Ie’ ◦ βe’) 两个提议后接收到该 Follower 对 NEWLEADER(e’, Ie’ ◦ βe’) 的 ACK 确认信息,就会直接发送 commit 提议让该节点提交可同步的事务,从而让该 Follower 完成 Phase 2 进入到 Phase 3,同时 Leader l 也会将该节点添加到自己的发送目标集合 Q 中;