0. 两阶段提交
在学习 zab 协议之前,我们先来学习一下二阶段提交的过程。
两阶段提交顾名思义主要分为两个阶段
第一阶段(请求阶段)
协调者
首先会发送某个事务的执行请求给其它所有的参与者,当参与者收到 perpare 请求时会检查自身并告诉 协调者
自己的决策是同意还是取消
第二阶段(提交阶段)
协调者
将根据第一阶段的投票结果发送提交或回滚请求(一般是所有参与者都返回同意就发送提交请求,否则发送回滚请求)。
PS:请参考下 paxos 算法
下面回到我们的 zab 协议。
1.zab 是什么?
zab 的英文全称是:ZooKeeper Atomic Broadcast
zab 协议是为分布式协调服务的 zookeeper 提供崩溃恢复和 主从同步数据(维护数据一致性)的一种协议。
zab 协议参考了 paxos 协议的一些设计思想。比如以下的一些特性:
- leader 向 follows 提出提案(proposal)
- leader 需要在达到法定数量 (半数以上) 的 follows 确认之后才会进行 commit
- 每一个 proposal 都有一个纪元 (epoch) 号,类似于 Paxos 中的选票(ballot)
ps: 关键术语真的很重要。
zab 和 Paxos 最大的不同是,ZAB 主要是为分布式主备系统设计的,而 Paxos 的实现是一致性状态机(state machine replication)
1.1 节点的三种状态
zk 集群有三种角色:
- leader 就是我们说的主
- follower 就是我们说的从
- observer 可以认为是主的 clone copy,不参与投票
zk 集群的一个节点,有三种状态:
- looking 选举状态,当前群龙无首;
- leading leader 才有的状态;
- following follower 才有的状态
一个集群中,有且只有一个 leader,只有 leader 才是术语 leading 状态。就如同一个国家只有一个皇帝,却可以有多个领主。
1.2 zxid
我们暂且把他当做是一个 znode 的事务 ID,如同 mysql 每个事务,都有一个唯一的编号。
zxid 全局唯一,且是自增。
ZooKeeper 会为每一个事务生成一个唯一且递增长度为 64 位的 ZXID,
ZXID 由两部分组成:低 32 位表示计数器 (counter) 和高 32 位的纪元号(epoch)。epoch 为当前 leader 在成为 leader 的时候生成的,且保证会比前一个 leader 的 epoch 大
我们可以抽象映射为现实的场景,比如一个国家有 n 多的领土(国王的儿子),但是只有一个国王。古时候消息比较阻塞,选举的国王发布的协议,通过 第 20 任国王的第 3 条协议 这样记录。
epoch 为 第 20 任国王,counter 为第三条协议。
下一任国王在选举的时候,自动投票为第 21 人国王选举人。
实际上当新的 leader 选举成功后,会拿到当前集群中最大的一个 ZXID,并去除这个 ZXID 的 epoch, 并将此 epoch 进行加 1 操作,作为自己的 epoch。
1.3 历史队列
每一个 follower 节点都会有一个先进先出(FIFO)的队列用来存放收到的事务请求,保证执行事务的顺序
可靠提交由 ZAB 的事务一致性协议保证 全局有序由 TCP 协议保证 因果有序由 follower 的历史队列 (history queue) 保证
2.Zab 的工作模式:消息广播
zab 是如何保持数据一致性的呢?
2.1 读操作
zk 集群中的多个服务器,都可以提供读服务。客户端连接到集群中的任一个 server 节点,可以进行正常的读操作。
2.2 写操作
广播模式:
1. 客户端发出写入数据请求给任意 Follower。
2.Follower 把写入数据请求转发给 Leader。
3.Leader 采用二阶段提交方式,先发送 Propose 广播给 Follower。
此时已经生成这个事务对一个全局唯一 ZXID
4.Follower 接到 Propose 消息,写入日志成功后,返回 ACK 消息给 Leader。:step1
写入日志到本地磁盘
follower 节点将收到的事务请求加入到历史队列(history queue)
5.Leader 接到半数以上 ACK 消息,返回成功给客户端,并且广播 Commit 请求给 Follower。:step2
当 follower 收到 commit 请求时,会判断该事务的 ZXID 是不是比历史队列中的任何事务的 ZXID 都小,如果是则提交,如果不是则等待比它更小的事务的 commit
zab 只能通过 leader 来处理写请求。集群中 follow 节点接受到写请求后,会发送消息到 leader 节点。
Zab 协议要保证同一个 Leader 发起的事务要按顺序被执行,同时还要保证只有先前 Leader 的事务被执行之后,新选举出来的 Leader 才能再次发起事务
Zab 协议既不是强一致性,也不是弱一致性,而是处于两者之间的单调一致性。它依靠事务 ID 和版本号,保证了数据的更新和读取是有序的
3、崩溃恢复
当整个服务框架启动过程中或 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时,Zab 协议就会进入恢复模式并选举产生新的 Leader 服务器。
恢复模式大致可以分为四个阶段
- 选举
- 发现
- 同步
- 广播
恢复过程的步骤大致可分为
- 当 leader 崩溃后,集群进入选举阶段,开始选举出潜在的新 leader(一般为集群中拥有最大 ZXID 的节点)
- 进入发现阶段,follower 与潜在的新 leader 进行沟通,如果发现超过法定人数的 follower 同意,则潜在的新 leader 将 epoch 加 1,进入新的纪元。新的 leader 产生
- 集群间进行数据同步,保证集群中各个节点的事务一致
- 集群恢复到广播模式,开始接受客户端的写请求
3.1 选举阶段
选举阶段,此时集群中的节点处于 Looking 状态。它们会各自向其他节点发起投票,投票当中包含自己的服务器 ID 和最新事务 ID(ZXID)。
接下来,节点会用自身的 ZXID 和从其他节点接收到的 ZXID 做比较,如果发现别人家的 ZXID 比自己大,也就是数据比自己新,那么就重新发起投票,投票给目前已知最大的 ZXID 所属节点。
每次投票后,服务器都会统计投票数量,判断是否有某个节点得到半数以上的投票。如果存在这样的节点,该节点将会成为准 Leader,状态变为 Leading。其他节点的状态变为 Following。
选举比较总结:(投票内容:(zxid,myid))
规则是优先检查 ZXID。ZXID 比较大的服务器优先作为 Leader。
如果 ZXID 相同,那么就比较 myid。myid 较大的服务器作为 Leader 服务器,
PS : zk 是 cp 的,不一定保证可用性,在选举的过程中,不能对外提供服务
3.2 发现阶段
发现阶段,用于在从节点中发现最新的 ZXID 和事务日志。
或许有人会问:既然 Leader 被选为主节点,已经是集群里数据最新的了,为什么还要从节点中寻找最新事务呢?
这是为了防止某些意外情况,比如因网络原因在上一阶段产生多个 Leader 的情况。
所以这一阶段,Leader 集思广益,接收所有 Follower 发来各自的最新 epoch 值。Leader 从中选出最大的 epoch,基于此值加 1,生成新的 epoch 分发给各个 Follower。
各个 Follower 收到全新的 epoch 后,返回 ACK 给 Leader,带上各自最大的 ZXID 和历史事务日志。Leader 选出最大的 ZXID,并更新自身历史日志。
3.3 同步阶段
同步阶段,把 Leader 刚才收集得到的最新历史事务日志,同步给集群中所有的 Follower。只有当半数 Follower 同步成功,这个准 Leader 才能成为正式的 Leader。
自此,故障恢复正式完成。
3.4 广播阶段
10、参考链接
https://juejin.im/entry/5b84d589e51d453885032159
https://juejin.im/post/5b037d5c518825426e024473