转载至 http://www.jasongj.com/zookeeper/fastleaderelection/:算法
原创文章,转载请务必将下面这段话置于文章开头处。
本文转发自技术世界,原文连接 http://www.jasongj.com/zookeeper/fastleaderelection/服务器
Zookeeper是一个分布式协调服务,可用于服务发现,分布式锁,分布式领导选举,配置管理等。网络
这一切的基础,都是Zookeeper提供了一个相似于Linux文件系统的树形结构(可认为是轻量级的内存文件系统,但只适合存少许信息,彻底不适合存储大量文件或者大文件),同时提供了对于每一个节点的监控与通知机制。数据结构
既然是一个文件系统,就不得不提Zookeeper是如何保证数据的一致性的。本文将介绍Zookeeper如何保证数据一致性,如何进行领导选举,以及数据监控/通知机制的语义保证。架构
Zookeeper集群是一个基于主从复制的高可用集群,每一个服务器承担以下三种角色中的一种并发
为了保证写操做的一致性与可用性,Zookeeper专门设计了一种名为原子广播(ZAB)的支持崩溃恢复的一致性协议。基于该协议,Zookeeper实现了一种主从模式的系统架构来保持集群中各个副本之间的数据一致性。分布式
根据ZAB协议,全部的写操做都必须经过Leader完成,Leader写入本地日志后再复制到全部的Follower节点。性能
一旦Leader节点没法工做,ZAB协议可以自动从Follower节点中从新选出一个合适的替代者,即新的Leader,该过程即为领导选举。该领导选举过程,是ZAB协议中最为重要和复杂的过程。设计
经过Leader进行写操做流程以下图所示rest
由上图可见,经过Leader进行写操做,主要分为五步:
这里要注意
经过Follower/Observer进行写操做流程以下图所示:
从上图可见
Leader/Follower/Observer均可直接处理读请求,从本地内存中读取数据并返回给客户端便可。
因为处理读请求不须要服务器之间的交互,Follower/Observer越多,总体可处理的读请求量越大,也即读性能越好。
myid
每一个Zookeeper服务器,都须要在数据文件夹下建立一个名为myid的文件,该文件包含整个Zookeeper集群惟一的ID(整数)。例如某Zookeeper集群包含三台服务器,hostname分别为zoo一、zoo2和zoo3,其myid分别为一、2和3,则在配置文件中其ID与hostname必须一一对应,以下所示。在该配置文件中,server.
后面的数据即为myid
1
2
3
|
server.1=zoo1:2888:3888
server.2=zoo2:2888:3888
server.3=zoo3:2888:3888
|
zxid
相似于RDBMS中的事务ID,用于标识一次更新操做的Proposal ID。为了保证顺序性,该zkid必须单调递增。所以Zookeeper使用一个64位的数来表示,高32位是Leader的epoch,从1开始,每次选出新的Leader,epoch加一。低32位为该epoch内的序号,每次epoch变化,都将低32位的序号重置。这样保证了zkid的全局递增性。
可经过electionAlg
配置项设置Zookeeper用于领导选举的算法。
到3.4.10版本为止,可选项有
0
基于UDP的LeaderElection1
基于UDP的FastLeaderElection2
基于UDP和认证的FastLeaderElection3
基于TCP的FastLeaderElection在3.4.10版本中,默认值为3,也即基于TCP的FastLeaderElection。另外三种算法已经被弃用,而且有计划在以后的版本中将它们完全删除而再也不支持。
FastLeaderElection选举算法是标准的Fast Paxos算法实现,可解决LeaderElection选举算法收敛速度慢的问题。
每一个服务器在进行领导选举时,会发送以下关键信息
自增选举轮次
Zookeeper规定全部有效的投票都必须在同一轮次中。每一个服务器在开始新一轮投票时,会先对本身维护的logicClock进行自增操做。
初始化选票
每一个服务器在广播本身的选票前,会将本身的投票箱清空。该投票箱记录了所收到的选票。例:服务器2投票给服务器3,服务器3投票给服务器1,则服务器1的投票箱为(2, 3), (3, 1), (1, 1)。票箱中只会记录每一投票者的最后一票,如投票者更新本身的选票,则其它服务器收到该新选票后会在本身票箱中更新该服务器的选票。
发送初始化选票
每一个服务器最开始都是经过广播把票投给本身。
接收外部投票
服务器会尝试从其它服务器获取投票,并记入本身的投票箱内。若是没法获取任何外部投票,则会确认本身是否与集群中其它服务器保持着有效链接。若是是,则再次发送本身的投票;若是否,则立刻与之创建链接。
判断选举轮次
收到外部投票后,首先会根据投票信息中所包含的logicClock来进行不一样处理
选票PK
选票PK是基于(self_id, self_zxid)与(vote_id, vote_zxid)的对比
统计选票
若是已经肯定有过半服务器承认了本身的投票(多是更新后的投票),则终止投票。不然继续接收其它服务器的投票。
更新服务器状态
投票终止后,服务器开始更新自身状态。若过半的票投给了本身,则将本身的服务器状态更新为LEADING,不然将本身的状态更新为FOLLOWING
初始投票给本身
集群刚启动时,全部服务器的logicClock都为1,zxid都为0。
各服务器初始化后,都投票给本身,并将本身的一票存入本身的票箱,以下图所示。
在上图中,(1, 1, 0)第一位数表明投出该选票的服务器的logicClock,第二位数表明被推荐的服务器的myid,第三位表明被推荐的服务器的最大的zxid。因为该步骤中全部选票都投给本身,因此第二位的myid便是本身的myid,第三位的zxid便是本身的zxid。
此时各自的票箱中只有本身投给本身的一票。
更新选票
服务器收到外部投票后,进行选票PK,相应更新本身的选票并广播出去,并将合适的选票存入本身的票箱,以下图所示。
服务器1收到服务器2的选票(1, 2, 0)和服务器3的选票(1, 3, 0)后,因为全部的logicClock都相等,全部的zxid都相等,所以根据myid判断应该将本身的选票按照服务器3的选票更新为(1, 3, 0),并将本身的票箱所有清空,再将服务器3的选票与本身的选票存入本身的票箱,接着将本身更新后的选票广播出去。此时服务器1票箱内的选票为(1, 3),(3, 3)。
同理,服务器2收到服务器3的选票后也将本身的选票更新为(1, 3, 0)并存入票箱而后广播。此时服务器2票箱内的选票为(2, 3),(3, ,3)。
服务器3根据上述规则,无须更新选票,自身的票箱内选票仍为(3, 3)。
服务器1与服务器2更新后的选票广播出去后,因为三个服务器最新选票都相同,最后三者的票箱内都包含三张投给服务器3的选票。
根据选票肯定角色
根据上述选票,三个服务器一致认为此时服务器3应该是Leader。所以服务器1和2都进入FOLLOWING状态,而服务器3进入LEADING状态。以后Leader发起并维护与Follower间的心跳。
Follower重启投票给本身
Follower重启,或者发生网络分区后找不到Leader,会进入LOOKING状态并发起新的一轮投票。
发现已有Leader后成为Follower
服务器3收到服务器1的投票后,将本身的状态LEADING以及选票返回给服务器1。服务器2收到服务器1的投票后,将本身的状态FOLLOWING及选票返回给服务器1。此时服务器1知道服务器3是Leader,而且经过服务器2与服务器3的选票能够肯定服务器3确实获得了超过半数的选票。所以服务器1进入FOLLOWING状态。
Follower发起新投票
Leader(服务器3)宕机后,Follower(服务器1和2)发现Leader不工做了,所以进入LOOKING状态并发起新的一轮投票,而且都将票投给本身。
广播更新选票
服务器1和2根据外部投票肯定是否要更新自身的选票。这里有两种状况
在上图中,服务器1的zxid为11,而服务器2的zxid为10,所以服务器2将自身选票更新为(3, 1, 11),以下图所示。
选出新Leader
通过上一步选票更新后,服务器1与服务器2均将选票投给服务器1,所以服务器2成为Follower,而服务器1成为新的Leader并维护与服务器2的心跳。
旧Leader恢复后发起选举
旧的Leader恢复后,进入LOOKING状态并发起新一轮领导选举,并将选票投给本身。此时服务器1会将本身的LEADING状态及选票(3, 1, 11)返回给服务器3,而服务器2将本身的FOLLOWING状态及选票(3, 1, 11)返回给服务器3。以下图所示。
旧Leader成为Follower
服务器3了解到Leader为服务器1,且根据选票了解到服务器1确实获得过半服务器的选票,所以本身进入FOLLOWING状态。
ZAB协议保证了在Leader选举的过程当中,已经被Commit的数据不会丢失,未被Commit的数据对客户端不可见。
Failover前状态
为更好演示Leader Failover过程,本例中共使用5个Zookeeper服务器。A做为Leader,共收到P一、P二、P3三条消息,而且Commit了1和2,且整体顺序为P一、P二、C一、P三、C2。根据顺序性原则,其它Follower收到的消息的顺序确定与之相同。其中B与A彻底同步,C收到P一、P二、C1,D收到P一、P2,E收到P1,以下图所示。
这里要注意
选出新Leader
旧Leader也即A宕机后,其它服务器根据上述FastLeaderElection算法选出B做为新的Leader。C、D和E成为Follower且以B为Leader后,会主动将本身最大的zxid发送给B,B会将Follower的zxid与自身zxid间的全部被Commit过的消息同步给Follower,以下图所示。
在上图中
通知Follower可对外服务
同步完数据后,B会向D、C和E发送NEWLEADER命令并等待大多数服务器的ACK(下图中D和E已返回ACK,加上B自身,已经占集群的大多数),而后向全部服务器广播UPTODATE命令。收到该命令后的服务器便可对外提供服务。
在上例中,P3未被A Commit过,同时由于没有过半的服务器收到P3,所以B也未Commit P3(若是有过半服务器收到P3,即便A未Commit P3,B会主动Commit P3,即C3),因此它不会将P3广播出去。
具体作法是,B在成为Leader后,先判断自身未Commit的消息(本例中即P3)是否存在于大多数服务器中从而决定是否要将其Commit。而后B可得出自身所包含的被Commit过的消息中的最小zxid(记为min_zxid)与最大zxid(记为max_zxid)。C、D和E向B发送自身Commit过的最大消息zxid(记为max_zxid)以及未被Commit过的全部消息(记为zxid_set)。B根据这些信息做出以下操做
上述操做保证了未被Commit过的消息不会被Commit从而对外不可见。
上述例子中Follower上并不存在未被Commit的消息。但可考虑这种状况,若是将上述例子中的服务器数量从五增长到七,服务器F包含P一、P二、C一、P3,服务器G包含P一、P2。此时服务器F、A和B都包含P3,可是由于票数未过半,所以B做为Leader不会Commit P3,而会经过TRUNC命令通知F删除P3。以下图所示。