聊聊 ZAB 协议

604 阅读7分钟

什么是 ZAB 协议

ZAB 协议是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的原子广播协议

ZAB 协议包括两种基本模式,分别是奔溃恢复模式消息广播模式

ZAB 协议的特性

  1. ZAB 协议需要确保那些已经在 Leader 服务器上提交的事务最终被所有服务器提交

  2. ZAB 协议需要丢弃那些只在 Leader 服务器上被提出的事务

第 2 点相信大家都很好理解,奔溃恢复过程中,发现当前的数据事务日志状态是非 commit 状态的日志都是无效的,都需要被丢弃,比如 Leader 发起 proposal 同步写请求给 follower 还没有收到 ack 或者还没有发出 commit 就挂了

关于第 1 点就存在一些疑问了,比如说如果 Leader 收到了过半服务器的 ACK 那么此时它就会异步发送 commit 给 follower 节点,同时自己也会去 commit,那么极端情况下 Leader commit 持久化磁盘成功,发送给 follower 由于网络等等原因导致 follower 没有接收到,此时正好 Leader 挂了,剩下的 Follower 节点会选出新的 Leader,epoch +1 进入新的 Leader 时代,此后之前失联的那台老的 Leader 重启恢复了,它会以 follower 的形式接入的新的集群中并且与 Leader 通信同步数据,这个时候老的 Leader 被 commit 的数据会被删除同步成新的 Leader 的数据,这一点从顺序一致性就能理解,如果再去还原老的 Leader 的 commit 数据那么新 Leader 在最新集群状态下处理的数据全部就变为无效了,如下图

发送给 follower 的由于网络等原因失败了,自己 commit 成功了,正好 Leader 挂了,由于选举新的 Leader 后会删除最新无效的数据所以 B C D E 节点 a = 1,A 节点 a = 3

之后选举出新的 Leader 出来处理请求,经过一系列操作 a 最终变为了 100,此时原先的 A 结点恢复了,它的 a = 3 当然就只能抛弃被新的 Leader 同步为 a=100 了

当然这是思考过程,实际上的实现是,新选举出来的 Leader zxid 中的高 32 为 epoch 会加 1,这样意味着新选举出来的 zxid 一定会大于之前宕机的老 Leader 的最大的 zxid 这时候现在集群中的 Leader 就会要求新加入老 Leader(它会以 follower 的身份加入到集群)去同步更新集群中最新的数据,也就是发送新选举出来之后的所有的 prososal 然后追加 commit 让其同步完成,同步完成后它就会参与到处理客户端请求的过程中去

服务器结点的几种状态

leading:领导状态,一般称其为 Leader,所有的事务请求都必须由一个全局唯一的服务器来处理,这样的服务器被称作 Leader 服务器

followering:跟随者状态,一本称呼其为 Follower,参与 Leader 选举和请求 ack 的,当收到写请求的时候会转发给 Leader 结点,Leader 结点的写操作必须被超过半数 Follower(这个数量包括 Leader 自己) 提交后才会响应给客户端成功状态

observing:围观状态,一般称呼其为 Observer,它只同步最后的选举结果不参与选举也不参与 ack,对外只提供读服务,也会转发写请求给 Leader

looking:处于竞选状态中

由于 Observer 不参与选举所以后续的讲解不会涉及到该状态的节点服务器

奔溃恢复模式

集群启动过程中或者 Leader 出现网络中断、奔溃退出、重启等情况,ZAB 协议就会进入恢复模式选举出新的 Leader 服务器

当有新的服务器加入到集群中,如果此时已经存在 Leader 服务器了那么直接进入奔溃恢复模式

myid 是搭建集群的时候给服务器设置的 id 值

什么是 zxid

zxid 是一个 64 位的数字,其中低 32 位是一个单调递增的计数器,高 32 位则代表的是 Leader 周期的 epoch 的编号

每次写请求到达 Leader 的时候,Leader 都会广播给 Follower 其中就会携带 zxid,每次处理写请求,单调递增的计数器都会加一,这个计数器的值可以用来标识请求的先后顺序

epoch 它的释义有纪元、朝代的含义,王朝的更替,改朝换代,上一任 Leader 下台,这一任 Leader 登基,登基后 epoch 值加 1 表示开启新元年

启动时候 Leader 的选举过程

每个服务器告诉其它服务器自己想竞选 Leader,发起竞选的时候同时携带了自身的 zxid、myid、epoch 信息

每个节点都发出自己的 zxid、myid、epoch 等信息给其它节点表明我想要竞选 Leader

  1. 节点首先会去检测选票的有效性查看 epoch 是否是本轮投票并且检查投票者的状态是否是 looking 等
  2. 然后会先对比 zxid 谁的 zxid 大选谁
  3. 然后对比 myid 谁的 myid 大就投票给谁
  4. 只要得到半数以上的节点支持那么就选举成功

myid 大的节点去连接小的节点(小节点连接大节点不会成功,避免重复连接)

运行时候 Leader 选举过程

当 Leader 出现网络中断、奔溃退出、重启或者集群中有半数结点失去连接等情况就会发起新一轮 Leader 选举

只要集群中有过半数的节点存活那么集群就有效

  • 比如集群 2 台服务器 1 台失效,集群失效
  • 集群 3 台服务器 1 台失效,集群有效
  • 集群 4 台服务器 1 台失效,集群有效,2 台失效集群无效

基于这个规律所以说一般情况下把 zookeeper 集群节点配置为奇数个节点更加有价值一些

区别在于如果是 Leader 失效了会发起新一轮 Leader 选举,如果是 follower 失效了那么不会发生新的选举,最新的 follower 上来后会同步 Leader 数据完成后参与到接收客户端请求中去

它的选举过程和启用选举是相似的,不过多了几点的是

  1. Leader 失效后新的 Follower 选举的时候会将 zxid 最大的并且是被 commit 状态的数据的节点选为准 Leader,当然如果有 2 个一样大,就对比他们的 myid,这样做的好处是它就不会再去同步其它节点最新的数据了,他的 zxid 最大意味着它包含了最新的事务修改操作
  2. 新的 Leader 选举出来后,肯定会跟集群其它机器通信对比下如果有谁的 zxid 落后了,就把落后的数据发送 prosoal 然后紧跟一个 commit 要求他们全部把数据同步好
  3. 当集群中有过半的节点数据同步好了,集群恢复到广播模式,开始处理客户端请求

消息广播模式

当集群中已经有过半的 Follower 服务器的数据已经完成了和 Leader 的数据同步就会进入消息广播模式

当集群中过半数 follower 节点返回 ack 后,Leader 就会异步发起 commit 要求 follower 提交事务,同时自己也会 commit 然后响应给客户端

如果集群中其它的节点收到了写请求都会转发给 Leader 由 Leader 来统一发起

由于在处理客户端请求的过程中,集群机器可能会出现网络中断、奔溃退出、重启或者集群中有半数结点失去连接等问题,所以这个时候就需要配合奔溃恢复模式来恢复集群

Observer 服务器

Observer 只负责读请求,同时他需要同步 Leader 的写请求,不过它不参与过半数 ack 的统计,也不参与 Leader 选举,这样它就可以大幅度的提升集群的读性能

参考文献

  • 从 Paxos 到 ZooKeeper 分布式一致性的原理和实践