Leader选举是ZooKeeper最重要的技术之一,也是保证分布式数据一致性的关键所在。
1 预备知识
1.1 术语解释
- SID:服务器ID,唯一标识一台zk中的服务器,和myid的值一致。
- ZXID:事务ID,标识服务器状态的变更。在某一时刻,集群中每台机器的ZXID值不一定全都一致,和zk对于客户端的“更新请求”有关。
- Vote:投票
- Quorum:过半机器数。如果集群总机器数是n的话,quorum=(n/2+1)。例如有3台机器,那quorum就等于2。
1.2 集群服务器的状态
(1)服务器的3种角色:
- Leader:主要工作:1)事务请求的唯一调度和处理者;2)集群内部服务器的调度者。
- Follower:主要工作:1)处理非事务请求,转发事务请求给Leader;2)参与Proposal投票;3)参与Leader选举投票。
- Observer:和Follower的区别:只提供非事务服务,不参与人任何投票,包括事务Proposal和Leader选举。作用是在不影响集群事务处理能力的前提下提升集群的非事务处理能力。
(2)服务器的4种状态:
- LOOKING:寻找Leader状态。处于该状态的服务器认为当前集群中没有Leader,需要选举;
- FOLLOWING:跟随者状态,对应角色Follower。
- LEADING:领导者状态,对应角色Leader。
- OBSERVEING:观察者状态,对应角色Observer
1.3 投票数据结构
org.apache.zookeeper.server.quorum.Vote
属性 | 说明 |
id | 被推举的Leader的SID值 |
zxid | 被推举的Leader的事务ID |
electionEpoch | 逻辑时钟,用于判断多个投票是否在同一个选举周期中。在服务端是一个自增序列,没进入新一轮投票,就会对该值加1 |
peerEpoch | 被推举的Leader的epoch |
state | 当前服务器的状态 |
2 Leader选举
2.1 服务器启动时的Leader选举
假设一个zk集群中有三台机器,初始状态:
服务器 | myid | ZXID |
Server1 | 1 | 0 |
Server2 | 2 | 0 |
Server3 | 3 | 0 |
我们用(myid, ZXID)标识一张投票。
1)初始情况,所有服务器投票给自己,然后将各自的投票发给集群中的其他服务器。
Server1先启动,投票给Server1;Server2后启动,投票给Server2。这样每台服务器看到的投票信息就是:
服务器 | 看到的投票 |
Server1 | (1,0),(2,0) |
Server2 | (2,0),(1,0) |
Sever3 | 尚未投票 |
2)处理投票
投票PK规则:
- 优先检查ZXID,ZXID较大的服务器优先作为Leader;
- 如果ZXID相同的话,就比较myid,myid较大的服务器作为Leader。(myid不可能相同,这个值是集群中每台服务器的唯一标识符)
于是,每台服务器对自己看到的所有选票进行处理,结果就是:
服务器 | 投票处理结果 |
Server1 | (2,0) |
Server2 | (2,0) |
Server3 | 尚未投票 |
3)统计投票
服务器 | 得票数 |
Server1 | 0票 |
Server2 | 2票 |
Server3 | 尚未投票 |
显然,Server2得票数2已经过半了(大于等于(n/2+1)就算过半)。
等到Server3开始投票,发现有2票投给了Server2,只有自己给自己投了1票,那也会选Server2作为Leader。
4)改变服务器状态
一旦确定了Leader,每台服务器就会更新自己的状态:如果是Follower,就会变成FOLLOWING;如果是Leader,就会变成LEADING。
2.2 服务器运行期间的Leader选举
什么时候需要在运行期间重新选举?
在zk集群正常运行过程中,一旦选出一个Leader,那么所有服务器的集群角色一般不会发生变化,Leader会一直作为集群Leader,即使集群中有非Leader服务器挂了、或有新机器加入也不会影响Leader。但是一旦Leader挂了,整个集群无法对外服务,就要进入新一轮Leader选举。
这个过程和启动时期的Leader选举过程基本一致,只是各服务器的ZXID可能区别比较大了。
我们仍然假设现在集群中运行着3台服务器:
服务器 | 角色 |
Server1 | Follower |
Server2 | Leader |
Server3 | Follower |
在一瞬间,Server2挂了,我们一起来看看接下来要怎么选出新的Leader:
假设挂掉的一瞬间,服务器的状态是:
服务器 | myid | ZXID |
Server1 | 1 | 123 |
Server2 | 2 | 110 |
Server3 | 3 | 122 |
1)变更状态:所有非Observer服务器将自己状态变更为LOOKING。
2)各服务器单独投票。
同样,每台服务器都投票给自己,然后再把投的票传给集群中的其他服务器看,所以整个集群中各服务器看到的投票情况就是:
服务器 | 看到的投票 | 投票PK结果 |
Server1 | (1,123),(3,122) | (1,123) |
Server2 | 已经挂了 | |
Sever3 | (3,122),(1,123) | (1,123) |
3)统计投票
服务器 | 得票数 |
Server1 | 2票 |
Server2 | - |
Server3 | 0票 |
显然,Server1的得票数已经过半了,成为新的Leader。
4)改变服务器状态,跟上面介绍的一样。
3 其他
zk的Leader选举算法有3种,在zoo.cfg中用electionAlg属性标识:
electionAlg | 对应的Leader选举算法 | 算法实现 |
0 | LeaderElection | 纯UDP实现 |
1 | AuthFastLeaderElection | UDP版本 |
2 | AuthFastLeaderElection | UDP版本,但使用授权模式 |
3 | FastLeaderElection | TCP版本 |
从3.4.0版本开始,zk废弃了0、1、2这三种Leader选举算法,只保留了TCP版本的FastLeaderElection算法,上面介绍的选举过程就是这种算法的。