ZK核心原理

    科技2024-06-14  86

    ZK是分布式协调工具,可以实现其他客户端集群的leader选举,分布式锁。

    ZK集群自身则是使用zab协议经行选举,以及数据同步

     

    一:分布式锁思路:

          可以利用ZK节点的特性来实现独占锁。就是同级节点的唯一性。多个进程往ZK节点下创建一个相同名称的节点,只有一个能成功,其他都失败。创建失败的节点全部通过ZK的watch机制来监听ZK这个字节点的变化,一旦监听到子节点的删除事件,则再次触发所有的进程去写锁。

          这种方式实现很简单,但是会产生惊群反应。简单来说,就是如果存在多个客户端等待锁,当监听到进程释放节点后,ZK会在短时间内发送大量子节点变更事件给所有获取锁的客户端,然后只有一个进程获取锁。

          解决方式:利用有序节点来实现分布式锁。

          每个客户端都往指定的节点下注册临时有序节点,越早创建的节点,节点的顺序编号越小。我们可以判断子节点中最小的节点设置为获取到锁,如果自己的节点不是最小的节点,以为着没有获取到锁。

          这个实现过程,与刚刚说的流程最大的差别是,每个节点只需要监听比自己小的节点,当比自己小的节点删除以后,客户端会收到watch事件,此时再次判断自己的节点是不是所有子节点中最小的。如果是,就获取到锁。否则,重复这个过程。这样就不会产生惊群效应,因为每个客户端只需要监控一个节点。(后续分析curator实现分布式锁源码)

     

    二:使用ZK实现leader选举

    Leader Latch:

    参与选举的所有节点,会创建一个顺序节点,其中最小的节点会设置master节点,没抢到Leader的节点都监听前一个节点的删除事件,在前一个节点删除后重新抢主。当master节点手动调用close方法或者master节点挂掉的时候,后续的节点会继续抢占master。

     

    LeaderSelector:

    LeaderSelector在Leader释放领导权以后,可以继续参与竞争。

     

    三:ZK的数据同步流程

         首先要知道,ZK集群是通过三种不同的集权角色来组成整个高性能集群的。在ZK中,客户端会随机连接到ZK集群中的一个节点,如果是读请求,就直接从当前节点中读取数据,如果是写请求,那么请求就会被转发给leader提交事务,然后leader会广播事务,只有超过半数节点写入成功(所以,ZK集群一般是奇数,2n+1个),那么写请求就会被提交。(类似 2PC事务。)

       那么问题来了,

      1 集群中的leader节点如何选举出来。

      2 leader节点崩溃以后,整个集群无法处理写请求,如何快速从其他节点里面选举出新的leader呢 ?

      3 leader节点如何与follower节点数据保持一致。

     

    ZAB协议:

    ZAB协议(Zookeeper Atomic Broadcast)协议是为分布式协调服务ZK专门设计的一种支持崩溃恢复的原子广播协议。在ZK中,主要依赖ZAB协议来实现分布式数据一致性,基于该协议,Zk实现了一种主备模式的系统架构来保持集群中各个副本之间的数据一致性。

    zab协议介绍:

    ZAB协议包含两种基本模式,分别是:

    1 崩溃恢复

    2 原子广播

    当整个集群在启动时,或者当leader节点出现网络中断,崩溃等情况时,ZAB协议就会进入恢复模式并选举产生新的leader,当leader服务器选举出来后,并且集群中有过半的机器和该leader节点完成数据同步后(同步指的是数据同步,用来保证集群中过半的机器能够和leader服务器的数据状态保持一致),ZAB协议就会退出恢复模式。当集群中已经有过半的Follower节点完成了和Leader状态同步以后,那么整个集群就进入了消息广播模式。这个时候,在Leader节点正常工作时,启动一台新的服务器加入到集群,那么这个服务器直接进入数据恢复模式,和leader节点直接进行数据同步,同步完成后,即可正常对外提供非事务请求的处理。

    需要注意的是: leader节点可以处理事务请求和非事务请求,follower节点只能处理非事务请求,如果follower节点接收到事务请求,会把请求转发给Leader服务器。

     

    消息广播实现原理:

    消息广播的过程实际上是一个简化版本的二阶段提交。

    1 leader接收到消息请求后,将消息赋予一个全局唯一的64位自增id,叫zxid,通过zxid的大小比较既可以实现因果有序这个特征。

    2 leader为每个follower准备了一个FIFO队列,(通过TCP协议来实现,以实现全局有序这个特点)将带有zxid的消息作为一个提案(proposal)分发给所有的follower。

    3 当follower接收到proposal,先把proposal写到磁盘,写入成功以后再向leader返回一个ack。

    4 当leader接收到合法数量(超过半数节点)的ACK后,leader就会像这些follower发送commit命令。同时在本地会执行该消息。

    5 当follower收到消息的commit后,会提交该消息。

    这里需要注意的是:

    leader的投票过程,不需要Observer的ack,也就是Observer不需要参与投票过程,但是observer必须要同步leader的数据,从而在处理请求的时候保证数据的一致性。

     

     

    崩溃恢复的实现原理:

    前面我们已经清楚了ZAB协议中的消息广播过程,ZAB协议的这个基于原子广播协议的 消息广播过程,在正常情况下是没有任何问题的,但是一旦Leader节点崩溃,或者由于网络问题导致Leader服务器失去了过半的follower节点,那么就会进入到崩溃恢复状态。在崩溃恢复状态下,zab协议需要做两件事:

    1 选举出新的leader

    2 数据同步

    前面说消息广播时,知道zab协议的消息广播机制时简化版的2PC协议,这种协议只需要集群中过半的节点相应提交即可。但是他无法处理leader服务器崩溃带来的数据不一致问题。

    如果一个事务Proposal在一台服务器上被处理成功,那么这个事务应该在所有机器上都处理成功,哪怕出现故障。

    1 已经被处理的消息不能被丢弃

    当leader收到合法数量follower的ACKS后,就会向各个follower节点发布广播commit命令,同时也会在本地执行commit命令,并向连接的客户端返回成功。 但是,如果在各个follower收到commit命令之前,leader就挂了,导致剩下的服务器没有执行该消息怎么办?

     

    2 被丢弃的消息不能再出现

    当leader收到事务请求后生成proposal后,就挂了,其他follower并没有收到此proposal,因此经过恢复模式重新选取了leader后,这条消息是被跳过的 。此后,之前挂了的leader,重新启动并注册成follower,它保留了被跳过取消的proposal状态,与整个系统时不一致的,需要被删除。

     

    ZAB协议要满足上面两种情况,就必须设计一个leader选举算法,能够确保已经被leader提交的事务proposal能够提交。同时,丢弃已经被跳过的proposal。

    针对这个要求

    1 如果 leader 选举算法能够保证新选举出来的 Leader 服务器拥有集群中所有机器最高编号(ZXID 最大)的事务Proposal,那么就可以保证这个新选举出来的 Leader 一定具有已经提交的提案。因为所有提案被 COMMIT 之前必须有超过半数的 follower ACK,即必须有超过半数节点的服务器的事务日志上有该提案的 proposal,因此,只要有合法数量的节点正常工作,就必然有一个节点保存了所有被 COMMIT 消息的 proposal 状态

    2. 另外一个,zxid 是 64 位,高 32 位是 epoch 编号,每经过一次 Leader 选举产生一个新的 leader,新的 leader会将 epoch 号+1,低 32 位是消息计数器,每接收到一条消息这个值+1,新 leader 选举后这个值重置为 0.这样设计的好处在于老的 leader 挂了以后重启,它不会被选举为 leader,因此此时它的 zxid 肯定小于当前新的leader。当老的 leader 作为 follower 接入新的 leader 后,新的 leader 会让它将所有的拥有旧的 epoch 号的未被 COMMIT 的 proposal 清除.

     

     

    手续会阅读ZK源码,从代码层面来剖析ZK的ZAB协议。

     

     

     

     

     

     

     

     

    Processed: 0.043, SQL: 8