Zookeeper ZAB协议实现源码分析( 四 )


Zookeeper 规定所有有效的投票都必须在同一个 轮次 中 , 每个服务器在开始新一轮投票时 , 都会对自己维护的 logicalClock 进行自增操作 。
每个服务器在广播自己的选票前 , 会将自己的投票箱(recvset)清空 。该投票箱记录了所收到的选票 。
例如:Server_2 投票给 Server_3 , Server_3 投票给 Server_1 , 则Server_1的投票箱为(2,3)、(3,1)、(1,1) 。(每个服务器都会默认给自己投票)
前一个数字表示投票者 , 后一个数字表示被选举者 。票箱中只会记录每一个投票者的最后一次投票记录 , 如果投票者更新自己的选票 , 则其他服务器收到该新选票后会在自己的票箱中更新该服务器的选票 。
这一阶段的目的就是为了选出一个准 Leader  , 然后进入下一个阶段 。
协议并没有规定详细的选举算法 , 后面会提到实现中使用的 Fast Leader Election 。

Zookeeper ZAB协议实现源码分析

文章插图
 
  1. 发现阶段(Descovery)
在这个阶段 , Followers 和上一轮选举出的准 Leader 进行通信 , 同步 Followers 最近接收的事务 Proposal。
一个 Follower 只会连接一个 Leader , 如果一个 Follower 节点认为另一个 Follower 节点 , 则会在尝试连接时被拒绝 。被拒绝之后 , 该节点就会进入 Leader Election阶段 。
这个阶段的主要目的是发现当前大多数节点接收的最新 Proposal , 并且准 Leader 生成新的 epoch  , 让 Followers 接收 , 更新它们的 acceptedEpoch 。
Zookeeper ZAB协议实现源码分析

文章插图
 
  1. 同步阶段(Synchronization)
同步阶段主要是利用 Leader 前一阶段获得的最新 Proposal 历史 , 同步集群中所有的副本 。
只有当 quorum(超过半数的节点) 都同步完成 , 准 Leader 才会成为真正的 Leader 。Follower 只会接受 zxid 比自己 lastZxid 大的 Proposal 。
Zookeeper ZAB协议实现源码分析

文章插图
 
  1. 广播阶段(Broadcast)
到了这个阶段 , Zookeeper 集群才能正式对外提供事务服务 , 并且 Leader 可以进行消息广播 。同时 , 如果有新的节点加入 , 还需要对新节点进行同步 。
需要注意的是 , Zab 提交事务并不像 2PC 一样需要全部 Follower 都 Ack , 只需要得到 quorum(超过半数的节点)的Ack 就可以 。
Zookeeper ZAB协议实现源码分析

文章插图
 
Zab协议实现协议的 Java 版本实现跟上面的定义略有不同 , 选举阶段使用的是 Fast Leader Election(FLE) , 它包含了步骤1的发现职责 。因为FLE会选举拥有最新提议的历史节点作为 Leader , 这样就省去了发现最新提议的步骤 。
实际的实现将发现和同步阶段合并为 Recovery Phase(恢复阶段) , 所以 , Zab 的实现实际上有三个阶段 。
Zab协议三个阶段:
  1. 选举(Fast Leader Election)
  2. 恢复(Recovery Phase)
  3. 广播(Broadcast Phase)
Fast Leader Election(快速选举)
前面提到的 FLE 会选举拥有最新Proposal history (lastZxid最大)的节点作为 Leader , 这样就省去了发现最新提议的步骤 。这是基于拥有最新提议的节点也拥有最新的提交记录
成为 Leader 的条件:
  1. 选 epoch 最大的
  2. 若 epoch 相等 , 选 zxid 最大的
  3. 若 epoch 和 zxid 相等 , 选择 server_id 最大的(zoo.cfg中的myid)
节点在选举开始时 , 都默认投票给自己 , 当接收其他节点的选票时 , 会根据上面的 Leader条件 判断并且更改自己的选票 , 然后重新发送选票给其他节点 。当有一个节点的得票超过半数 , 该节点会设置自己的状态为 Leading  , 其他节点会设置自己的状态为 Following 。
Zookeeper ZAB协议实现源码分析

文章插图
 
Recovery Phase(恢复阶段)
这一阶段 Follower 发送他们的 lastZxid 给 Leader , Leader 根据 lastZxid 决定如何同步数据 。这里的实现跟前面的 Phase 2 有所不同:Follower 收到 TRUNC 指令会终止 L.lastCommitedZxid 之后的 Proposal  , 收到 DIFF 指令会接收新的 Proposal 。


推荐阅读