Zookeeper学习(二):ZAB原子广播和集群leader选举策略

一、概述

  • Zookeeper 作为分布式系统协调者和管理者,承担着联结分布式系统的各组件来组成一个完整服务的职责,如 kafka 作为一个分布式集群,在 kafka 的内部体系结构中包含消息生产者,消费消费者,消息存储 broker 三个核心组件,kafka 通过在 zookeeper 中维护这三大组件的运作信息,并且这三大组件分别通过 zookeeper 获取其他组件的运作信息或者获取组件内部其他子组件的运作状态来协同工作,如消息消费者节点将其所消费的消息主题的分区的消息 offset 上传到 zookeeper 中,当该消费者机器节点宕机时,另外一个消费者可以基于 zookeeper 中保存的消息消费情况,继续接着消费数据,避免数据重复,从而实现了不同消费者节点之间的协作。
  • 所以 Zookeeper 自身需要保证高可用,这样才能作为分布式系统的大管家,保证分布式系统的稳定运作。zookeeper 的高可用也是通过集群的方式来实现的,即多个 zookeeper 实例节点共同组成一个集群来避免单点问题。

二、高可用集群

  • zookeeper 作为分布式系统协调者,在数据存储方面,zookeeper 将数据组织成目录树的结构,然后在每个目录节点存放部分数据。从 zookeeper集群角度而言,集群内每个 zookeeper 节点存放的都是相同的数据,这个跟 Redis 的集群是不一样的,Redis 的集群主要用于实现一个分布式数据库来进行海量数据存储,所以每个节点存放不同的数据。而 zookeeper 集群的主要目的是实现高可用,所以在设计层面是基于主从来设计的,这样当主节点宕机时,可以将某个从节点升级为主节点,从而避免主节点的单点问题,实现高可用。
  • 在数据读写方面,由于 zookeeper 集群是一个主从结构集群,故主节点负责处理写请求,然后将写请求同步给从节点,从而实现整个集群的数据一致性。所以 zookeeper 也是适合于读多写少的应用场景的。

1. 机器节点角色

  • 在 zookeeper 集群中,通过给节点定义角色来区分不同节点的功能。由以上分析可知,zookeeper 集群是一个主从结构集群,其中角色定义包括三种:leader,follower,observer。
leader:领导者(读写请求,从节点维护)
  • leader 节点是 zookeeper 集群的主节点,主要负责所有的写请求,也可以处理读请求。除此之外,需要维持与从节点,即 follower 节点和 observer 节点的心跳,从而实时监测从节点的运作情况和通知从节点自身的运作情况,如假如 leader 节点宕机了,则不会再发心跳包给 follower 节点和 observer 节点,此时 follower 节点才能发现主节点挂了,从而进行新一轮 leader 选举实现崩溃恢复。
follower:跟随者(处理读请求和投票)
  • follower 节点是 zookeeper 集群的从节点,主要负责处理客户端的读请求,对于写请求则首先统一转发给 leader 节点,然后在 leader 节点执行写请求时,会将写请求以协议消息的方式发送给所有 follower 节点,通过投票来决定是否需要执行此次写请求,所以 follower 节点需要 ack 响应 leader 节点来进行投票。如果投票通过,则 leader 向所有的 follower 和 observer 节点发送提交 commit 请求,从而在所有节点执行本次数据写操作。(两阶段提交)
  • 除了对 leader 节点的写请求进行投票外,follower 节点还需要对 leader 节点的选举进行投票,从而选举出leader节点。
observer:观察者(处理读请求)
  • observer 节点跟 follower 节点差不多,也是主要处理读请求,对于写请求则统一转发给 leader 节点。与 follower 节点不同的是,observer 节点没有投票权,即不参与 leader 节点发起的写请求的投票和 leader 选举的投票,只是从 leader 节点同步数据,处理读请求,所以 observer 节点主要是对 zookeeper 集群的读请求的拓展,所以说 zookeeper 是适合读多写少的应用场景。

2. 节点状态与心跳包

  • 每个节点的状态跟节点的角色类似,主要包含 LOOKING,LEADING, FOLLOWING 和 OBSERVING 四种。
  • LOOKING:不确定 leader 节点的状态,此时该节点会认为当前集群不存在 leader 节点,故会主动发起一次 leader 选举,广播选举包,即投给自己和其他节点。此时 leader 节点收到后会将自己的 LEADING 状态告诉该节点并投票给 leader 自身,其他 follower 节点收到后,则是将自己的 FOLLOWING 状态告诉该节点并投票给 leader 节点,该节点收到 leader 和其他 follower 的状态和投票后,知道当前 leader 节点的信息,并通过其他follower的投票确认该 leader 节点确实是 leader 节点,则设置自身状态为 FOLLOWING,成为当前 leader 的 follower 节点。
  • FOLLOWING:跟随者状态,即自身角色是 follower。
  • LEADING:领导状态,自身角色为 leader,并且维持着与 follower 和 observer 的心跳。
  • OBSERVING:观察者状态,即自身角色是observer。

三、Zab协议

  • Zab 协议,即原子广播协议,主要定义了 zookeeper 集群的数据同步方式,从而实现集群数据的最终一致性和定义了在 leader 节点宕机时的 leader 选举方式,从而实现集群的高可用。

1. 写请求:原子广播

  • 由上面的分析可知,zookeeper 集群的每个节点的数据是一致的,并且由 leader 节点负责处理所有的写请求,然后再同步给 follower 和 observer 节点。
  • leader 节点执行一次写请求的过程如下:
    1. 客户端向 leader 节点发送写请求,或者 follower 节点或者 observer 节点向 leader 节点转发写请求;
    2. leader 节点收到写请求,首先持久化到本地文件,然后将写请求以proposal 的方式,注意 proposal 是包含写请求的修改操作的内容的,通过与 follower 节点维持的长连接(即心跳包来维持),广播给所有的 follower 节点并等待 follower 节点的 ack ;
    3. follower 节点和 observer 接收到该 proposal,将写操作持久化到本地,然后 follower 节点会响应 ack 给 leader;
    4. leader 只要收到超过半数 follower 节点的ack,即 n/2+1 个(leader自身也算一个 ack,如集群包含3个节点,则挂了1个,还有两个,故 2/3 > 1/2;如果集群是4个,如果挂了2个,则 2/4 = 1/2,不满足超过半数,故也只能最多挂1个,故从高可用的角度来说,3个就够了,所以一般说是奇数个),其中 n 为 follower 节点的个数,则向所有的 follower 和 observer 发送提交请求 commit。注意这个过程有点像两阶段提交2PC,不同之处是 2PC 需要收到所有节点的 ack 才执行提交操作,而 Zab 协议则只需要收到超过半数即提交,故性能相对 2PC 较好。不过这里说收到超过半数即提交不是说其他还没收到的节点就可以不同步,其他还没发送 ack 或者 ack 还没传到 leader 节点的 follower 还会继续同步数据,除非这个节点宕机了;
    5. leader 节点,follower 节点,observer 节点收到 commit 请求后,则执行写操作,修改节点的内存数据,从而保证了数据持久性。
消息有序性:全局有序消息ZXID
  • 由以上过程可知,leader 节点可能同时收到多个写请求,而写操作的过程又是异步非阻塞的,即 leader 可以同时处理多个写请求,而不需要等待前一个写请求完成以上过程才能继续下一个写请求,所以这里就存在一个消息有序性问题。
  • Zookeeper 主要是利用一个全局有序消息 id 和 FIFO 队列来实现的。
  • 全局有序 id:leader 节点每处理一个写请求都会为该写请求分配一个全局有序的消息 id,称为 zxid。zxid是一个64位的数字,其中前32位选举轮次epoch,后32位本轮次处理事务次数。即每经过一次leader选举,则epoch递增,处理事务次数从0开始。故在整个运行过程中,每个写请求都是全局唯一的。
  • FIFO队列:leader节点会发送多个写请求对应的 proposal 给 follower,leader节点会为每个follower节点都维护一个 FIFO 队列,从而实现对该follower的所有proposal按照FIFO的顺序进行 ack 处理,保证写请求的先后顺序一致。
最终一致性
  • zookeeper 集群是一个高可用集群,实现的是数据的最终一致性,即集群各节点的数据最终会得到一致,而不是强一致性。由分布式的 CAP 理论可知,任何时候只能存在CP或者AP,即高可用+分区容忍性,或者强一致性和分区容忍性,CA,即高可用和强一致性是不可能同时存在的,因为数据需要通过网络在不同节点之间传播,由于网络的不稳定性,总是存在延迟或者数据丢失,所以不同节点的数据可能不一致。

2. 崩溃恢复与leader选举:FastLeaderElection机制

  • 由于 leader 节点基于心跳机制维持与 follower 和 observer 节点的长连接,故当 leader 节点宕机之后,该连接就断开了,follower 和 observer 就不会再继续接收到 leader 的心跳包,或者出现网络分区(即脑裂) leader 节点失去大多数的 followers,则会进入崩溃恢复阶段,重新进行 leader 选举。
选举的投票数据
  • leader 选举时,每个 follower 节点的投票包的相关核心数据如下:
    1. logicalClock:该节点发起的第几轮投票,每选举一次加1;
    2. self_id:当前节点自身的 myid;
    3. self_zxid:当前节点自身所保存的数据的最大 zxid,越大说明处理了越多数据写请求;
    4. vote_id:当前节点投票给的节点的 myid;
    5. vote_zxid:当前节点投票给的节点的数据的最大 zxid。
leader选举的过程
  1. leader 选举开始时,每个 follower 节点首先清空自己的投票箱,然后投票给自己,并通过广播的方式通知所有其他节点给自己投票;
  2. 每个 follower 节点接收到其他 follower 节点的选票,将该外部选票与自己的选票进行对比,对比主要是基于以上5个核心数据项来展开:
    1. 选举轮次:比较 logicalClock,如果外部选票的 logicalClock 大于自己的,则说明自己的选举轮次落后于该外部节点了,则清空自己的投票箱,并将自己的投票更新为当前轮次后重新广播投票出去;小于则忽略该外部选票;等于则进入下面步骤继续比较其他数据;
    2. vote_zxid 大小比较:将外部选票的 vote_zxid 与自己的投票的vote_zxid 进行对比,如果外部的大,则将自己的(vote_myid,vote_zxid)更新为该外部选票的 vote_myid 和 vote_zxid 并广播出去,即投给这个 vote_zxid 更大的 vote_myid;并更新自身的投票箱,即添加或者更新该外部投票对应的 vote_myid 的选票情况。因为在每个节点的投票箱中,对于集群中的所有参与投票 follower 节点只能存在一张投票,即当当前节点收到某个节点的多次投票时,则需要进行覆盖,如节点A刚开始收到节点B投给B自己的投票,A放入投票箱为(B,B),后来又收到B投给C,则更新为(B,C),此时A的投票箱不再存在(B,B)的这种选票了,而是更新为了(B,C);
    3. vote_myid大小比较:如果 vote_zxid 相同,则投票给 vote_myid 更大的节点;
    4. 重复以上过程,当某个节点发现过半数的 follower 节点都投给了自己,则更新自己的状态为 LEADING,其他节点则更新自己的状态为FOLLOWING,投票结束。接下来进入数据同步阶段。
    5. 数据同步阶段:主要是当前新的 leader 节点将自己的已经 commit 的数据同步给其他 follower 节点。
  • 36
    点赞
  • 110
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值