Zookeeper的领导者选举和原子广播

简单的说一下zookeeper工作的过程,如果对这个过程还不太清楚,或者说对它如何使用等不太清楚的,可以参考一下其他的文章

Zookeeper工作原理概述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
zookeeper有两种工作的模式,一种是单机方式,另一种是集群方式.单机方式不属于这里分析的范畴,因为研究zookeeper的目的就在于研究一个zookeeper集群的机器如何协调起来工作的.
要配置几台zookeeper一起工作,大家在开始必须使用相同的配置文件,配置文件中有一些配置项
但是与集群相关的是这一项:
server.1=192.168.211.1:2888:3888
server.2=192.168.211.2:2888:3888
server.3=192.168.211.3:2888:3888
这里定义了两台服务器的配置,格式为:
server.serverid=serverhost:leader_listent_port:quorum_port
serverid是本服务器的id
leader_listen_port是该服务器一旦成为leader之后需要监听的端口,用于接收来自follower的请求
quorum_port是集群中的每一个服务器在最开始选举leader时监听的端口,用于服务器互相之间通信选举leader
需要注意的是,server id并没有写在这个配置文件中,而是在datadir中的myid文件中指定
我理解这么做的目的是:
所有的服务器统一使用一个配置文件,该配置文件里面没有任何与特定服务器相关的信息
这样便于发布服务的时候不会出错,而独立出来一个文件专门存放这个server id值
Zookeeper集群工作的过程包括如下几步:
recovery,这个过程泛指集群服务器的启动和恢复,因为恢复也可以理解为另一种层面上的"启动"–需要恢复历史数据的启动,后面会详细讲解.
broadcast,这是启动完毕之后,集群中的服务器开始接收客户端的连接一起工作的过程,如果客户端有修改数据的改动,那么一定会由leader广播给follower,所以称为"broadcast".
Zookeeper集群大概是这样工作的:
a.首先每个服务器读取配置文件和数据文件,根据serverid知道本机对应的配置(就是前面那些地址和端口),并且将历史数据加载进内存中.
b.集群中的服务器开始根据前面给出的quorum port监听集群中其他服务器的请求,并且把自己选举的leader也通知其他服务器,来来往往几回,选举出集群的一个leader.
c.选举完leader其实还不算是真正意义上的"leader",因为到了这里leader还需要与集群中的其他服务器同步数据,如果这一步出错,将返回b中重新选举leader.
在leader选举完毕之后,集群中的其他服务器称为"follower",也就是都要听从leader的指令.
d.到了这里,集群中的所有服务器,不论是leader还是follower,大家的数据都是一致的了,可以开始接收客户端的连接了.
如果是读类型的请求,那么直接返回就是了,因为并不改变数据;
否则,都要向leader汇报,如何通知leader呢?就是通过前面讲到的leader_listen_port.
leader收到这个修改数据的请求之后,将会广播给集群中其他follower,当超过一半数量的follower有了回复,那么就相当于这个修改操作完成了,这时leader可以告诉之前的那台服务器可以给客户端一个回应了.
可以看到,上面a,b,c对应的recovery过程,而d对应的broadcast过程.

Fast Leader选举算法(领导者选举)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
如何在Zookeeper集群中选举出一个leader?
Zookeeper使用了三种算法,具体使用哪种算法,在配置文件中是可以配置的,对应的配置项是"electionAlg",其中:
1对应的是LeaderElection算法
2对应的是AuthFastLeaderElection算法
3对应的是FastLeaderElection算法
默认使用FastLeaderElection算法

数据恢复阶段
每个在zookeeper服务器先读取当前保存在磁盘的数据,zookeeper中的每份数据,都有一个对应的id值
这个值是依次递增的,换言之,越新的数据,对应的ID值就越大

向其他节点发送投票值
在读取数据完毕之后,每个zookeeper服务器发送自己选举的leader(首次选自己)
这个协议中包含了以下几部分的数据:
所选举leader的id(就是配置文件中写好的每个服务器的id) ,在初始阶段,每台服务器的这个值都是自己服务器的id,也就是它们都选举自己为leader.
服务器最大数据的id,这个值大的服务器,说明存放了更新的数据.
逻辑时钟的值,这个值从0开始递增,每次选举对应一个值,也就是说:如果在同一次选举中,那么这个值应该是一致的;逻辑时钟值越大,说明这一次选举leader的进程更新.
本机在当前选举过程中的状态,有以下几种:LOOKING,FOLLOWING,OBSERVING,LEADING

接受来自其他节点的数据
每台服务器将自己服务器的以上数据发送到集群中的其他服务器之后,同样的也需要接收来自其他服务器的数据
它将做以下的处理:
如果所接收数据中服务器的状态还是在选举阶段(LOOKING 状态),那么首先判断逻辑时钟值,又分为以下三种情况:
如果发送过来的逻辑时钟大于目前的逻辑时钟,那么说明这是更新的一次选举,此时需要更新一下本机的逻辑时钟值,同时将之前收集到的来自其他服务器的选举清空,因为这些数据已经不再有效了.
然后判断是否需要更新当前自己的选举情况.在这里是根据选举leader id,保存的最大数据id来进行判断的
这两种数据之间对这个选举结果的影响的权重关系是:
首先看数据id,数据id大者胜出;
其次再判断leader id,leader id大者胜出.
然后再将自身最新的选举结果(也就是上面提到的三种数据)广播给其他服务器).
发送过来数据的逻辑时钟小于本机的逻辑时钟,说明对方在一个相对较早的选举进程中,这里只需要将本机的数据发送过去就是了
两边的逻辑时钟相同,此时也只是调用totalOrderPredicate函数判断是否需要更新本机的数据,如果更新了再将自己最新的选举结果广播出去就是了.
再处理两种情况:
服务器判断是不是已经收集到了所有服务器的选举状态,如果是,那么这台服务器选举的leader就定下来了,然后根据选举结果设置自己的角色(FOLLOWING还是LEADER),然后退出选举过程就是了.
即使没有收集到所有服务器的选举状态,也可以根据该节点上选择的最新的leader是不是得到了超过半数以上服务器的支持
如果是,那么当前线程将被阻塞等待一段时间(这个时间在finalizeWait定义)看看是不是还会收到当前leader的数据更优的leader
如果经过一段时间还没有这个新的leader提出来,那么这台服务器最终的leader就确定了,否则进行下一次选举.
如果所接收服务器不在选举状态,也就是在FOLLOWING或者LEADING状态
做以下两个判断:
如果逻辑时钟相同,将该数据保存到recvset,如果所接收服务器宣称自己是leader,那么将判断是不是有半数以上的服务器选举它,如果是则设置选举状态退出选举过程
否则这是一条与当前逻辑时钟不符合的消息,那么说明在另一个选举过程中已经有了选举结果,于是将该选举结果加入到outofelection集合中,再根据outofelection来判断是否可以结束选举,如果可以也是保存逻辑时钟,设置选举状态,退出选举过程.

Leader与Follower同步数据(原子广播)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
根据 Fast Leader选举算法中的分析,如果一台zookeeper服务器成为集群中的leader,那么一定是当前所有服务器中保存数据最多(不是最新??)的服务器
所以在这台服务器成为leader之后,首先要做的事情就是与集群中的其它服务器(现在是follower)同步数据,保证大家的数据一致,这个过程完毕了才开始正式处理来自客户端的连接请求
Fast Leader选举算法中提到的同步数据时使用的逻辑时钟,它的初始值是0,每次选举过程都会递增的,在leader正式上任之后做的第一件事情,就是根据当前保存的数据id值,设置最新的逻辑时钟值
随后,leader构建NEWLEADER封包,该封包的数据是当前最大数据的id,广播给所有的follower,也就是告知follower leader保存的数据id是多少,大家看看是不是需要同步
然后,leader根据follower数量给每个follower创建一个线程LearnerHandler,专门负责接收它们的同步数据请求
leader主线程开始阻塞在这里,等待其他follower的回应(也就是LearnerHandler线程的处理结果),同样的,只有在超过半数的follower已经同步数据完毕,这个过程才能结束,leader才能正式成为leader.

Leader所做的工作
所以其实leader与follower同步数据的大部分操作都在LearnerHandler线程中处理的,接着看这一块
leader接收到的来自某个follower封包一定是FOLLOWERINFO,该封包告知了该服务器保存的数据id
之后根据这个数据id与本机保存的数据进行比较:
如果数据完全一致,则发送DIFF封包告知follower当前数据就是最新的了
判断这一阶段之内有没有已经被提交的提议值,如果有,那么:
如果有部分数据没有同步,那么会发送DIFF封包将有差异的数据同步过去.同时将follower没有的数据逐个发送COMMIT封包给follower要求记录下来.
如果follower数据id更大,那么会发送TRUNC封包告知截除多余数据.(一台leader数据没同步就宕掉了,选举之后恢复了,数据比现在leader更新)
如果这一阶段内没有提交的提议值,直接发送SNAP封包将快照同步发送给follower
消息完毕之后,发送UPTODATE封包告知follower当前数据就是最新的了,再次发送NEWLEADER封包宣称自己是leader,等待follower的响应

Follower做的工作:
会尝试与leader建立连接,这里有一个机制,如果一定时间内没有连接上,就报错退出,重新回到选举状态.
其次在发送FOLLOWERINFO封包,该封包中带上自己的最大数据id,也就是会告知leader本机保存的最大数据id.
根据前面对LeaderHandler的分析,leader会根据不同的情况发送DIFF,UPTODATE,TRUNC,SNAP,依次进行处理就是了,此时follower跟leader的数据也就同步上了.
由于leader端发送的最后一个封包是UPTODATE,因此在接收到这个封包之后follower结束同步数据过程,发送ACK封包回复leader.

以上过程中,任何情况出现的错误,服务器将自动将选举状态切换到LOOKING状态,重新开始进行选举.