选举机制
zxid
zxid:事务编号(zookeeper transaction id),8字节的整型数字,但是zk把这个数字拆成了两部分,64位整数分为前32位和后32位。
初始化的时候zxid是0,即:
00000000000000000000000000000000 00000000000000000000000000000000
每一次写请求都会增加后32位,假设现在进行了10次写请求(即使没有修改到数据)zxid:
00000000000000000000000000000000 00000000000000000000000000001010
当进行一次选举的时候,前 32 位就会增加 1,并且清零后 32 位,此时zxid:
00000000000000000000000000000001 00000000000000000000000000000000
此外,当后32位彻底用完,即zk正常执行了 2^32-1 次写请求都没有进行过一次选举,则前32位加1,此时zxid:
# 进位前
00000000000000000000000000000000 11111111111111111111111111111111
# 进位后
00000000000000000000000000000001 00000000000000000000000000000000
总结:
- zxid事务编号的前32位官方命名为epoch,表示纪元,含义就是完成了一次leader的选举,或者写操作次数达到了一次上限
- zxid事务编号的后32为表示节点写操作的次数
myid
zk官方启动配置zoo.cfg
中有一项dataDir
指定了数据存放的路径(默认是/tmp/zookeeper
)搭建zk集群时要在此路径下新建一个文本文件,命名为myid,文本内容就是一个数字,这个数字就是当前节点的myid。
此外在zoo.cfg
中配置的信息有:
server.1=zoo1:2888:3888
server.2=zoo2:2888:3888
server.3=zoo3:2888:3888
其中server.
之后的数字就是myid,zk中要求myid各不相同(类似于主键)。
选举规则
leader的选举完全看这三个值的大小:
- epoch(前32位)
- 写请求次数(后32位)
- myid
谁大就更有资格成为leader,如果相同则逐级比,因为myid一定不同,所以一定可以分出胜负。
可以看出选举的原则是:
- 谁当leader的次数多谁先当
- 谁被请求写操作多,表示谁拿到的数据最新,谁先当
- 都相同比较myid
虽然我们说要让epoch先比,写请求次数后比。但其实只要比zxid就好了,因为先比epoch大小,再比写请求次数大小这个行为,在数值上等价于直接比zxid。
选举前
三个节点组成的集群对外开始服务前必须选出leader,在选举之前要求:
- 每个节点必须已知集群中一共有多少个节点
- 每个节点都在内部维护一个投票箱(表)进行计票
- 每个节点都有唯一不重复的myid
- 每次投票后节点会把选票信息发送给集群上所有的节点
zk中节点的状态有:
- LOOKING:正在寻找leader,处于此阶段的节点不能对外提供服务
- LEADING:当前节点就是leader,可以对外提供服务
- FOLLOWING:当前节点是follower,可以对外提供服务
发送个其他节点的选票信息:
- sid:我是谁
- leader:我选谁
- state:我当前状态
- zxid:我选择的leader的最大事务编号
首次选举leader
设myid分别为1,2,3,4,5节点依次启动
1节点启动
第一次启动,彼此之间没有通信过,每个节点都选自己为leader
选票表:
key | value |
---|---|
sid | myid:1 |
leader | myid:1 |
state | LOOKING |
zxid | 0 |
1节点把投票信息发送给了其他4个节点,而此时另外4个节点没启动,无法收到1节点的选票,同样其他4个节点也没给1节点发送选票信息。
投票箱:
投票人 | 选择leader |
---|---|
myid:1 | myid:1 |
当前节点所选leader的myid:1,它获得的选票来自节点myid:1
由于1节点票数没有达到半数以上,不能选为leader,且集群中没有leader,故1节点状态仍为LOOKING
2节点启动
同样2节点启动后先投给自己
选票表
key | value |
---|---|
sid | myid:2 |
leader | myid:2 |
state | LOOKING |
zxid | 0 |
投票箱:
投票人 | 选择leader |
---|---|
myid:2 | myid:2 |
当前节点所选leader的myid:2,它获得的选票来自节点myid:2
2节点随后把选票信息发送给集群上的其他节点,此时集群中只有1节点能收到。
收到后1节点会拿2节点选的leader和1节点自己选leader进行比较,依据比较条件,在zxid一样的情况下,更改选票投给myid更大2节点,同时维护1节点的投票箱,并将选票信息发送给集群上的所有节点,此时只有2节点能收到,2节点在比较后在投票箱中维护数据。
此时1节点和2节点中维护的投票箱为:
投票人 | 选择leader |
---|---|
myid:1 | myid:2 |
myid:2 | myid:2 |
当前节点所选leader的myid:2,它获得的选票来自节点myid:1,myid:2
由于此时2节点虽然有2票但是任然没到半数以上,所以1节点和2节点仍未LOOKING状态
3节点启动
3节点也先投给自己,其中选票信息为:
key | value |
---|---|
sid | myid:3 |
leader | myid:3 |
state | LOOKING |
zxid | 0 |
随后3节点把该选票信息发送到集群中的所有节点上,由于只有1节点和2节点启动,只有他俩收到了,收到后1节点2节点会进行比较,同上可知,仍然选择了myid较大的3节点作为新的选举对象,同时把新的选票发送到所有节点,内部更新投票箱,此时1,2,3三个节点中维护的投票箱一致为:
投票人 | 选择leader |
---|---|
myid:1 | myid:3 |
myid:2 | myid:3 |
myid:3 | myid:3 |
当前节点所选leader的myid:3,它获得的选票来自节点myid:1,myid:2,myid:3
此时3节点获得的票数已经超过半数,更新3节点的状态为LEADING。1节点2节点的状态为FOLLOWING。所有节点epoch加1。集群开放对外服务。
4节点5节点启动
由于启动后得知已经选出leader,则新进入的节点自动状态改为FOLLOWING
挂了重选leader
假设3节点宕机,且在新leader选举出来之前3节点没有恢复上线。此时集群内部知道集群内可用机器数量仍在半数以上,依然可以提供服务,但是要暂停服务选举新的leader。
在暂停期间,所有节点都是LOOKING状态,且都会第一次把票投给自己,投完票后会把选票信息发给集群上有所的节点(包括自己),在收集群上节点的选票时,会不断维护内部的投票箱,每一次都zxid、myid大小进行比较,每一次都把票投给较大的那个同时维护投票箱,保证投票箱在集群上是有一致性的。在这个过程中得票率先超过半数的节点将成为新的leader,它的状态改为LEADING,epoch加1。其他节点状态改为FOLLOWING。集群开启对外服务。
如果3节点此时恢复上线,自动成为follower。
如果3节点在新leader选举出来恢复上线,状态位LOOKING,平等参加选举。