那聊 **ZAB **协议,先得看下 Zookeeper 是个什么玩意。
Zookeeper 是一个分布式数据一致性的解决方案,分布式应用可以基于它实现诸如数据发布/订阅,负载均衡,命名服务,分布式协调/通知,集群管理,Master 选举,分布式锁和分布式队列等功能。 Zookeeper 致力于提供一个高性能、高可用、且具有严格的顺序访问控制能力的分布式协调系统 。
好像确实挺牛逼的,能干这么多事呢,看来搞它是必须的呀!
考虑到 Zookeeper 主要操作数据的状态,为了保证状态的一致性,Zookeeper提出了两个安全属性:
为了保证上述两个安全属性,Zookeeper 使用了 TCP 协议 和 Leader :
而 ZAB(Zookeeper Atomic Broadcast 即 Zookeeper 原子消息广播协议)正是作为其数据一致性的核心算法,下面介绍一下 ZAB 协议。
ZAB ,Zookeeper Atomic Broadcast,zk 原子消息广播协议,是专为 ZooKeeper 设计的一 种支持崩溃恢复的原子广播协议。在 Zookeeper 中,基于该协议,ZooKeeper 实现了一种主从模式的系统架构来保持集群中各个副本之间的数据一致性。
Zookeeper 使用一个单一主进程来接收并处理客户端的所有事务请求,即写请求。当服 务器数据的状态发生变更后,集群采用 ZAB 原子广播协议,以事务提案 Proposal 的形式广 播到所有的副本进程上。ZAB 协议能够保证一个全局的变更序列,即可以为每一个事务分配 一个全局的递增编号 xid。
当 Zookeeper 客户端连接到 Zookeeper 集群的一个节点后,若客户端提交的是读请求, 那么当前节点就直接根据自己保存的数据对其进行响应;如果是写请求且当前节点不是 Leader,那么节点就会将该写请求转发给 Leader,Leader 会以提案的方式广播该写操作,只 要有超过半数节点同意该写操作,则该写操作请求就会被提交。然后 Leader 会再次广播给 所有订阅者,即 Learner,通知它们同步数据。
ZAB 协议要求每个 Leader 都要经历三个阶段: 发现,同步,广播 。
ZAB 协议的核心:定义了事务请求的处理方式
ZAB 协议包括两种基本的模式:崩溃恢复 和 消息广播
当整个集群启动过程中,或者当 Leader 服务器出现网络中弄断、崩溃退出或重启等异常时,ZAB 协议就会 进入崩溃恢复模式 ,选举产生新的 Leader。
当选举产生了新的 Leader,同时集群中有过半的机器与该 Leader 服务器完成了状态同步(即数据同步)之后,ZAB 协议就会退出崩溃恢复模式, 进入消息广播模式 。
这时,如果有一台遵守 ZAB 协议的服务器加入集群,那么因为此时集群中已经存在一个 Leader 服务器在广播消息,那么该新加入的服务器自动进入恢复模式:找到 Leader 服务器,并且完成数据同步。同步完成后,作为新的 Follower 一起参与到消息广播流程中。
当 Leader 出现崩溃退出或者机器重启,亦或是集群中不存在超过半数的服务器与 Leader 保存正常通信,ZAB 就会再一次进入崩溃恢复,发起新一轮Leader 选举并实现数据同步。同步完成后又会进入消息广播模式,接收事务请求。
在整个消息广播中,Leader 会将每一个事务请求转换成对应的 Proposal 来进行广播,并且在广播 事务 Proposal 之前,Leader 服务器会首先为这个事务 Proposal 分配一个全局单递增的唯一ID,称之为事务ID(即 zxid),由于 Zab 协议需要保证每一个消息的严格的顺序关系,因此必须将每一个 Proposal 按照其 zxid 的先后顺序进行排序和处理。
一旦 Leader 服务器出现崩溃或者由于网络原因导致 Leader 服务器失去了与过半 Follower 的联系,那么就会进入崩溃恢复模式。
前面我们说过,崩溃恢复具有两个阶段: Leader 选举 与 初始化同步 。当完成 Leader 选 举后,此时的 Leader 还是一个准 Leader,其要经过初始化同步后才能变为真正的 Leader。
具体过程如下:
当集群正在启动过程中,或 Leader 与超过半数的主机断连后,集群就进入了恢复模式。对于要恢复的数据状态需要遵循两个原则。
当 Leader 收到超过半数 Follower 的 ACKs 后,就向各个 Follower 广播 COMMIT 消息, 批准各个 Server 执行该写操作事务。当各个 Server 在接收到 Leader 的 COMMIT 消息后就会在本地执行该写操作,然后会向客户端响应写操作成功。
但是如果在非全部 Follower 收到 COMMIT 消息之前 Leader 就挂了,这将导致一种后 果: 部分 Server 已经执行了该事务,而部分 Server 尚未收到 COMMIT 消息 ,所以其并没有执行该事务。当新的 Leader 被选举出,集群经过恢复模式后需要保证所有 Server 上都执行 了那些已经被部分 Server 执行过的事务。
当新事务在 Leader 上已经通过,其已经将该事务更新到了本地,但所有 Follower 还都没有收到 COMMIT 之前,Leader 宕机了(比前面叙述的宕机更早),此时,所有 Follower 根本 就不知道该 Proposal 的存在。当新的 Leader 选举出来,整个集群进入正常服务状态后,之 前挂了的 Leader 主机重新启动并注册成为了 Follower。若那个别人根本不知道的 Proposal 还保留在那个主机,那么其数据就会比其它主机多出了内容,导致整个系统状态的不一致。所以,该 Proposa 应该被丢弃。类似这样应该被丢弃的事务,是不能再次出现在集群中的, 应该被清除。
当集群中的 Learner 完成了初始化状态同步,那么整个 zk 集群就进入到了正常工作模式 了。
如果集群中的 Learner 节点收到客户端的事务请求,那么这些 Learner 会将请求转发给 Leader 服务器。然后再执行如下的具体过程:
为了避免 Zookeeper 的单点问题,zk 也是以集群的形式出现的。zk 集群中的角色主要有 以下三类:
这三类角色在不同的情况下又有一些不同的名称,在zookeeper源码中的定义,可以了解下,看源码可能会少点疑惑的。
在 ZAB 中有三个很重要的数据:
每当选举产生一个新的 Leader ,就会从这个 Leader 服务器上取出本地事务日志中最大编号 Proposal 的 zxid,并从 zxid 中解析得到对应的 epoch 编号,然后再对其加1,之后该编号就作为新的 epoch 值,并将低32位数字归零,由0开始重新生成 zxid。
zk 集群中的每一台主机,在不同的阶段会处于不同的状态。每一台主机具有四种状态。
代码实现中,多了一种状态:Observing 状态是 Zookeeper 引入 Observer 之后加入的,Observer 不参与选举,是只读节点,实际上跟 ZAB 协议没有关系。这里为了阅读源码加上此概念。
节点在一开始都处于选举节点,只要有一个节点得到超过半数节点的票数,它就可以当选准 Leader,只有到达第三个阶段(也就是同步阶段),这个准 Leader 才会成为真正的 Leader。
Zookeeper 规定所有有效的投票都必须在同一个 轮次 中,每个服务器在开始新一轮投票时,都会对自己维护的 logicalClock 进行自增操作 。
每个服务器在广播自己的选票前,会将自己的投票箱(recvset)清空。该投票箱记录了所收到的选票。
例如:Server_2 投票给 Server_3,Server_3 投票给 Server_1,则Server_1的投票箱为 (2,3)、(3,1)、(1,1)。(每个服务器都会默认给自己投票)
前一个数字表示投票者,后一个数字表示被选举者。票箱中只会记录每一个投票者的最后一次投票记录,如果投票者更新自己的选票,则其他服务器收到该新选票后会在自己的票箱中更新该服务器的选票。
这一阶段的目的就是为了选出一个准 Leader ,然后进入下一个阶段。
在这个阶段,Followers 和上一轮选举出的准 Leader 进行通信,同步 Followers 最近接收的事务 Proposal 。
这个阶段的主要目的是发现当前大多数节点接收的最新 Proposal,并且准 Leader 生成新的 epoch ,让 Followers 接收,更新它们的 acceptedEpoch 。
同步阶段主要是利用 Leader 前一阶段获得的最新 Proposal 历史,同步集群中所有的副本 。
只有当 quorum(超过半数的节点) 都同步完成,准 Leader 才会成为真正的 Leader。Follower 只会接收 zxid 比自己 lastZxid 大的 Proposal。
到了这个阶段,Zookeeper 集群才能正式对外提供事务服务,并且 Leader 可以进行消息广播。同时,如果有新的节点加入,还需要对新节点进行同步。需要注意的是,ZAB 提交事务并不像 2PC 一样需要全部 Follower 都 Ack,只需要得到 quorum(超过半数的节点)的Ack 就可以。
上面已经针对 ZAB 协议涉及流程作了详细的描述,那么它和 Paxos 是什么关系呢?
ZAB 的作者认为ZAB 与 Paxos 并不相同,之所以没有采用 Paxos 是因为Paxos 保证不了全序顺序:
Because multiple leaders can propose a value for a given instance two problems arise. First, proposals can conflict. Paxos uses ballots to detect and resolve conflicting proposals. Second, it is not enough to know that a given instance number has been committed, processes must also be able to fi gure out which value has been committed.
Paxos 算法的确是不关心请求之间的逻辑顺序,而只考虑数据之间的全序,但很少有人直接使用 Paxos 算法,都会经过一定的简化、优化。
Google的粗粒度锁服务Chubby的设计开发者Burrows曾经说过:“ 所有一致性协议本质上要么是 Paxos 要么是其变体 ”。这句话还是有一定道理的,ZAB本质上就是 Paxos 的一种简化形式。
本文主要讲解了ZAB 上述一系列巧妙的设计,比如:为了加快收敛速度避免活锁引发的竞争引入了 Leader 角色,在正常情况下最多只有一个参与者扮演 Leader角色,其他参与者扮演 Acceptor;在这种优化算法中,只有 Leader 可以提出议案,从而避免了竞争使得算法能够快速地收敛而趋于一致;而为了保证 Leader 的健壮性,又引入了 Leader 选举,再考虑到同步的阶段,提出了消息广播和崩溃初始化同步以及恢复模式的两个原则。
全部0条评论
快来发表一下你的评论吧 !