ZAB协议
ZAB协议简介
ZAB协议是Fast Paxos算法中的一种工业实现算法。ZAB,即Zookeeper Atomic Broadcast,zk原子消息广播协议,是专为Zookeeper设计的一种支持崩溃恢复的原子广播协议,在Zookeeper中,主要依赖ZAB协议来实现分布式数据的一致性。
Zookeeper使用一个单一进程来接受并处理客户端的所有事务请求(写请求),当服务器数据的状态发生变更后,集群中采用ZAB原子广播协议,以事务提案Proposal的形式广播到所有的副本进程上,ZAB协议能够保证一个全局的变更序列,即可以为每一个事务分配一个全局的递增编号xid。
当Zookeeper客户端连接到Zookeeper集群的一个节点后,若客户端提交的是读请求,那么当前节点就直接根据自己保存的数据对其进行响应(Leader,Follower和Observer都是可以处理读请求的);如果是写请求并且当前节点不是Leader,那么节点就会将写请求转发给Leader,Leader会以提案的方式广播该写操作,只要超过半数节点同意该写操作,则该写操作请求就会被提交,然后Leader会再次广播给所有订阅者,即Learner,通知它们同步数据。
三类角色
为了避免Zookeeper的单点问题,zk也是以集群的形式出现的,zk集群中的角色主要有以下三类:
-
Leader:zk集群写请求的唯一处理者,并且负责进行投票的发起和决议,更新系统状态。Leader是很民主的,并不是说其在接受到写请求之后马上就修改其中保存的数据,而是首先根据写请求提出一个提案,在大都数zkServer均同意的情况下才会做出修改。 -
Follower:接受客户端请求,处理读请求,并向客户端返回结果,将写请求转发给Leader,在选Leader和决议Leader发出的写操作的Proposal中参与投票,注意,Follower是具有投票权的。 -
Observer:可以理解为无选主投票权与写操作投票权的Server,其不属于法定人数范畴,主要是为了协助Follower处理更多的读请求。
以上三类角色在不同的情况下又有一些不同的叫法。
-
Learner:学习者,要从Leader中同步数据的Server,在集群处于正常服务状态时,Follower和Observer相对于Leader来说,都是Learner,即Learner=Follower+Observer -
QuorumServer:法定服务器,具有投票权限的主机。在Leader选举过程中,各个参与选举与被选举的主机都称为QuorumServer。QuorumServer不包含Observer,在选举结束后,这些QuorumServer中会产生一个Leader,其他的就成为了Follower了,即QuorumServer=Leader+Follower。
三种模式
ZAB协议中对zkServer的状态描述有三种模式:恢复模式、同步模式和广播模式,这三种模式并没有十分明显的界限,它们相互交织在一起。
-
恢复模式:在服务器重启过程中,或者在
Leader崩溃后,就进入到了恢复模式,恢复模式主要包含两个阶段:Leader选举阶段和初始化同步阶段,这两个阶段完成之后zk集群恢复到了正常的服务状态。 -
广播模式:广播模式分类两类:初始化广播和更新广播。当
Leader被选举出来之后,Leader需要将自己拥有但是其他Server可能没有的事务及自己的epoch广播给所有的Learner,这是初始化广播。在集群正常服务时,当Leader的事务提案被大多数Follower同意之后,Leader会修改自身数据,然后会将修改后的数据广播给其他的Learner,这就是更新广播。 -
同步模式:同步模式也分为两类:初始化同步和更新同步,跟广播模式相对于,只不过广播模式是站在
Leader角度,而同步模式是站在Learner角度。当Leader选举出来并发布过其初始化广播之后,所有的Learner需要将Leader广播的事务以及epoch同步到本地,这就是初始化同步。在集群正常服务时,当Leader发布了更新广播之后,所有的Learner需要将Leader广播的事务同步到本地,这就是更新同步。
三个数据
在Zookeeper中有三个很重要的数据:zxid、epoch和xid。
-
zxid:zxid为64位长度的long类型,其中高32位表示纪元epoch,低32位表示事务标识xid。即xid由两部分构成epoch和xid。 -
epoch:每一个Leader都会具有一个不同的epoch值,表示一个时期、时代,每一次新的选举结束之后生成一个新的epoch,新的Leader产生,则会更新所有的zkServer的zxid中的epoch。 -
xid:xid则为zk事务的id,每一个写操作都是一个事务,都会有一个xid,xid为一个依次递增的流水号,每一个写操作都需要由Leader发起一个提案,由所有的Follower表决是否同意本次写操作,而每个提案都具有一个zxid。
同步模式与广播模式
初始化同步
当完成Leader选举之后,此时的Leader是准Leader,其要进入到回复模式下的初始化同步阶段,将自己有的数据而其他Follower跟Observer没有的同步给他们,使准Leader转变为真正的Leader,保证数据的一致性。初始化同步的原理图如下:
- 1.为了保证
Leader向Learner发送提案的顺序性,Leader会每一个Learner服务器准备一个队列。 - 2.
Leader将那些没有被各个Learner服务器同步的事务封装成为Proposal。 - 3.
Leader将这些Proposal逐条发送给各个Learner服务器,并在每一个Proposal后紧跟一个COMMIT消息,表示事务已经被提交,Learner可以直接接收并执行。 - 4.
Learner接收到来自Leader的Proposal,并将其更新到本地。 - 5.当
Follower更新成功之后,会向准Leader发送ACK反馈。 - 6.
Leader服务器在收到来自Follower的ACK之后就会将该Follower加入到真正可用的Follower队列中。
上图中为什么Learner3没有向准Leader发送ACK呢?因为Learner3是Observer,Observer是没有反馈功能的
消息广播算法
如果集群中的其他节点收到客户端的事务请求,那么这些Learner会将写请求转发给Leader服务器,然后再执行如下的具体过程:注意下面的FollowersQueues和ObserverQueues两个队列在上面的初始化同步中已经创建出
- 1.
Leader接收到事务请求之后,为事务赋予一个全局唯一的64位自增id,即zxid,通过zxid的大小比较即可实现事务的有序性管理,然后将事务封装为一个Proposal。 - 2.
Leader根据Follower列表获取到所有的Follower,然后再将Proposal通过Follower队列将提案发送给各个Follower。 - 3.当
Follower接收到提案后。会先将提案的zxid与本地记录的事务日志中的最大的zxid进行比较,若当前提案的zxid大于最大的zxid,则将当前提案记录到本地事务日志中,并向Leader返回一个ACK. - 4.当
Leader接收到超过半数的ACKS之后,Leader就会向所有的Follower队列发送COMMIT消息,向所有的Observer队列发送Proposal。 - 5.当
Follower接收到COMMIT消息之后,就会将日志中的事务正式更新到本地。当Observer接收到Proposal之后,会直接将事务更新到本地。
恢复模式两个原则
已被处理过的消息不能丢
如上图所示,当Leader接收到超过半数Follower的ACK之后,就向各个Follower即A,B,C,D广播COMMIT消息,批准各个Server执行该写操作事务,当各个Server在接收到Leader的COMMIT消息后就会在本地执行写操作,然后向客户端响应写操作是否执行成功。
但是此时假设只有A和B接收到了COMMIT消息后,并在本地执行了更新操作,而C和D在还没有接收到COMMIT消息之前Leader就挂了,这将导致一种后果:部分的Server(A和B)已经执行了该事务,而部分Server(C和D)尚未接收到COMMIT消息,所以没有在本地执行该事务。
接着进行新的Leader选举,此时Leader肯定会在A和B中产生。为什么一定会在在A和B中产生呢?
因为A和B中执行了最新的事务,而C和D没有,说明A和B中的事务标识xid肯定比C和D的大,并且此时A,B,C,D的epoch肯定一样的。所以A和B的zxid肯定比C和D大的。我们知道进行Leader选举时,首先比较zxid,选取zxid最大的当选,如果zxid一样,再接着比较myid即Server的标识。
假设A和B中的A本选取当了Leader之后,开始进行初始化同步操作,就会把自己有而其他主机没有的数据进行同步,此时原来Leader宕机前的最后一个事务也会被同步到C和D中,就保证了已被处理过的消息不会丢失。
被丢弃的消息不能再现
如上图所示,当Leader接收到事务请求并生成了Proposal,但是还没有向A,B,C,D四个Follower发送时就挂了,此时所有的Follower都不知道该Proposal的存在。
接着进行新的Leader选举,假设此时A被当选为新的Leader,整个集群进入到正常服务状态后,之前挂了的Leader主机重新启动并注册成为了Follower,不要忘了该主机上还存在着不为人知的Proposal,那么其数据就会比其他主机多了内容,导致整个系统状态的不一致,所以,该Proposal就被消除。
那么如何将其清除呢?根据epoch生成规则,新的Leader即A的epoch值一定会大于当前集群中任何主机记录的事务的zxid值(确切的说。是大于zxid的高32位),当新的Leader产生后,会将它的epoch广播给所有的Learner,并且会将所有Learner中所有的旧的未被COMMIT过的Proposal清除。