春哥大魔王的博客

春哥大魔王的博客

Zab协议

Zab是Zk实现原子广播的协议,Zk内部节点基于此协议作为共识算法实现主备模式的系统架构,以保障集群中各个副本数据之间的一致性。

zab协议主要解决了zk的崩溃恢复和原子广播问题。当leader可用时就是消息广播模式,如果leader不可用时,就是崩溃恢复模式。

在Zk中所有写请求都是由leader服务器协调处理,如果写请求到了follower,则该follower会将请求转发到leader进行处理,leader接收到请求后,通过类似于2pc过半数节点写入成功,leader会向集群广播commit消息并提交(自己先提交,再发送commit给follower)。如果是读请求,则直接由当前节点处理。

Zk中节点状态有以下几种:

  • looking:进入leader选举状态;
  • leading:某个节点变成了leader并负责协调事务;
  • following:follower节点服从leader节点的命令,并参与共识;
  • observing:observer节点是只读节点,用于增加集群的只读事务性能,不参与共识与选举;

Leader选举

在leader选举阶段,所有节点状态都是looking的,每个节点向自己及集群中其他节点毛遂自荐,请求参数包含任期和zxid。为了避免无休止的选举,zab总体保证拥有最新数据的节点当选leader:

  1. 谁的epoch(任期)大,则选举谁;
  2. 谁的zxid大,代表数据最新,则选举谁;
  3. 比较每个节点自己的myid(每个节点有一个唯一id:myid),谁大选谁;

一旦某个节点拥有超过半数的投票,则变为leader,其他节点自动变成follower。

当leader被选举出来之后,follower和leader建立长连接,并进行数据同步。当数据恢复阶段结束后,zk集群才能正式对外提供事务服务,也就进入了消息广播阶段。

消息广播

zab协议基于主备模式的原子广播协议,采用了操作的顺序性。所有的副本数据都是以主节点为准,主节点采用2pc提交,向follower节点同步数据。zab采用了fifo队列,保证消息处理的顺序性。

zab协议实现了基于leader的专政模式和严格的fifo顺序提交日志模式保证操作的有序性。

  • zab实现的是最终一致性,当写请求过半节点成功后,是不能保证每次都实时读到最新数据的,但是可以保证在一定时间范围内,客户端最终可以从服务端获取最新数据。
  • zab选举过程需要通信多次,选举过程会慢一些。

对于写请求,任意节点收到写请求,最终会转发给leader节点。leader节点对于写请求生成事务提案,生成zxid(64位,高32位表示当前leader的任期,低32位是一个递增的序列数,高32位代表leader的唯一性,低32位代表每个leader事务的唯一性)。

然后将提案广播给其他节点,其他节点收到请求后,以事务日志形式存入本地磁盘,并反馈ack响应。如果leader收到超过半数节点的ack响应后,则回复客户端写入成功,并向集群发送commit消息,集群节点进行事务提交,各节点收到commit后,会完成事务提交,数据写到数据副本当中。

leader会收到多个写请求,为统计每个写请求来自于follower的ack反馈数,leader中通常会为每个follower维护一个消息队列,然后将需要广播的提案依次放入队列中,并基于fifo规则发送消息。leader只需要统计每个zxid对应的提案超过半数ack即可,无需等待所有follower响应。

Zookeeper(zk)那些事-LMLPHP

Zookeeper(zk)那些事-LMLPHP

Zookeeper(zk)那些事-LMLPHP

Zookeeper(zk)那些事-LMLPHP

再leader和follower之间有个消息队列,用来解耦,避免同步并发问题。

Zookeeper(zk)那些事-LMLPHP

崩溃恢复

Zk处理崩溃恢复一般有两个阶段:

  1. leader选举;
  2. 数据恢复;

我们考虑两种情况。

Zookeeper(zk)那些事-LMLPHP

第一种情况,在leader上提交的事务最终会被所有的节点提交,因为依赖于zab协议,只有超过半数节点ack,leader才会提交,所以这种情况是满足的。

第二种情况,在leader上提出的事务,但没有被提交的事务,这种情况比较麻烦一些。

  1. 如果leader广播提案时崩溃,或等待半数follower的ack响应期间崩溃,这时还没有向客户端回复写入成功,也没用向集群发送commit。因此对于客户端来说没有收到leader节点的写操作成功的响应,这种事务是失败的。对于follower来说,没有收到commit命令,因此没有任何一个follower节点进行了数据提交。

  2. 如果leader在发送第二阶段commit后,回复客户端之前崩溃,这需要要求对于follower的ack设置合理的超时时间,还包括重试和幂等去重的设置。

Zookeeper(zk)那些事-LMLPHP

一致性保证

新leader选举之后,在对外提供服务之前,leader首先确认自己身上已提交的事务被全局提交,目的是保证一致性。

当follow连接上新leader后,leader会根据自己最后提交的zxid和follower上的zxid进行对比,要么回滚,要么和leader同步(如果差得多会进行大量的事务同步复制)。

当所有follower都同步成功后,leader会将这些节点加入到服务可用节点列表。

zk并不提供一致性的全局数据视图。对于读请求,每个节点都有一个本地缓存,可以提高读性能。如果读压力大,可以线性增加follower节点均衡读压力,但是节点过多会影响写性能。

raft算法对于读一致性方式解决策略:

  1. 读取走raft log,让读取请求也走一遍raft流程,raft日志全局严格有序,读写有序,因此处理读请求时,能够保证之前的写入请求都已经sync状态机了。
03-09 00:56