概述
ZooKeeper Atomic Broadcast,ZooKeeper原子消息广播协议。ZAB 协议是为分布式协调服务ZK专门设计的一种支持崩溃恢复的原子广播协议。ZK主要依赖 ZAB 协议来实现分布式数据一致性,基于该协议,ZK实现一种主备模式的系统架构来保持集群中各个副本之间的数据一致性。
概念
集群角色
- Leader:同一时间集群总只允许有一个Leader,提供对客户端的读写功能,负责将数据同步至各个节点;
- Follower:提供对客户端读功能,写请求则转发给Leader处理,当Leader崩溃失联之后参与Leader选举;
- Observer:与Follower不同的是但不参与Leader选举
运行时状态
进程的可能状态:
- LOOKING:Leader选举状态,正在寻找Leader
- FOLLOWING:当前节点是Follower。与Leader服务器保持同步状态
- LEADING:当前节点是Leader,作为主进程领导状态
- OBSERVING:observer角色
ZAB状态
ZK给ZAB定义的4种状态,反应ZK从选举到对外提供服务的过程中的四个步骤。状态枚举源码:
public enum ZabState {
ELECTION,
DISCOVERY,
SYNCHRONIZATION,
BROADCAST
}
- ELECTION:集群进入选举状态,此过程会选出一个节点作为leader角色;
- DISCOVERY:连接上leader,响应leader心跳,并且检测leader的角色是否更改,通过此步骤之后选举出的leader才能执行真正职务;
- SYNCHRONIZATION:整个集群都确认leader之后,将会把leader的数据同步到各个节点,保证整个集群的数据一致性;
- BROADCAST:过渡到广播状态,集群开始对外提供服务。
Zxid
该协议主要通过唯一的事务编号Zxid(ZooKeeper Transaction id)保障集群状态的唯一性。Zxid与RDBMS中的事务id类似,用于标识一次提议(Proposal)的id;为了保证顺序性,Zxid必须单调递增,保证全局有序。
Epoch:Epoch指当前集群的周期号(年代号),集群的每次Leader变更都会产生一个新的周期号,周期号的产生规则是在上一个周期号的基础上加1,这样当之前的Leader崩溃恢复后会发现自己的周期号比当前的周期号小,说明此时集群已经产生新的Leader,旧的Leader会再次以Follower的角色加入集群。
Zxid指ZAB协议的事务编号,一个64位的数字,低32位存储的是一个简单的单调递增的计数器,针对客户端的每一个事务请求,计数器都加1。高32位存储的是Leader的周期号Epoch。每次选举产生一个新的Leader时,该Leader都会从当前服务器的日志中取出最大事务的Zxid,获取其中高32位的Epoch值并加1,以此作为新的Epoch,并将低32位重置为0,重新开始计数。
这样设计的好处是旧的Leader挂了后重启,它不会被选举为 leader,因为此时它的Zxid肯定小于当前的新Leader。当旧的Leader作为 follower 接入新的Leader后,新的Leader会让它将所有的拥有旧的 epoch 号的未被 COMMIT 的 proposal 清除。
模式
ZAB协议有两种模式,分别是崩溃恢复模式(集群选主)和消息广播模式(数据同步):
恢复模式
Recovery,当集群启动、集群重启、网络中断、Leader崩溃后,集群将开始选主,该过程为崩溃恢复模式。当选举产生新的Leader服务器,同时集群中已经有过半的机器与该Leader服务器完成状态(数据)同步之后,ZAB协议会退出恢复模式。在ZAB协议中,为保证程序的正确运⾏,整个恢复过程结束后需要选举出⼀个新的Leader 服务器。Leader选举算法不仅仅需要让Leader⾃身知道已经被选举为Leader,同时还需要让集群中的所有其他机器也能够快速感知到选举产⽣出来的新Leader服务器。
广播模式
Boardcast,广播的过程实际上是一个简化的二阶段提交过程。
当Leader被选举出来后,Leader将最新的集群状态广播给其他Follower,该过程为广播模式。在半数以上的Follower完成与Leader的状态同步后,广播模式结束。
当集群中有过半的Follower服务器完成和Leader服务器的状态同步,整个服务框架就可以进入 消息广播模式 。
当一台遵守ZAB协议的服务器启动后加入到集群中,如果此时集群中已经存在一个Leader服务器在负责进行消息广播,那么加入的服务器会自觉的进入 数据恢复模式:找到Leader 所在的服务器,并与其进⾏数据同步,数据同步完成后参与到消息⼴播流程中。
ZAB协议的消息广播使用原子广播协议, 类似一个二阶段提交的过程 ,但又有所不同:
- 二阶段提交中,需要所有参与者反馈ACK后再发送Commit请求。要求所有参与者要么成功,要么失败。这样会产生严重的阻塞问题
- ZAB协议中,Leader等待半数以上的Follower成功反馈ACK即可,不需要收到全部的Follower反馈ACK。
消息广播过程:
- 客户端发起写请求
- Leader将客户端请求信息转化为事务Proposal,同时为每个Proposal分配一个事务ID
- Leader为每个Follower单独分配一个FIFO的队列,将需要广播的Proposal依次放入到队列中
- Follower接收到Proposal后,首先将其以事务日志的方式写入到本地磁盘中,写入成功后给Leader反馈一个ACK响应
- Leader接收到半数以上Follower的ACK响应后,即认为消息发送成功,可以发送Commit消息
- Leader向所有Follower广播Commit消息,同时自身也会完成事务提交。Follower接收到Commit消息后也会完成事务的提交
状态切换
-
启动时的状态转换
- 所有进程的初始状态都是LOOKING状态,此时不存在Leader
- 接下来,进程会试图选举出来一个新的Leader,Leader切换为LEADING状态,其它进程发现已经选举出新的Leader,那么它就会切换到FOLLOWING状态,并开始与Leader保持同步
- 处于FOLLOWING状态的进程称为Follower,LEADING状态的进程称为Leader
- 当Leader崩溃或者放弃领导地位时,其余Follower进程就会切换到LOOKING状态开始新一轮的Leader选举
-
运行过程中的状态转换
- 一个Follower只能和一个Leader保持同步,Leader进程和所有的Follower进程之间通过心跳监测机制来感知彼此的情况
- 若Leader能够在超时时间内正常的收到心跳检测,那么Follower就会一直与该Leader保持连接
- 如果在指定时间内Leader无法从过半的Follower进程那里接收到心跳检测,或者TCP连接断开,那么Leader会放弃当前周期的领导,并转换为LOOKING状态;其他的Follower也会选择放弃这个Leader,同时转换为LOOKING状态,之后会进行新一轮的Leader选举
- 选举阶段(Leader Election):在集群选举开始时,所有节点都处于选举阶段。当某一个节点的票数超过半数节点后,该节点将被推选为准Leader。选举阶段的目的就是产生一个准Leader。只有到达广播阶段后,准Leader才会成为真正的Leader
- 发现阶段(Discovery):在发现阶段,各个Follower开始和准Leader进行通信,同步Follower最近接收的事务提议。这时,准Leader会产生一个新的Epoch,并尝试让其他Follower接收该Epoch后再更新到本地。发现阶段的一个Follower只会连接一个Leader,如果节点1认为节点2是Leader,则当节点1尝试连接节点2时,如果连接被拒绝,则集群会进入重新选举阶段。发现阶段的主要目的是发现当前大多数节点接收的最新提议
- 同步阶段(Synchronization):同步阶段主要是将Leader在前一阶段获得的最新提议信息同步到集群中所有的副本,只有当半数以上的节点都同步完成时,准Leader才会成为真正的Leader。Follower只会接收Zxid比自己的lastZxid大的提议。同步阶段完成后集群选主的操作才完成,新的Leader将产生
- 广播阶段(Broadcast):在广播阶段,ZooKeeper集群开始正式对外提供事务服务,这时Leader进行消息广播,将其上的状态通知到其他Follower,如果后续有新的节点加入,则Leader会对新节点进行状态的同步
ZAB协议的Java实现与其定义略有不同,在实际实现时,选举阶段采用Fast Leader Election模式。在该模式下,节点首先向所有Server提议自己要成为Leader,当其他Server收到提议以后,判断Epoch信息并接收对方的提议,然后向对方发送接收提议完成的消息;同时,在Java的实现过程中将发现阶段和同步阶段合并为恢复阶段。因此,ZAB协议的Java实现只有3个阶段:FastLeader Election、Recovery和Broadcast。
ZK选举机制每个Server首先都提议自己是Leader,并为自己投票,然后将投票结果与其他Server的选票进行对比,权重大的胜出,使用权重较大的选票更新自身的选票箱,具体选举过程:
- 每个Server启动以后都询问其他Server给谁投票,其他Server根据自己的状态回复自己推荐的Leader并返回对应的Leader id和Zxid。在集群初次启动时,每个Server都会推荐自己为Leader
- 当Server收到所有其他Server的回复后,计算出Zxid最大的Server,并将该Server设置成下一次要投票推荐的Server
- 计算过程中票数最多的Server将成为获胜者,如果获胜者的票数超过集群个数的一半,则该Server将被推选为Leader。否则,继续投票,直到Leader被选举出来
- Leader等待其他Server连接
- Follower连接Leader,将最大的Zxid发送给Leader
- Leader根据Follower的Zxid确定同步点,至此,选举阶段完成。
在选举阶段完成后,Leader通知其他Follower集群已经成为Uptodate状态,Follower收到Uptodate消息后,接收Client的请求并开始对外提供服务。
问答主从架构下,Leader崩溃,数据一致性怎么保证?
Leader崩溃后,集群会选出新的Leader,进入恢复阶段,新Leader具有所有已经提交的提议,因此它会保证让Followers同步已提交的提议,丢弃未提交的提议(以Leader的记录为准),保证整个集群的数据一致性。
选举Leader时,整个集群无法处理写请求的,如何快速进行 leader 选举?
通过 Fast Leader Election 实现的,Leader选举只需要超过半数的节点投票即可,这样不需要等待所有节点的选票,能够尽早选出Leader。