本文为书籍《从Paxos到Zookeeper 分布式一致性原理与实践》倪超著_北京:电子工业出版社的读书笔记,这本书还是蛮值得推荐的。
一、服务器启动时期的Leader选举
假设Server1 (myId 1)这台启动了,它是无法进行Leader选举的,要等到Server2 (myId 2)启动后,这两台机器能够进行相互通信,每台机器试图找到一个Leader,于是进入Leader选举流程
1.每个Server会发出一个投票
由于是初始情况,因此对于Server1和Server2来说,都会将自己作为Leader服务器来进行投票。
每次投票包含的最基本的元素是:所推举的服务器的myid和ZXID。我们以(myid,ZXID)的形式来表示。
即Server1投票为(1,0),Server2投票为(2,0)。
然后各自将这个投票发给集群中其他所有机器。
2.接收来自各个服务器的投票
每个服务器都会接受来自其他服务器的投票。集群中的每个服务器在收到投票后,首先会判断该投票的有效性,包括检查是否是本轮投票,是否来自LOOKING状态的服务器。
3.处理投票
在接收到来自其他服务器的投票后,针对每一个投票,服务器都需要将别人的投票和自己的投票进行PK,PK的规则如下:
优先检查ZXID。ZXID比较大的服务器优先作为Leader。
如果ZXID相同的话,那么就比较myid。myid比较大的服务器作为Leader服务器。
现在我们来看server1和server2实际是如何进行投票处理的。
对于server1来说,它自己的投票是(1,0),而收到的投票是(2,0)。
首先会对比两者的ZXID,因为都是0,所以无法决定谁是Leader。
接下来对比两者的myid,Server1收到的投票中myid是2,大于自己,于是会更新自己的投票为(2,0),然后重新发出去。
对于server2来说,不需要更新自己的投票信息,只是再一次向集群中所有机器发出上一次投票信息(2,0)即可。
4.统计投票
每次投票后,服务器都会统计所有投票,判断是否已有过半的机器接收到相同的投票信息。
对于server1和server2服务器来说,都统计出集群中已经有两台机器接受了(2,0)这个投票信息。此时已过半。认为已经选举除了Leader为server2
5.改变服务器状态
一旦确定了Leader,每个服务器就会更新自己的状态:如果是Follower那么就变更为FOLLOWING,如果是Leader那么就变更为LEADING
二、服务器运行期间的Leader选举
在Zookeeper集群正常运行过程中,一旦选举出一个Leader那么所有服务器的集群角色一般不会发生变化。
但是,如果Leader所在的服务器挂了,那么整个集群将暂时无法对外服务,并且进入新一轮的Leader选举。
服务器运行期间的Leader选举和启动时期的Leader选举基本过程一致。
我们假设当前正在运行的Zookeeper服务器有3台机器组成,分别是Server1、Server2和Server3,当前Leader是Server2。假设在某一瞬间Leader挂了,这个时候便开始了Leader选举
1.变更状态
当Leader挂了之后,余下的非Observer服务器都会将自己的服务器状态变更为LOOKING,然后开始进入Leader选举流程。
2.每个Server会发出一个投票
在这个过程中,需要生成投票信息(myid,ZXID)。
因为是运行期间,因此每个服务器上的ZXID可能不同,我们假定Server1的ZXID为123,而Server3的ZXID为122。
在第一轮投票中,Server1和Server3都会投自己,即分别产生投票(1,123)和(3,122),然后各自将这个投票发给集群中所有机器。
3.接收来自各个服务器的投票
4.处理投票
对于Server1来说,收到了Server3(3,122),跟自己的(1,123)比较,先比较ZXID,发现自己的123>server3的122,所以Server1再一次向集群中所有机器发出上一次投票信息(1,123)
对于Server3来说,收到了server1 (1,123),跟自己的(3,122),先比较ZXID,发现自己的122<server1的123,所以更新自己的投票为(1,123),然后再发出去
5.统计投票
每次投票后,服务器都会统计所有投票,判断是否已有过半的机器接收到相同的投票信息。
对于server1和server3服务器来说,都统计出集群中已经有两台机器接受了(1,123)这个投票信息。此时已过半。认为已经选举除了Leader为server1
6.改变服务器状态
一旦确定了Leader,每个服务器就会更新自己的状态:如果是Follower那么就变更为FOLLOWING,如果是Leader那么就变更为LEADING
三、ZAB协议
ZooKeeper使用的是ZAB协议作为数据一致性的算法, ZAB(ZooKeeper Atomic Broadcast ) 全称为:原子消息广播协议。
在Paxos算法基础上进行了扩展改造而来的,ZAB协议设计了支持原子广播、崩溃恢复,ZAB协议保证Leader广播的变更序列被顺序的处理。
zookeeper根据ZAB协议建立了主备模型保证zookeeper集群中个副本之间数据一致性。
ZAB协议中存在着三种状态,每个节点都属于以下三种中的一种:
Looking :系统刚启动时或者Leader崩溃后正处于选举状态
Following :Follower节点所处的状态,Follower与Leader处于数据同步阶段;
Leading :Leader所处状态,当前集群中有一个Leader为主进程;
1.协议核心
所有事务请求必须由一个全局唯一的服务器来协调处理,这样的服务器被称为Leader服务器,而余下的其他服务器称为Follower服务器。
Leader服务器负责将一个客户端事务请求转换成一个事务Proposal(提议),并将该Proposal分发给集群中所有的Follower服务器。
之后Leader服务器需要等待所有的Follower服务器的反馈,一旦超过半数的Follower服务器进行了正确的反馈后,那么Leader就会再次向所有的Follower服务器分发Commit消息,要求其将前一个Proposal进提交。
2.协议阶段
ZAB协议整体可划分为两个基本的模式:消息广播和崩溃恢复
按协议原理可细分为四个阶段:选举(Leader Election)、发现(Discovery)、同步(Synchronization)和广播(Broadcast)
按协议实现分为三个时期:选举(Fast Leader Election)、恢复(Recovery Phase)和广播(Broadcast Phase)
ZAB选举(election)时当Follower存在ZXID(事务ID)时判断所有Follower节点的事务日志,只有lastZXID的节点才有资格成为Leader,这种情况下选举出来的Leader总有最新的事务日志,
基于这个原因所以ZooKeeper实现的时候把 发现(discovery)与同步(sync)合并为恢复(recovery) 阶段;
Election :在Looking状态中选举出Leader节点,Leader的lastZXID总是最新的;
Discovery :Follower节点向准Leader推送FOllOWERINFO,该信息中包含了上一周期的epoch,接受准Leader的NEWLEADER指令,检查newEpoch有效性,准Leader要确保Follower的epoch与ZXID小于或等于自身的;
sync :将Follower与Leader的数据进行同步,由Leader发起同步指令,最总保持集群数据的一致性;
Broadcast :Leader广播Proposal与Commit,Follower接受Proposal与Commit;
Recovery :在Election阶段选举出Leader后本阶段主要工作就是进行数据的同步,使Leader具有highestZXID,集群保持数据的一致性;
3.消息广播模式
在zookeeper集群中数据副本的传递策略就是采用消息广播模式。
针对客户端的事务请求,Leader服务器会为其生成对应的事务提议Proposal,并将该Proposal分发给集群中所有的Follower服务器。
然后leader再分别收集各自的选票,超过半数的Follower服务器进行了正确的反馈后,那么Leader就会再次向所有的Follower服务器分发Commit消息,要求其将前一个Proposal进提交。
消息广播过程
1.Leader服务器会给每一个Follower服务器都各自分配一个单独的队列,然后将需要广播的事务Proposal依次放入这些队列中去,并且根据FIFO策略进行消息发送。
2.每一个Follower服务器在接收到这个事务Proposal之后,都会首先将其以事务日志的形式写入到本地磁盘中去,并且在写入成功以后反馈给leader服务器一个ACK响应。
3.当Leader服务器收到超过半数Follower的ACK响应后,就会广播一个Commit消息给所有的Follower服务器已通知其进行事务提交,同时Leader自身也会完成对事务的提交。
4.每一个Follower服务器在接收到Commit消息后,也会完成对事务的提交。
如果保证事务处理的先后顺序
在广播事务Proposal之前,Leader服务器会首先为这个事务Proposal分配一个全局单调递增的唯一ID,我们称之为事务ID(即ZXID)。
由于ZAB协议需要保证每一个消息严格的因果关系,因此必须将每一个事务Proposal按照其ZXID的先后顺序进行排序和处理。
zxid —— 最新事务ID
高32位是Leader的epoch,从1开始,每次选出新的Leader,epoch加一;
低32位为该epoch内的序号,每次epoch变化,都将低32位的序号重置;
保证了zkid的全局递增性。
ZAB消息广播与二阶段提2PC的区别
2PC的要求协调者必须等到所有的参与者全部反馈ACK确认消息后,再发送commit消息。要求所有的参与者要么全部成功要么全部失败。二阶段提交会产生严重阻塞问题。
ZAB协议中Leader等待follower的ACK反馈是指”只要半数以上的follower成功反馈即可,不需要收到全部follower反馈”
在这种简化的二阶段提交模型下,无法处理Leader服务器崩溃退出而带来的数据不一致问题,因此ZAB协议添加了崩溃恢复模式来解决这个问题
4.崩溃恢复模式
zookeeper集群中为保证任何所有进程能够有序的顺序执行,只能是leader服务器接受写请求,即使是follower服务器接受到客户端的请求,也会转发到leader服务器进行处理。
如果leader服务器发生崩溃,则zab协议要求zookeeper集群进行崩溃恢复和leader服务器选举。
ZAB协议崩溃恢复要求满足如下2个要求
1. 确保已经被leader提交的proposal必须最终被所有的follower服务器提交。
2. 确保丢弃那些只在leader服务器上被提出的proposal。
结合上面提交的这两个崩溃恢复过程中需要处理的特色情况,就决定了ZAB协议必须设计这样一个Leader选举算法:
能够确保提交已经被Leader提交的事务Proposal,同时丢弃已经被跳过的事务Proposal。
针对这个要求,如果让Leader选举算法能够保证新选举出来的Leader服务器拥有集群中所有机器最高编号(即ZXID最大)的事务Proposal,那么就可以保证这个新选举出来的Leader具有所有已经提交的Proposal。
这样还可以省去leader服务器检查proposal的提交和丢弃工作这一操作。
5.数据同步
1.在zookeeper集群中新的leader选举成功之后,leader会将自身的提交的最大proposal的事物ZXID发送给其他的follower节点。
follower节点会根据leader的消息进行回退或者是数据同步操作。
最终目的要保证集群中所有节点的数据副本保持一致。
2.数据同步完之后,zookeeper集群如何保证新选举的leader分配的ZXID是全局唯一呢?
这个跟ZXID的设计相关:
ZXID是一个长度64位的数字,其中低32位是按照数字递增,即每次客户端发起一个proposal,低32位的数字简单加1。
高32位是leader周期的epoch编号,每当选举出一个新的leader时,新的leader就从本地事物日志中取出ZXID,然后解析出高32位的epoch编号,进行加1,再将低32位的全部设置为0。
这样就保证了每次新选举的leader后,保证了ZXID的唯一性而且是保证递增的。
6.Zab与Paxos
1.Paxos算法的确是不关心请求之间的逻辑顺序,而只考虑数据之间的全序,但很少有人直接使用paxos算法,都会经过一定的简化、优化。
2.Paxos算法在出现竞争的情况下,其收敛速度很慢,甚至可能出现活锁的情况,例如当有三个及三个以上的proposer在发送prepare请求后,很难有一个proposer收到半数以上的回复而不断地执行第一阶段的协议。
因此,为了避免竞争,加快收敛的速度,在算法中引入了一个Leader这个角色,在正常情况下同时应该最多只能有一个参与者扮演Leader角色,而其它的参与者则扮演Acceptor的角色。
在这种优化算法中,只有Leader可以提出议案,从而避免了竞争使得算法能够快速地收敛而趋于一致;
而为了保证Leader的健壮性,又引入了Leader选举,再考虑到同步的阶段,渐渐的你会发现对Paxos算法的简化和优化已经和上面介绍的ZAB协议很相似了。