type
status
date
slug
summary
tags
category
icon
password
1、概述
Zab 协议是为 Zookeeper 专门设计的一种支持崩溃恢复的原子广播协议 ,是 Zookeeper 保证数据一致性的核心算法。Zab 借鉴了 Paxos 算法,但又不像 Paxos 那样,是一种通用的分布式一致性算法。它是特别为 Zookeeper 设计的支持崩溃恢复的原子广播协议。
基于该协议,Zookeeper 实现了一种主备模型(即Leader和Follower模型)的系统架构来保证集群中各个副本之间数据的一致性。 这里的主备系统架构模型,就是指只有一台客户端(Leader)负责处理外部的写事务请求,然后 Leader 客户端将数据同步到其他 Follower 节点。
Zookeeper 客户端会随机的链接到 Zookeeper 集群中的一个节点,如果是读请求,就直接从当前节点中读取数据;如果是写请求,那么节点就会向 Leader 提交事务,Leader 接收到事务提交,会广播该事务,只要超过半数节点写入成功,该事务就会被提交。
Zab 还要保证同一个 Leader 发起的事务要按顺序被执行,同时还要保证只有先前 Leader 的事务被执行之后,新选举出来的 Leader 才能再次发起事务。比如 P1 的事务 t1 可能是创建节点
/a
,t2 可能是创建节点 /a/bb
,只有先创建了父节点 /a
,才能创建子节点 /a/b
。2、Zab协议内容
2.1 Zab协议的角色
ZAB 协议定义了三种角色,每个服务器承担如下三种角色中的一种:
- Leader:集群的核心,一个ZooKeepe r集群同一时间只会有一个实际工作的 Leader,工作内容:
- 发起并维护与各 Follwer 及 Observer 间的心跳。
- 所有的写操作必须要通过 Leader 完成再由 Leader 将写操作广播给其它服务器。
- Follower:Leader 的追随者,一个ZooKeeper集群可能同时存在多个Follower,工作内容:
- 响应Leader的心跳。
- 直接处理并返回客户端的读请求。
- 将写请求转发给 Leader 处理,并且负责在 Leader 处理写请求时对请求进行投票。
- 参与 Leader 选举投票
- Observer:为了提高集群写性能而引入的新角色。因为写操作需要(通常)需要集群中至少一半的节点投票达成一致,因此随着更多投票者的加入,写入性能也会随着下降。Observer 不参与事务请求 Proposal 的投票,也不参与 Leader 选举投票,其他功能和 Follower 完全相同。所以我们可以引入任意数量的Observer,同时不会影响我们集群的写入性能。工作内容有:
- 响应Leader的心跳。
- 直接处理并返回客户端的读请求。
- 将写请求转发给 Leader处理,但只听取投票结果,不会参与投票过程。
每个服务器通过自身的状态来区分自己的角色,在运行期间可能会出现以下状态之一:
- LOOKING:寻找 Leader 服务的状态,处于当前状态后,将会进行 Leader 选举流程
- FOLLOWING:代表当前服务端处于跟随者状态,表明是 Follower 角色
- LEADING:代表当前服务端处于领导者状态,表明是 Leader 角色
- OBSERVING:观察者状态,表明是 Observer 角色
2.2 Zab协议的核心原理
Zab 协议分为两种模式:
- 崩溃恢复:在进入崩溃恢复模式时 Zookeeper 集群会进行 Leader 选举,一般有两种情况会进入该模式:
- 当服务器启动时期会进行 Leader 选举。
- 当服务器运行期 Leader 服务器的出现网络中断、奔溃退出、重启等异常情况,或者当集群中半数的服务器与该 Leader 服务器无法通信时,进入崩溃恢复模式,开始 Leader 选举。
- 消息广播:当选举产生了新的 Leader,同时集群中有过半的机器与该 Leader 服务器完成了状态同步(即数据同步)之后,Zab协议就会退出崩溃恢复模式,进入消息广播模式,开始接收处理客户端的请求。这个模式有点类似于 2PC。
2.3 崩溃恢复模式
崩溃恢复主要包括两部分:Leader选举 和 数据恢复 。
2.3.1 Leader选举
一旦 Leader 服务器出现崩溃或者由于网络原因导致 Leader 服务器失去了与过半 Follower 的联系,那么就会进入崩溃恢复模式。余下的非 Observer 服务器都会将自己的服务器状态变更为 LOOKING,然后开始进入 Leader 选举流程。
- 自增选举轮次。每个服务器在开始新一轮投票时,会先对自己维护的 logicClock 进行自增操作。
- 初始化投票。每个服务器在广播自己的选票前,会将自己的投票箱清空,生成选票信息,然后先把票投给自己。生成的选票有以下两种情况:
- 在服务启动的初始阶段没有 zxid 值,就会发送(myid,0)。
- 在服务器运行期间,每个服务器上的 zxid 都有值,且 zxid 都不相同,所以就正常发送(myid,zxid)。
- 发送投票。把(myid,zxid)作为广播信息发送给所有参与投票的服务器(除了Observer),然后等待集群中的服务器返回信息。
- 处理投票。当前服务器收到其他服务器发来的选票,记入自己的投票箱内。先判断回复节点此时的运行状态:
- 回复节点是 LOOKING 状态,说明对方也在选举中,当前服务器需要将别人的投票和自己的投票进行PK
- electionEpoch 比较:
- 如果收到选票的 electionEpoch 小,直接忽略此选票。
- 如果收到选票的 electionEpoch 和当前相同,那么认为是合法的选票,接着判断是否应该更新选票。
- 如果收到选票的 electionEpoch 更大,说明自己所保存的逻辑时钟落伍了,使用收到选票的electionEpoch更新本机逻辑时钟,然后清空收到的选票信息,更新提议信息(这里有一个判断过程),重新发送更新后的提议。
- 选票pk:假如 A 是当前服务器收到的选票,B 是当前服务器本身的选票,当以下三个条件满足其一时,将接受 A 选票的提议,并重新发送选票信息。
- election epoch(A) > election epoch (B)
- zxid(A) > zxid(B)
- sid(A) > sid(B)
- 回复节点是 LEADING/FOLLOWING 状态,说明集群目前有 Leader,只需要确保当前服务器和 Leader 能够正常通信,并收到了集群半数以上服务器推荐推荐此 Leader时,就直接加入到集群中去。
- electionEpoch 比较:
- 如果逻辑时钟 Epoch 相同,将该数据保存到 recvset,如果所接收服务器宣称自己是 Leader,那么将判断是不是有半数以上的服务器选举它,如果是则设置选举状态退出选举过程。
- 否则这是一条与当前逻辑时钟不符合的消息,那么说明在另一个选举过程中已经有了选举结果,于是将该选举结果加入到 outofelection 集合中,再根据 outofelection 来判断是否可以结束选举,如果可以也是保存逻辑时钟,设置选举状态,退出选举过程。
- 统计投票:每次投票后,服务器都会统计所有投票,判断是否已经有过半的机器接收到相同的投票信息。所谓“过半”就是指大于集群机器数量的一半,即大于或等于 (n/2+1)。对于这里由 3 台机器构成的集群,大于等于2台即为达到“过半”要求。达到过半要求表示已经已经选出了 Leader。
- 更新服务器状态:一旦确定了 Leader,每个服务器就会更新自己的状态:如果是 Follower,那么就变更为 FOLLOWING,如果是 Leader,那么就变更为 LEADING。
选举过程中的相关概念:
- zxid:Leader 服务器在接收到事务请求后,会为每个事务请求生成对应的 Proposal 来进行广播,并且在广播事务 Proposal 之前,Leader 服务器会首先为这个事务 Proposal 分配一个全局单调递增的唯一 ID ,我们称之为事务 ID(即 ZXID)。一个 zxid 是一个全局有序的 64 位的数字,可以分为两个部分:
- 高 32 位:epoch(纪元),理解为当前集群所处的年代,或者周期。每当选举产生一个新的 Leader 服务器时就会取出其本地日志中最大事务的 ZXID ,解析出 epoch(纪元)值操作加 1 作为新的 epoch ,并将低 32 位置零。所以每经过一次 leader 选举产生一个新的 leader,新 leader 会将 epoch 号 +1。
- 低 32 位:counter(计数器),它是一个简单的单调递增的计数器,针对客户端的每个事务请求都会进行加 1 操作。新 leader 选举后这个值重置为 0。
- sid:也称作 myid,服务器id。每个 ZooKeeper 服务器都需要在数据文件夹下创建一个名为
myid
的文件,该文件包含整个 ZooKeeper 集群唯一的ID(整数)。在配置文件中 ID 与hostname必须一一对应,server.
后面的数据即为myid
,=
后面为hostname
,例如server.1=zoo1:2888:3888
,服务器的myid
为1
,hostname
为zoo1
。
- electionEpoch:每次 leader 选举,electionEpoch就会自增1,统计选票信息时,首先保证 electionEpoch 相同。
- logicClock:每个服务器会维护一个自增的整数,名为 logicClock,它表示这是该服务器发起的第多少轮投票。
- recvset:用来记录选票信息,以方便后续统计。
- outofelection:用来记录选举逻辑之外的选票,例如当一个服务器加入 zookeeper集群 时,因为集群已经存在,不用重新选举,只需要在满足一定条件下加入集群即可。
2.3.2 数据恢复
完成 Leader 选举后,在正式开始工作之前,Leader 服务器会首先确认事务日志中的所有的 Proposal 是否已经被集群中过半的服务器 Commit。对比的原理是将 Follower 的最新的日志 zxid 和 Leader 的已经提交的日志 zxid 对比,会有以下几种可能:
- 如果 Leader 的最新提交的日志 zxid 比 Follower 的最新日志的 zxid 大,那就将多的 Proposal 发送给 Follower,让他补齐。
- 如果 Leader 的最新提交的日志 zxid 比 Follower 的最新日志的 zxid 小,那就发送命令给 Follower,要求 Follower 进行一个回退操作,回退到一个确实已经被集群中过半机器 Commit 的最新 Proposal。
- 如果两者恰好一样,那什么都不用做。
2.4 消息广播模式
进入消息广播模式后,所有服务器开始接收客户端的请求。
- 客户端发起一个写操作请求。
- Leader 服务器将客户端的请求转化为事务 Proposal 提案,同时为每个 Proposal 分配一个全局的ID,即 zxid。
- Leader 服务器为每个 Follower 服务器分配一个单独的队列,然后将需要广播的 Proposal 依次放到队列中,并且根据 FIFO 策略进行消息发送,保证了 Follower 接收到的 Proposal 提案是按照 zxid 大小顺序发出的。
- Follower 接收到 Proposal 后,会首先将其以事务日志的方式写入本地磁盘中,写入成功后向 Leader 反馈一个 Ack 响应消息。
- Leader 接收到超过半数以上 Follower 的 Ack 响应消息后,即认为消息发送成功,可以发送 commit 消息。
- Leader 向所有 Follower 广播 commit 消息,同时自身也会完成事务提交。Follower 接收到 commit 消息后,会将上一条事务提交。
2.5 异常处理
Zab 协议中可能发生的两个异常:
- 一个事务在 Leader 上提交了,并且过半的 Folower 都响应 Ack 了,但是 Leader 在 Commit 消息发出之前挂了。
- 一个事务在 Leader 提出之后,Leader 挂了。
要确保如果发生上述两种情况,数据还能保持一致性,那么 Zab 协议选举算法必须满足以下要求:
- 已经被 Leader commit 的 Proposal 必须最终被所有的 Follower 服务器提交。解决流程:
- 选举拥有 proposal 最大值(即 zxid 最大) 的节点作为新的 Leader:由于所有提案被 COMMIT 之前必须有合法数量的 follower ACK,即必须有合法数量的服务器的事务日志上有该提案的 proposal,因此,zxid 最大也就是数据最新的节点保存了所有被 commit 消息的 proposal 状态。
- 新的 Leader 将自己事务日志中 proposal 但未 COMMIT 的消息处理。
- 新的 Leader 与 Follower 建立先进先出的队列, 先将自身有而 Follower 没有的 proposal 发送给 Follower,再将这些 proposal 的 commit 命令发送给 Follower,以保证所有的 Follower 都保存了所有的 proposal、所有的 Follower 都处理了所有的消息。通过以上策略,能保证已经被处理的消息不会丢。
- Leader 提出的但是没有被 commit 的 Proposal 要被丢弃。解决流程:
- 当旧的 Leader 作为 Follower 接入新的 Leader 后,新的 Leader 会让它将所有的拥有旧的 epoch 号的未被 commit 的 proposal 清除。
- Author:mcbilla
- URL:http://mcbilla.com/article/791c3a91-f8b2-4643-950c-35753079eb8c
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!