本文旨在介绍Zookeeper集群Leader过程及相关算法分析,对Zookeeper数据同步及其他功能涉及有限。
最近部门组织技术分享,分享内容是架构相关的,其中考虑线上集群数据一致性问题。由此引发了Zookeeper是如何实现数据一致的讨论,中间最重要环节就是Zookeeper 集群Leader的选举过程及算法,之前自己也网上搜罗过一些Zookeepre集群Leader选举相关的文章,但多数文章对一些选举的细节讲的都不够深入和透彻,以至于直到这次分享讨论时都没有完全明白整个Leader选举过程,遂以此文终结该问题。
正文开始之前,先介绍一下Zookeeper相关的基本概念及下文中用到的相关术语。
分布式集群中最典型的部署就是Master/Slave 模式(主备模式)和Master/Follower 模式(主从模式),然而Zookeeper并未没有采用上述模式,而是将集群中的服务分为:Leader、Follower 和 Observer 三种角色,每个角色的职责与承担功能略有差异。
Zookeeper集群中服务器具有以下四种状态,分别为:LOOKING、FOLLOWING、LEADING及OBSERVING。
Zookeeper集群常用术语及Leader选举过程中名次解释:
常用术语:
znode:Zookeeper中的节点,数据操作与承载的主体,对zk的操作主要是对znode的操作,根据存活时间可分为:持久节点和临时节点;持久节点的存活时间不依赖于客户端会话,只有客户端在显式执行删除节点操作时节点才消失,
临时节点的存活时间依赖于客户端会话,当会话结束,临时节点将会被自动删除(也可以手动删除),临时节点也不能拥有子节点 ;
ZAB协议(ZooKeeper Atomic Broadcast 原子广播):是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的原子广播协议,是Zookeeper实现分布式数据一致性的基础,基于该协议ZooKeeper 实现了一种主备模式的系统架构来保持集群中各个副本之间的数据一致性,协议有两种基本模式:恢复模式和消息广播模式;
ZAB协议的两种基本模式:
1、整个服务框架在启动或是 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时,ZAB 协议就会进人*恢复模式*并选举产生新的Leader服务器。当选举产生了新的 Leader 服务器,同时集群中已经有过半的机器与该Leader服务器完成了状态(数据)同步之后,ZAB协议就会退出*恢复模式*进入*消息广播模式*。其中,所谓的状态(数据)同步用来保证集群中存在过半的机器能够和Leader服务器的数据状态保持一致。
2、当一台同样遵守ZAB协议的服务器启动后加入到集群中时,如果此时集群中已经存在一个Leader服务器在负责进行消息广播,那么新加入的服务器就会进人数据恢复模式(Leader服务器暂停更新):找到Leader所在的服务器,并与其进行数据同步,数据同步完成后进入消息广播模式。
3、为了实现数据一致性,ZooKeeper设计成只允许唯一的一个Leader服务器来进行事务请求的处理。Leader服务器在接收到客户端的事务请求后,会生成对应的事务提案(zxid)并发起一轮广播协议,决定是否进行事务处理;而集群中的Follower接收到客户端的事务请求时,会将这个事务请求转发给Leader服务器,由Leader发起广播协议进而处理事务请求。
Leader选举名词:
Zookeeper集群Leader选取过程中主要有3中选举算法:
LeaderElection:LeaderElection是Fast Paxos最简单的一种实现,每个Server启动以后都询问其它的Server它要投票给谁,收到所有Server回复以后,就计算出zxid最大的哪个Server,并将这个Server相关信息设置成下一次要投票的Server。该算法于Zookeeper 3.4以后的版本废弃;
a.选举线程首先向所有Server发起一次询问(包括自己);
b.选举线程收到回复后,验证是否是自己发起的询问(验证zxid是否一致),然后获取对方的sid,并存储到当前询问对象列表中,最后获取对方提议的leader相关信息(sid,zxid),并将这些信息存储到当次选举的投票记录表中;
c.收到所有Server回复以后,就计算出zxid最大的那个Server,并将这个Server相关信息设置成下一次要投票的Server;
d.线程将当前zxid最大的Server设置为当前Server要推荐的Leader,如果此时获胜的Server获得多数Server票数, 将当前获胜的Server设置为Leader,其他Server根据Leader相关信息设置自己的状态(数据),否则,继续这个过程,直到leader被选举出来;
异常问题的处理:
a.选举过程中,新Server的加入:
新Server启动后它都会发起一次选举投票,由选举线程发起相关流程,该Server会获得当前zxid最大的Server,如果当次最大的Server没有获得不低于 n/2+1 (n为所有的Server数量)的票数,那么下一次投票时,该Server将Leader选举投票给zxid最大的Server,重复以上流程,最后一定能选举出一个Leader。
b.选举过程中,Server的退出:
只要保证集群中至少有n/2+1个Server是正常的就没有任何问题,如果少于n/2+1个Server存活,那么该集群就不能正常提供服务(Zookeeper集群协议决定),也就没有必要进行Leader选举了。
c.选举过程中,Leader死亡:
选举出Leader以后,每个Server的状态(FLLOWING)都已经确定,如果此时Leader死亡,Fllower都会向Leader发送Ping消息,检查Leader状态,以便数据同步,如果无法ping通,就改变自己的状为(FLLOWING ==> LOOKING),发起新的一轮选举。
d.双主问题:
Leader的选举是保证有且只有一个Leader产生,而且Follower重新选举与旧Leader恢复并退出基本上是同时发生的,当有一半以上Follower无法Ping通Leader时是就会认为Leader已经出问题开始重新选举。
FastLeaderElection:由于LeaderElection收敛速度较慢(需要等n台Server全部都收到其他对应的n-1台Server回复自己信息及自己投票信息后才进行下一轮选举,一般2-3轮投票能选出Leader),所以Zookeeper引入了FastLeaderElection选举算法,FastLeaderElection也是Zookeeper默认的Leader选举算法。FastLeaderElection是标准的Fast Paxos的实现,它首先向所有Server提议自己要成为Leader,当其它Server收到提议以后,比较投票中 sid 和 zxid 的值,并决定是否接受对方的提议,然后向对方发送接受提议完成的消息。FastLeaderElection算法通过异步的通信方式来收集其它节点的选票,同时在分析选票时又根据投票者的当前状态来作不同的处理,以加快Leader的选举进程;
A.发起一轮投票选举,推举自己作为Leader,通知所有的服务器,等待接收外部选票;
B.只要当前服务器状态为LOOKING,进入循环,不断地读取其它Server发来的通知、进行比较、更新自己的投票、发送自己的投票、统计投票结果,直到Leader选出或出错退出;具体实现如下:
从队列中取出一个Notification(选票),则根据消息中对方的状态进行相应的处理:
1.LOOKING状态:
a.如果其他Server发送过来Notification的逻辑时钟大于当前的逻辑时钟,说明这是一次新的选举投票,此时更新本机的逻辑时钟(logicalclock),清空投票箱(数据已经过期),判断Notification是否优于当前本机的投票,是的话用对方推荐的Leader更新下一次的投票,否则使用本机的投票(投自己),通知其它Server我的投票,跳到d;
b.如果对方处于上轮投票,不予理睬,回到B;
c.如果对方也处于本轮投票,判断对方的投票是否优于当前的投票,是的话更新当前的投票,否则使用初始的投票(投自己)并新生成Notification消息放入发送队列。通知其它Server我的投票
d.将收到的投票放入自己的投票箱中。
e.判断所推荐的Leader是否得到集群多数人的同意(根据计票器的实现不同,可以是单纯看数量是否超过n/2,也可以是按权重来判断,我们这里假设单纯看数量),如果得到多数人同意,那么还需等待一段时间,看是否有比当前更优的提议,如果没有,则认为投票结束。根据投票结果修改自己的状态。以上任何一条不满足,则继续循环。
2.OBSERVING状态:不做任何事;
3.FOLLOWING或LEADING状态:
a.如果选举周期相同(选票是同一轮选举产生),将该数据保存到投票箱,根据当前投票箱的投票判断对方推荐的Leader是否得到多数人的同意,如果是则设置状态退出选举过程,否则到b;
b.这是一条与当前逻辑时钟不符合的消息,或者对方推荐的Leader没有得到多数人的同意(有可能是收集到的投票数不够),那么说明可能在另一个选举过程中已经有了选举结果,于是将该选举结果加入到outofelection集合中,再根据outofelection来判断是否可以结束选举,如果可以也是保存逻辑时钟,设置状态,退出选举过程。否则继续循环。outofelection用于保存那些状态为FOLLOWING或者LEADING的ZooKeeper节点发送的选票,由于对方的状态为FOLLOWING或者LEADING,所以它们当前不参与选举过程(可能人家已经选完了),因此称为“out of election”;
AuthFastLeaderElection:AuthFastLeaderElection算法同FastLeaderElection算法基本一致,只是在消息中加入了认证信息,该算法在最新的Zookeeper中也建议弃用,这里就不做过多的介绍了,有需要的可自行网上查阅了解;
上面介绍了一些Zookeeper相关的知识,特别是Leader选举算法中的FastLeaderElection算法,实现起来比较复杂,看完之后也未必能够明白,下面重点通过实例的方式对该算法进行讲解说明。
Leader是保证分布式数据一致性的关键所在,所有Follower服务器都需要从Leader同步数据,如果集群中Leader不存在将无法保证集群中服务器上的数据一致性,Zookeeper也就失去了其存在的价值与意义。
Leader选举一般有两种情况:服务器初始化启动和服务器运行期间无法和Leader保持连接,下面分别针对这两种情况进行说明:
若进行Leader选举,则至少需要3台机器(否则无法达到Leader收到投票过半数的要求),假设服务器集群中有5台机器(编号依次为Server1-Server5),在集群初始化阶段,所有服务器均启动完成后,此时任意两台机器都可以相互通信,每台机器都试图找到Leader,于是就进入Leader选举过程。
选举过程如下:
1. 各个Server发起投票。每个Server将自己作为Leader服务器来进行投票,每次投票会包含所推举的服务器的myid(sid)和zxid,使用(myid, zxid)来表示,此时Server1的投票为(1, 0),Server2的投票为(2, 0),Server3的投票为(3, 0),Server4的投票为(4, 0),Server5的投票为(5, 0),然后各自将这个投票发给集群中其他机器;
2. 接受来自其他Server的投票。集群的每个服务器收到其他Server的投票后,首先判断该投票的有效性,如检查是否是本轮投票(基于zxid)、是否来自LOOKING状态的服务器等;
3. 处理投票。针对每一个接收到的投票,服务器都需要将其他Server的投票和自己的投票进行PK,PK规则如下:
a. 比较zxid,zxid大的投票信息保留,并作为下一轮的投票信息;
b.zxid相同比较myid(sid),myid(sid)大的投票信息保留,并作为下一轮的投票信息;
对于Server1而言,它自己的投票是(1, 0),接收到的投票为(2, 0)、(3, 0)、(4, 0)、(5, 0),规矩比较规则,会更新自己的投票为(5, 0),然后重新投票;对于Server3、Server4同理也会更新自己的投票为(5,0);对Server5而言,其无须更新自己的投票,只是再次向集群中所有机器发出上一次投票信息即可。
4. 统计投票结果。每次投票后,服务器都会统计投票信息,判断是否已经有过半机器接受到相同的投票信息,对于第二轮投票Server1、Server2、Server3而言,都统计出集群中已经有3台机器接受了(5, 0)的投票信息,此时便认为已经选出了Leader 为 Server5;
5. 改变服务器状态。一旦确定了Leader,每个服务器就会更新自己的状态,如果是Follower,那么就变更为FOLLOWING,如果是Leader,就变更为LEADING;
在Zookeeper运行期间,Leader与Follower服务器各司其职,即便当有Follower服务器宕机或新加入,此时也不会影响Leader,但是一旦Leader服务器挂了,那么整个集群将暂停对外服务,进入新一轮Leader选举,其过程和启动时期的Leader选举过程基本一致。假设Zookeepre集群中正在运行的服务器有Server1、Server2、Server3、Server4、Server5,当前Leader是Server5,若某一时刻Server5挂了,此时便开始新Leader的选举。选举过程如下:
1. 变更状态。Leader挂后,余下的Follower服务器都会将自己的服务器状态变更为LOOKING,然后开始进入Leader选举;
2. 每个Server会发出一个投票。在运行期间,每个服务器上的zxid可能不同,此时假定Server1的zxid为121,Server2的zxid为122,Server3的zxid为122,Server4的zxid为121;在第一轮投票中,Server1、Server2、Server3、Server4都会投自己,产生投票(1, 121),(2, 122),(3, 122),(4, 121),然后各自将投票发送给集群中所有机器;
3. 处理投票。针对每一个接收到的投票,服务器都需要将其他Server的投票和自己的投票进行PK,PK规则如下:
a. 比较zxid,zxid大的投票信息保留,并作为下一轮的投票信息;
b.zxid相同比较myid(sid),myid(sid)大的投票信息保留,并作为下一轮的投票信息;
对于Server1而言,它自己的投票是(1, 121),接收到的投票为(2, 122)、(3, 122)、(4, 121),规矩比较规则,会更新自己的投票为(3,122),然后重新投票;对于Server2、Server4同理也会更新自己的投票为(3,122);对Server3而言,其无须更新自己的投票,只是再次向集群中所有机器发出上一次投票信息即可。
4. 统计投票结果。每次投票后,服务器都会统计投票信息,判断是否已经有过半机器接受到相同的投票信息,对于第二轮投票Server1、Server2、Server4而言,都统计出集群中已经有3台机器接受了(3,122)的投票信息,此时便认为已经选出了Leader位Server3;
5. 改变服务器状态。一旦确定了Leader,每个服务器就会更新自己的状态,如果是Follower,那么状态会有LOOKING变更为FOLLOWING,如果是Leader,就变更为LEADING;
Zookeeper实现数据一致性的核心是ZAB协议,该协议需要做到以下几点:
Zab协议有两种模式: 崩溃恢复(选主+数据同步)和消息广播(事务操作),任何时候都需要保证只有一个主进程负责进行事务操作,而如果主进程崩溃了,就需要迅速选举出一个新的主进程,主进程的选举机制与事务操作机制是紧密相关的。
1、选主后数据同步:选主算法中的zxid是从内存数据库中取的最新事务id,事务操作分为两个阶段:提出阶段和提交阶段,leader生成提议并广播给followers,收到半数以上的ack后,再广播commit消息,同时将事务操作应用到内存中。follower收到提议后先将事务写到本地事务日志,然后反馈ack,等接到leader的commit消息时,才会将事务操作应用到内存中。选主其实只是选出了内存数据是最新的节点,仅仅靠这个是无法保证已经在leader服务器上提交的事务最终被所有服务器都提交。比如leader发起提议p1,并收到半数以上follower关于p1的ack后,在广播commit消息之前宕机了,选举产生的新leader之前是follower,未收到关于p1的commit消息,内存中是没有p1的数据。而ZAB协议的设计是需要保证选主后,p1是需要应用到集群中的。这块的逻辑是通过选主后的数据同步来弥补。
选主后,节点需要切换状态,leader切换成LEADING状态后的流程如下:
2、事务操作:ZAB协议对于事务操作的处理是一个类似于二阶段提交过程。针对客户端的事务请求,leader服务器会为其生成对应的事务proposal,并将其发送给集群中所有follower机器,然后收集各自的选票,最后进行事务提交。流程如下图:
ZAB协议的二阶段提交过程中,移除了中断逻辑(事务回滚),所有follower服务器要么正常反馈leader提出的事务proposal,要么就抛弃leader服务器。follower收到proposal后的处理很简单,将该proposal写入到事务日志,然后立即反馈ack给leader,也就是说如果不是网络、内存或磁盘等问题,follower肯定会写入成功,并正常反馈ack。leader收到过半follower的ack后,会广播commit消息给所有learner,并将事务应用到内存;learner收到commit消息后会将事务应用到内存。
1.https://segmentfault.com/a/1190000016349824
2.https://juejin.im/post/5b949d595188255c6a041c22
3.https://www.cnblogs.com/hongdada/p/8145075.html
4.https://blog.csdn.net/zhengzhihust/article/details/53456371
5.https://zhuanlan.zhihu.com/p/25594630