协议的 Java 版本实现跟上面的定义有些不同,选举阶段使用的是 Fast Leader Election(FLE),它包含了 Phase 1 的发现职责。因为 FLE 会选举拥有最新提议历史的节点作为 leader,这样就省去了发现最新提议的步骤。实际的实现将 Phase 1 和 Phase 2 合并为 Recovery Phase(恢复阶段)。所以,ZAB 的实现只有三个阶段:
- Fast Leader Election
- Recovery Phase
- Broadcast Phase
Fast Leader Election
FastLeaderElection算法通过异步的通信方式来收集其它节点的选票,同时在分析选票时又根据投票者的当前状态来作不同的处理,以加快Leader的选举进程。FLE 会选举拥有最新提议历史(lastZxid最大)的节点作为 leader,这样就省去了发现最新提议的步骤。这是基于拥有最新提议的节点也有最新提交记录的前提。
成为 leader 的条件:
- 选epoch最大的
- epoch相等,选 zxid 最大的
- epoch和zxid都相等,选择server id最大的(就是我们配置zoo.cfg中的myid)
核心思想
节点在选举开始都默认投票给自己,当接收其他节点的选票时,会根据上面的条件更改自己的选票并重新发送选票给其他节点,当有一个节点的得票超过半数,该节点会设置自己的状态为 leading,其他节点会设置自己的状态为 following。
具体分析
启动时数据恢复
每个ZooKeeper Server启动时读取当前磁盘的数据(transaction log),获取最大的zxid。发送选票
每个参与投票的ZooKeeper Server向其他Server发送自己所推荐的Leader,这个协议中包括几部分数据:
- 所推举的Leader id。在初始阶段,第一次投票所有Server都推举自己为Leader。
- 所推举节点上最大zxid值。这个值越大,说明该Server的数据越新。
- logicalclock。这个值从0开始递增,每次选举对应一个值,即在同一次选举中,这个值是一致的。这个值越大说明选举进程越新。
- 本机的所处状态。包括LOOKING,FOLLOWING,OBSERVING,LEADING。
- 处理选票
每台Server将自己的数据发送给其他Server之后,同样也要接受其他Server的选票,并做一下处理。
如果Sender的状态是LOOKING:
- 如果发送过来的logicalclock大于目前的logicalclock。说明这是更新的一次选举,需要更新本机的logicalclock,同时清空已经收集到的选票,因为这些数据已经不再有效。然后判断是否需要更新自己的选举情况。首先判断zxid,zxid大者胜出;如果相同比较leader id,大者胜出。
- 如果发送过来的logicalclock小于于目前的logicalclock。说明对方处于一个比较早的选举进程,只需要将本机的数据发送过去即可。
- 如果发送过来的logicalclock等于目前的logicalclock。根据收到的zxid和leader id更新选票,然后广播出去。当Server处理完选票后,可能需要对Server的状态进行更新:
1)判断服务器是否已经收集到所有的服务器的选举状态。如果是根据选举结果设置自己的角色(FOLLOWING or LEADER),然后退出选举。
2)如果没有收到没有所有服务器的选举状态,也可以判断一下根据以上过程之后更新的选举Leader是不是得到了超过半数以上服务器的支持。如果是,那么尝试在200ms内接收下数据,如果没有心数据到来说明大家已经认同这个结果。这时,设置角色然后退出选举。
如果Sender的状态是FOLLOWING或者LEADER:
- 如果LogicalClock相同,将数据保存到recvset,如果Sender宣称自己是Leader,那么判断是不是半数以上的服务器都选举它,如果是设置角色并退出选举。
- 否则,这是一条与当前LogicalClock不符合的消息,说明在另一个选举过程中已经有了选举结果,于是将该选举结果加入到OutOfElection集合中,根据OutOfElection来判断是否可以结束选举,如果可以也是保存LogicalClock,更新角色,退出选举。
ZK中实现
Recovery Phase
这一阶段 follower 发送它们的 lastZixd 给 leader,leader 根据 lastZixd 决定如何同步数据。
ZAB中Leader会根据Follower数据情况使用多种同步策略:
- SNAP:如果Follower数据太老,Leader将发送快照SNAP指令给Follower同步数据
- DIFF:Leader发送从Follower.lastZxid到Leader.lastZxid之间的提案DIFF数据给Follower同步数据
-
TRUNC:当Follower.lastZxid比Leader.lastZxid大时,Leader发送从Leader.lastZxid到Follower.lastZxid的TRUNC指令让Follower丢弃该段数据SNAP和DIFF用于保证集群中Follower节点上已经Committed的数据的一致性,TRUNC用于抛弃已经被处理但是还没有Committed的数据。
Follower接收SNAP/DIFF/TRUNC指令同步数据与ZXID,同步成功之后向Leader发送ACK-LD,Leader才会将其加入到可用Follower列表。
具体消息流程如下:
Broadcast Phase
与ZAB原有设计无差别