分布式一致性协议

分布式一致性协议的演化

2PC

3PC

与 2PC 的区别是:

  1. 将 2PC 的 prepare 阶段拆为 CanCommit 和 PreCommit 两个阶段,其中:
    • CanCommit 阶段:参与者需要判断是否可以执行事务提交操作
      这个阶段如果出现超时、某个参与者返回 No 等情况,事务直接失败;
      在所有参与者 CanCommit 阶段都返回 Yes 响应后可以进入 PreCommit 阶段;
    • PreCommit 阶段:预提交即执行事务操作,并记录 undo 和 redo 日志,这时本地事务还未 commit
      这个阶段如果有任何一个参与者向协调者返回了 No 响应或超时协调者还未接收到参与者的响应,则协调者会开始执行事务的 abort;
      如果所有参与者都返回了 ACK 响应,则可以进入最终的 doCommit 阶段;
    • abort:上一步,如果要开始执行中断事务,协调者会向所有参与者发送 abort 请求,参与者接收
    • doCommit 阶段:

Paxos

与 2PC(及其衍生协议)区别

Paxos 协议和 2PC 协议在分布式系统中所起的作用并不相同。Paxos 协议用于保证同一个数据分片的多个副本之间的数据一致性。当这些副本分布到不同的数据中心时,这个需求尤其强烈。2PC 协议用于保证属于多个数据分片上的操作的原子性。这些数据分片可能分布在不同的服务器上,2PC 协议保证多台服务器上的操作要么全部成功,要么全部失败。
Paxos 协议有两种用法:一种用法是用它来实现全局的锁服务或者命名和配置服务,例如 Google Chubby 以及 Apache Zookeeper。另外一种用法是用它来将用户数据复制到多个数据中心,例如 Google Megastore 以及 Google Spanner。
2PC 协议最大的缺陷在于无法处理协调者宕机问题。如果协调者宕机,那么,2PC 协议中的每个参与者可能都不知道事务应该提交还是回滚,整个协议被阻塞,执行过程中申请的资源都无法释放。因此,常见的做法是将 2PC 和 Paxos 协议结合起来,通过 2PC 保证多个数据分片上的操作的原子性,通过 Paxos 协议实现同一个数据分片的多个副本之间的一致性(一般)。另外,通过 Paxos 协议解决 2PC 协议中协调者宕机问题。当 2PC 协议中的协调者出现故障时,通过 Paxos 协议选举出新的协调者继续提供服务,可以解决单点故障问题(但是你确定服务参与者足够多能够发起一次选举吗?)。

Paxos

Basic Paxos(单值Paxos)

目前在多个proposer可以同时发起提议的情况下,满足P1、P2a即能做到确定并只确定一个值。如果再加上节点宕机恢复、消息丢包的考量呢?
假设acceptor c 宕机一段时间后恢复,c 宕机期间其他acceptor已经确定了一项值为v的决议但c 因为宕机并不知晓;c 恢复后如果有proposer马上发起一项值不是v的提议,由于条件P1,c 会接受该提议,这与P2a矛盾。为了避免这样的情况出现,进一步地我们对proposer作约束:

1
P2b. 如果一项值为v的提议被确定,那么proposer后续只发起值为v的提议

满足P2b则P2a成立 (P2b => P2a => P2)。
P2b约束的是提议被确定(chosen)后proposer的行为,我们更关心提议被确定前proposer应该怎么做:

1
P2c. 对于提议(n,v),acceptor的多数派S中,如果存在acceptor最近一次(即ID值最大)接受的提议的值为v',那么要求v = v';否则v可为任意值

满足P2c则P2b成立 (P2c => P2b => P2a => P2)。

条件P2c是Basic Paxos的核心,光看P2c的描述可能会觉得一头雾水,我们通过 The Part-Time Parliament中的例子加深理解:
BasicPaxos例子
假设有A~E 5个acceptor,- 表示acceptor因宕机等原因缺席当次决议,x 表示acceptor不接受提议,o 表示接受提议;多数派acceptor接受提议后提议被确定,以上表格对应的决议过程如下:
ID为2的提议最早提出,根据P2c其提议值可为任意值,这里假设为a
acceptor A/B/C/E 在之前的决议中没有接受(accept)任何提议,因而ID为5的提议的值也可以为任意值,这里假设为b
acceptor B/D/E,其中D曾接受ID为2的提议,根据P2c,该轮ID为14的提议的值必须与ID为2的提议的值相同,为a
acceptor A/C/D,其中D曾接受ID为2的提议、C曾接受ID为5的提议,相比之下ID 5较ID 2大,根据P2c,该轮ID为27的提议的值必须与ID为5的提议的值相同,为b;该轮决议被多数派acceptor接受,因此该轮决议得以确定
acceptor B/C/D,3个acceptor之前都接受过提议,相比之下C、D曾接受的ID 27的ID号最大,该轮ID为29的提议的值必须与ID为27的提议的值相同,为b
以上提到的各项约束条件可以归纳为3点,如果proposer/acceptor满足下面3点,那么在少数节点宕机、网络分化隔离的情况下,在“确定并只确定一个值”这件事情上可以保证一致性(consistency):
B1(ß): ß中每一轮决议都有唯一的ID标识
B2(ß): 如果决议B被acceptor多数派接受,则确定决议B
B3(ß): 对于ß中的任意提议B(n,v),acceptor的多数派中如果存在acceptor最近一次(即ID值最大)接受的提议的值为v’,那么要求v = v’;否则v可为任意值
(注: 希腊字母ß表示多轮决议的集合,字母B表示一轮决议)
另外为保证P2c,我们对acceptor作两个要求:

  1. 记录曾接受的ID最大的提议,因proposer需要问询该信息以决定提议值
  2. 在回应提议ID为n的proposer自己曾接受过ID最大的提议时,acceptor同时保证(promise)不再接受ID小于n的提议
    至此,proposer/acceptor完成一轮决议可归纳为prepare和accept两个阶段。prepare阶段proposer发起提议问询提议值、acceptor回应问询并进行promise;accept阶段完成决议,图示如下:
    accept流程
    还有一个问题需要考量,假如proposer A发起ID为n的提议,在提议未完成前proposer B又发起ID为n+1的提议,在n+1提议未完成前proposer C又发起ID为n+2的提议…… 如此acceptor不能完成决议、形成活锁(livelock),虽然这不影响一致性,但我们一般不想让这样的情况发生。解决的方法是从proposer中选出一个leader,提议统一由leader发起。
    最后我们再引入一个新的角色:learner,learner依附于acceptor,用于习得已确定的决议。以上决议过程都只要求acceptor多数派参与,而我们希望尽量所有acceptor的状态一致。如果部分acceptor因宕机等原因未知晓已确定决议,宕机恢复后可经本机learner采用pull的方式从其他acceptor习得。

Multi Paxos(多值Paxos)

通过以上步骤分布式系统已经能确定一个值,“只确定一个值有什么用?这可解决不了我面临的问题。” 你心中可能有这样的疑问。
其实不断地进行“确定一个值”的过程、再为每个过程编上序号,就能得到具有全序关系(total order)的系列值,进而能应用在数据库副本存储等很多场景。我们把单次“确定一个值”的过程称为实例(instance),它由proposer/acceptor/learner组成,下图说明了A/B/C三机上的实例:
MultiPaxos
不同序号的实例之间互相不影响,A/B/C三机输入相同、过程实质等同于执行相同序列的状态机(state machine)指令 ,因而将得到一致的结果。
proposer leader在Multi Paxos中还有助于提升性能,常态下统一由leader发起提议,可节省prepare步骤(leader不用问询acceptor曾接受过的ID最大的提议、只有leader提议也不需要acceptor进行promise)直至发生leader宕机、重新选主。

总结

以上介绍了Paxos的推演过程、如何在Basic Paxos的基础上通过状态机构建Multi Paxos。
微信后台开发同学实现并开源了一套基于Paxos协议的多机状态拷贝类库PhxPaxos,PhxPaxos用于将单机服务扩展到多机,其经过线上系统验证并在一致性保证、性能等方面作了很多考量。

Paxos变种和优化

如果想把Paxos应用于工程实践,了解基本原理还不够。
有很多基于Paxos的优化,在保证一致性协议正确(safety)的前提下,减少Paxos决议通信步骤、避免单点故障、实现节点负载均衡,从而降低时延、增加吞吐量、提升可用性,下面我们就来了解这些Paxos变种。

Multi Paxos

首先我们来回顾一下Multi Paxos,Multi Paxos在Basic Paxos的基础上确定一系列值,其决议过程如下:
MultiPaxos例子
phase1a: leader提交提议给acceptor
phase1b: acceptor返回最近一次接受的提议(即曾接受的最大的提议ID和对应的value),未接受过提议则返回空
phase2a: leader收集acceptor的应答,分两种情况处理
phase2a.1: 如果应答内容都为空,则自由选择一个提议value
phase2a.2: 如果应答内容不为空,则选择应答里面ID最大的提议的value
phase2b: acceptor将决议同步给learner
Multi Paxos中leader用于避免活锁,但leader的存在会带来其他问题,一是如何选举和保持唯一leader(虽然无leader或多leader不影响一致性,但影响决议进程progress),二是充当leader的节点会承担更多压力,如何均衡节点的负载。Mencius[1]提出节点轮流担任leader,以达到均衡负载的目的;租约(lease)可以帮助实现唯一leader,但leader故障情况下可导致服务短期不可用。

Fast Paxos

在Multi Paxos中,proposer -> leader -> acceptor -> learner,从提议到完成决议共经过3次通信,能不能减少通信步骤?
对Multi Paxos phase2a,如果可以自由提议value,则可以让proposer直接发起提议、leader退出通信过程,变为proposer -> acceptor -> learner,这就是Fast Paxos[2]的由来。
FastPaxos例子
Multi Paxos里提议都由leader提出,因而不存在一次决议出现多个value,Fast Paxos里由proposer直接提议,一次决议里可能有多个proposer提议、出现多个value,即出现提议冲突(collision)。leader起到初始化决议进程(progress)和解决冲突的作用,当冲突发生时leader重新参与决议过程、回退到3次通信步骤。
Paxos自身隐含的一个特性也可以达到减少通信步骤的目标,如果acceptor上一次确定(chosen)的提议来自proposerA,则当次决议proposerA可以直接提议减少一次通信步骤。如果想实现这样的效果,需要在proposer、acceptor记录上一次决议确定(chosen)的历史,用以在提议前知道哪个proposer的提议上一次被确定、当次决议能不能节省一次通信步骤。

EPaxos

除了从减少通信步骤的角度提高Paxos决议效率外,还有其他方面可以降低Paxos决议时延,比如Generalized Paxos[3]提出不冲突的提议(例如对不同key的写请求)可以同时决议、以降低Paxos时延。
更进一步地,EPaxos[4](Egalitarian Paxos)提出一种既支持不冲突提议同时提交降低时延、还均衡各节点负载、同时将通信步骤减少到最少的Paxos优化方法。
为达到这些目标,EPaxos的实现有几个要点。一是EPaxos中没有全局的leader,而是每一次提议发起提议的proposer作为当次提议的leader(command leader);二是不相互影响(interfere)的提议可以同时提交;三是跳过prepare,直接进入accept阶段。EPaxos决议的过程如下:
EPaxos的accept过程

小结

以上介绍了几个基于Paxos的变种,Mencius中节点轮流做leader、均衡节点负载,Fast Paxos减少一次通信步骤,Generalized Paxos允许互不影响的决议同时进行,EPaxos无全局leader、各节点平等分担负载。

Raft

Paxos 偏向于理论、对如何应用到工程实践提及较少。理解的难度加上现实的骨感,在生产环境中基于 Paxos 实现一个正确的分布式系统非常难。
Raft 在 2013 年提出,提出的时间虽然不长,但已经有很多系统基于 Raft 实现。相比 Paxos,Raft 的优点就是更利于理解、更易于实行。
为达到更容易理解和实行的目的,Raft 将问题分解和具体化:Leader 统一处理变更操作请求,一致性协议的作用具化为保证节点间操作日志副本(log replication)一致,以 term 作为逻辑时钟(logical clock)保证时序,节点运行相同状态机(state machine)得到一致结果。Raft 协议具体过程如下:

Raft执行流程

  1. Client 发起请求,每一条请求包含操作指令
  2. 请求交由 Leader 处理,Leader 将操作指令(entry)追加(append)至操作日志,紧接着对 Follower 发起 AppendEntries 请求、尝试让操作日志副本在 Follower 落地
  3. 如果 Follower 多数派(quorum)同意 AppendEntries 请求,Leader 进行 commit 操作、把指令交由状态机处理
  4. 状态机处理完成后将结果返回给 Client

指令通过 log index(指令 id)和 term number 保证时序,正常情况下 Leader、Follower 状态机按相同顺序执行指令,得出相同结果、状态一致。
宕机、网络分化等情况可引起 Leader 重新选举(每次选举产生新 Leader 的同时,产生新的 term)、Leader/Follower 间状态不一致。Raft 中 Leader 为自己和所有 Follower 各维护一个 nextIndex 值,其表示 Leader 紧接下来要处理的指令 id 以及将要发给 Follower 的指令 id,LnextIndex 不等于 FnextIndex 时代表 Leader 操作日志和 Follower 操作日志存在不一致,这时将从 Follower 操作日志中最初不一致的地方开始,由 Leader 操作日志覆盖 Follower,直到 LnextIndex、FnextIndex 相等。
Paxos 中 Leader 的存在是为了提升决议效率,Leader 的有无和数目并不影响决议一致性,Raft 要求具备唯一 Leader,并把一致性问题具体化为保持日志副本的一致性,以此实现相较 Paxos 而言更容易理解、更容易实现的目标。

Zab

Zab 的全称是 Zookeeper atomic broadcast protocol,是 Zookeeper 内部用到的一致性协议。相比 Paxos,Zab 最大的特点是保证强一致性(strong consistency,或叫线性一致性 linearizable consistency)。
和 Raft 一样,Zab 要求唯一 Leader 参与决议,Zab 可以分解成 discovery、sync、broadcast 三个阶段:
Zab原理

  • discovery
    选举产生PL(prospective leader),PL 收集Follower epoch(cepoch),根据Follower的反馈 PL 产生newepoch(每次选举产生新 Leader 的同时产生新 epoch,类似 Raft 的 term)。
  • sync
    PL 补齐相比 Follower 多数派缺失的状态、之后各 Follower 再补齐相比 PL 缺失的状态,PL 和 Follower 完成状态同步后 PL 变为**正式 Leader(established leader)**。
  • broadcast
    Leader 处理 Client 的写操作,并将状态变更广播至 Follower,Follower多数派通过之后 Leader 发起将状态变更**提交(deliver/commit)**。

Leader 和 Follower 之间通过心跳判别健康状态,正常情况下 Zab 处在 broadcast 阶段,出现 Leader 宕机、网络隔离等异常情况时 Zab 重新回到 discovery 阶段。

Zookeeper介绍

Zookeeper是一个开源的分布式协调服务,其设计目标是将那些复杂的且容易出错的分布式一致性服务封装起来,构成一个高效可靠的原语集,并以一些列简单的接口提供给用户使用。其是一个典型的分布式数据一致性的解决方案,分布式应用程序可以基于它实现诸如数据发布/发布、负载均衡、命名服务、分布式协调/通知、集群管理、Master选举、分布式锁和分布式队列等功能。其可以保证如下分布式一致性特性。

  1. 顺序一致性,从同一个客户端发起的事务请求,最终将会严格地按照其发起顺序被应用到Zookeeper中去。
  2. 原子性,所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,即整个集群要么都成功应用了某个事务,要么都没有应用。
  3. 单一视图,无论客户端连接的是哪个Zookeeper服务器,其看到的服务端数据模型都是一致的。
  4. 可靠性,一旦服务端成功地应用了一个事务,并完成对客户端的响应,那么该事务所引起的服务端状态变更将会一直被保留,除非有另一个事务对其进行了变更。
  5. 实时性,Zookeeper保证在一定的时间段内,客户端最终一定能够从服务端上读取到最新的数据状态。

设计目标

Zookeeper致力于提供一个高性能、高可用、且具有严格的顺序访问控制能力(主要是写操作的严格顺序性)的分布式协调服务,其具有如下的设计目标。

  1. 简单的数据模型,Zookeeper使得分布式程序能够通过一个共享的树形结构的名字空间来进行相互协调,即Zookeeper服务器内存中的数据模型由一系列被称为ZNode的数据节点组成,Zookeeper将全量的数据存储在内存中,以此来提高服务器吞吐、减少延迟的目的。
  2. 可构建集群,一个Zookeeper集群通常由一组机器构成,组成Zookeeper集群的而每台机器都会在内存中维护当前服务器状态,并且每台机器之间都相互通信。
  3. 顺序访问,对于来自客户端的每个更新请求,Zookeeper都会分配一个全局唯一的递增编号,这个编号反映了所有事务操作的先后顺序。
  4. 高性能,Zookeeper将全量数据存储在内存中,并直接服务于客户端的所有非事务请求,因此它尤其适用于以读操作为主的应用场景。

基本概念

  1. 集群角色,最典型的集群就是Master/Slave模式(主备模式),此情况下把所有能够处理写操作的机器称为Master机器,把所有通过异步复制方式获取最新数据,并提供读服务的机器为Slave机器。Zookeeper引入了Leader、Follower、Observer三种角色,Zookeeper集群中的所有机器通过Leader选举过程来选定一台被称为Leader的机器,Leader服务器为客户端提供写服务,Follower和Observer提供读服务,但是Observer不参与Leader选举过程,不参与写操作的过半写成功策略,Observer可以在不影响写性能的情况下提升集群的性能。
  2. 会话,指客户端会话,一个客户端连接是指客户端和服务端之间的一个TCP长连接,Zookeeper对外的服务端口默认为2181,客户端启动的时候,首先会与服务器建立一个TCP连接,从第一次连接建立开始,客户端会话的生命周期也开始了,通过这个连接,客户端能够心跳检测与服务器保持有效的会话,也能够向Zookeeper服务器发送请求并接受响应,同时还能够通过该连接接受来自服务器的Watch事件通知。
  3. 数据节点,第一类指构成集群的机器,称为机器节点,第二类是指数据模型中的数据单元,称为数据节点-Znode,Zookeeper将所有数据存储在内存中,数据模型是一棵树,由斜杠/进行分割的路径,就是一个ZNode,如/foo/path1,每个ZNode都会保存自己的数据内存,同时还会保存一些列属性信息。ZNode分为持久节点和临时节点两类,持久节点是指一旦这个ZNode被创建了,除非主动进行ZNode的移除操作,否则这个ZNode将一直保存在Zookeeper上,而临时节点的生命周期和客户端会话绑定,一旦客户端会话失效,那么这个客户端创建的所有临时节点都会被移除。另外,Zookeeper还允许用户为每个节点添加一个特殊的属性:SEQUENTIAL。一旦节点被标记上这个属性,那么在这个节点被创建的时候,Zookeeper会自动在其节点后面追加一个整形数字,其是由父节点维护的自增数字。
  4. 版本,对于每个ZNode,Zookeeper都会为其维护一个叫作Stat的数据结构,Stat记录了这个ZNode的三个数据版本,分别是version(当前ZNode的版本)、cversion(当前ZNode子节点的版本)、aversion(当前ZNode的ACL版本)。
  5. Watcher,Zookeeper允许用户在指定节点上注册一些Watcher,并且在一些特定事件触发的时候,Zookeeper服务端会将事件通知到感兴趣的客户端。
  6. ACL,Zookeeper采用ACL(Access Control Lists)策略来进行权限控制,其定义了如下五种权限:
    · CREATE:创建子节点的权限。
    · READ:获取节点数据和子节点列表的权限。
    · WRITE:更新节点数据的权限。
    · DELETE:删除子节点的权限。
    · ADMIN:设置节点ACL的权限。

ZAB协议

Zookeeper使用了Zookeeper Atomic Broadcast(ZAB,Zookeeper原子消息广播协议)的协议作为其数据一致性的核心算法。ZAB协议是为Zookeeper专门设计的一种支持崩溃恢复的原子广播协议。
Zookeeper依赖ZAB协议来实现分布式数据的一致性,基于该协议,Zookeeper实现了一种主备模式的系统架构来保持集群中各副本之间的数据的一致性,即其使用一个单一的主进程来接收并处理客户端的所有事务请求,并采用ZAB的原子广播协议,将服务器数据的状态变更以事务Proposal的形式广播到所有的副本进程中,ZAB协议的主备模型架构保证了同一时刻集群中只能够有一个主进程来广播服务器的状态变更,因此能够很好地处理客户端大量的并发请求。
ZAB协议的核心是定义了对于那些会改变Zookeeper服务器数据状态的事务请求的处理方式,即:所有事务请求必须由一个全局唯一的服务器来协调处理,这样的服务器被称为Leader服务器,余下的服务器则称为Follower服务器,Leader服务器负责将一个客户端事务请求转化成一个事务Proposal(提议),并将该Proposal分发给集群中所有的Follower服务器,之后Leader服务器需要等待所有Follower服务器的反馈,一旦超过半数的Follower服务器进行了正确的反馈后,那么Leader就会再次向所有的Follower服务器分发Commit消息,要求其将前一个Proposal进行提交。
ZAB协议包括两种基本的模式:崩溃恢复和消息广播。
当整个服务框架启动过程中或Leader服务器出现网络中断、崩溃退出与重启等异常情况时,ZAB协议就会进入恢复模式并选举产生新的Leader服务器。当选举产生了新的Leader服务器,同时集群中已经有过半的机器与该Leader服务器完成了状态同步之后,ZAB协议就会退出恢复模式,状态同步时指数据同步,用来保证集群在过半的机器能够和Leader服务器的数据状态保持一致。
当集群中已经有过半的Follower服务器完成了和Leader服务器的状态同步,那么整个服务框架就可以进入消息广播模式,当一台同样遵守ZAB协议的服务器启动后加入到集群中,如果此时集群中已经存在一个Leader服务器在负责进行消息广播,那么加入的服务器就会自觉地进入数据恢复模式:找到Leader所在的服务器,并与其进行数据同步,然后一起参与到消息广播流程中去。Zookeeper只允许唯一的一个Leader服务器来进行事务请求的处理,Leader服务器在接收到客户端的事务请求后,会生成对应的事务提议并发起一轮广播协议,而如果集群中的其他机器收到客户端的事务请求后,那么这些非Leader服务器会首先将这个事务请求转发给Leader服务器。
当Leader服务器出现崩溃或者机器重启、集群中已经不存在过半的服务器与Leader服务器保持正常通信时,那么在重新开始新的一轮的原子广播事务操作之前,所有进程首先会使用崩溃恢复协议来使彼此到达一致状态,于是整个ZAB流程就会从消息广播模式进入到崩溃恢复模式。一个机器要成为新的Leader,必须获得过半机器的支持,同时由于每个机器都有可能会崩溃,因此,ZAB协议运行过程中,前后会出现多个Leader,并且每台机器也有可能会多次成为Leader,进入崩溃恢复模式后,只要集群中存在过半的服务器能够彼此进行正常通信,那么就可以产生一个新的Leader并再次进入消息广播模式。如一个由三台机器组成的ZAB服务,通常由一个Leader、2个Follower服务器组成,某一个时刻,加入其中一个Follower挂了,整个ZAB集群是不会中断服务的。

  1. 消息广播,ZAB协议的消息广播过程使用原子广播协议,类似于一个二阶段提交过程,针对客户端的事务请求,Leader服务器会为其生成对应的事务Proposal,并将其发送给集群中其余所有的机器,然后再分别收集各自的选票,最后进行事务提交。

在ZAB的二阶段提交过程中,移除了中断逻辑,所有的Follower服务器要么正常反馈Leader提出的事务Proposal,要么就抛弃Leader服务器,同时,ZAB协议将二阶段提交中的中断逻辑移除意味着我们可以在过半的Follower服务器已经反馈Ack之后就开始提交事务Proposal,而不需要等待集群中所有的Follower服务器都反馈响应,但是,在这种简化的二阶段提交模型下,无法处理Leader服务器崩溃退出而带来的数据不一致问题,因此ZAB采用了崩溃恢复模式来解决此问题,另外,整个消息广播协议是基于具有FIFO特性的TCP协议来进行网络通信的,因此能够很容易保证消息广播过程中消息接受与发送的顺序性。再整个消息广播过程中,Leader服务器会为每个事务请求生成对应的Proposal来进行广播,并且在广播事务Proposal之前,Leader服务器会首先为这个事务Proposal分配一个全局单调递增的唯一ID,称之为事务ID(ZXID),由于ZAB协议需要保证每个消息严格的因果关系(顺序一致性>因果一致性),因此必须将每个事务Proposal按照其ZXID的先后顺序来进行排序和处理。

  1. 崩溃恢复,在Leader服务器出现崩溃,或者由于网络原因导致Leader服务器失去了与过半Follower的联系,那么就会进入崩溃恢复模式,在ZAB协议中,为了保证程序的正确运行,整个恢复过程结束后需要选举出一个新的Leader服务器,因此,ZAB协议需要一个高效且可靠的Leader选举算法,从而保证能够快速地选举出新的Leader,同时,Leader选举算法不仅仅需要让Leader自身知道已经被选举为Leader,同时还需要让集群中的所有其他机器也能够快速地感知到选举产生的新的Leader服务器。
  2. 基本特性,ZAB协议规定了如果一个事务Proposal在一台机器上被处理成功,那么应该在所有的机器上都被处理成功,哪怕机器出现故障崩溃。ZAB协议需要确保那些已经在Leader服务器上提交的事务最终被所有服务器都提交,假设一个事务在Leader服务器上被提交了,并且已经得到了过半Follower服务器的Ack反馈,但是在它Commit消息发送给所有Follower机器之前,Leader服务挂了。如下图所示

在集群正常运行过程中的某一个时刻,Server1是Leader服务器,其先后广播了P1、P2、C1、P3、C2(C2是Commit Of Proposal2的缩写),其中,当Leader服务器发出C2后就立即崩溃退出了,针对这种情况,ZAB协议就需要确保事务Proposal2最终能够在所有的服务器上都被提交成功,否则将出现不一致。
ZAB协议需要确保丢弃那些只在Leader服务器上被提出的事务。如果在崩溃恢复过程中出现一个需要被丢弃的提议,那么在崩溃恢复结束后需要跳过该事务Proposal,如下图所示
假设初始的Leader服务器Server1在提出一个事务Proposal3之后就崩溃退出了,从而导致集群中的其他服务器都没有收到这个事务Proposal,于是,当Server1恢复过来再次加入到集群中的时候,ZAB协议需要确保丢弃Proposal3这个事务。
在上述的崩溃恢复过程中需要处理的特殊情况,就决定了ZAB协议必须设计这样的Leader选举算法:能够确保提交已经被Leader提交的事务的Proposal,同时丢弃已经被跳过的事务Proposal。如果让Leader选举算法能够保证新选举出来的Leader服务器拥有集群中所有机器最高编号(ZXID最大)的事务Proposal,那么就可以保证这个新选举出来的Leader一定具有所有已经提交的提议,更为重要的是如果让具有最高编号事务的Proposal机器称为Leader,就可以省去Leader服务器查询Proposal的提交和丢弃工作这一步骤了。

  1. 数据同步,完成Leader选举后,在正式开始工作前,Leader服务器首先会确认日志中的所有Proposal是否都已经被集群中的过半机器提交了,即是否完成了数据同步。Leader服务器需要确所有的Follower服务器都能够接收到每一条事务Proposal,并且能够正确地将所有已经提交了的事务Proposal应用到内存数据库中。Leader服务器会为每个Follower服务器维护一个队列,并将那些没有被各Follower服务器同步的事务以Proposal消息的形式逐个发送给Follower服务器,并在每一个Proposal消息后面紧接着再发送一个Commit消息,以表示该事务已经被提交,等到Follower服务器将所有其尚未同步的事务Proposal都从Leader服务器上同步过来并成功应用到本地数据库后,Leader服务器就会将该Follower服务器加入到真正的可用Follower列表并开始之后的其他流程。
    下面分析ZAB协议如何处理需要丢弃的事务Proposal的,ZXID是一个64位的数字,其中32位可以看做是一个简单的单调递增的计数器,针对客户端的每一个事务请求,Leader服务器在产生一个新的事务Proposal时,都会对该计数器进行加1操作,而高32位则代表了Leader周期epoch的编号,每当选举产生一个新的Leader时,就会从这个Leader上取出其本地日志中最大事务Proposal的ZXID,并解析出epoch值,然后加1,之后以该编号作为新的epoch,低32位则置为0来开始生成新的ZXID,ZAB协议通过epoch号来区分Leader周期变化的策略,能够有效地避免不同的Leader服务器错误地使用不同的ZXID编号提出不一样的事务Proposal的异常情况。当一个包含了上一个Leader周期中尚未提交过的事务Proposal的服务器启动时,其肯定无法成为Leader,因为当前集群中一定包含了一个Quorum(过半)集合,该集合中的机器一定包含了更高epoch的事务的Proposal,因此这台机器的事务Proposal并非最高,也就无法成为Leader。
    2.4 ZAB协议原理
    ZAB主要包括消息广播和崩溃恢复两个过程,进一步可以分为三个阶段,分别是发现(Discovery)、同步(Synchronization)、广播(Broadcast)阶段。ZAB的每一个分布式进程会循环执行这三个阶段,称为主进程周期。
    · 发现,选举产生PL(prospective leader),PL收集Follower epoch(cepoch),根据Follower的反馈,PL产生newepoch(每次选举产生新Leader的同时产生新epoch)。
    · 同步,PL补齐相比Follower多数派缺失的状态、之后各Follower再补齐相比PL缺失的状态,PL和Follower完成状态同步后PL变为正式Leader(established leader)。
    · 广播,Leader处理客户端的写操作,并将状态变更广播至Follower,Follower多数派通过之后Leader发起将状态变更落地(deliver/commit)。
    在正常运行过程中,ZAB协议会一直运行于阶段三来反复进行消息广播流程,如果出现崩溃或其他原因导致Leader缺失,那么此时ZAB协议会再次进入发现阶段,选举新的Leader。
    2.4.1 运行分析
    每个进程都有可能处于如下三种状态之一
    · LOOKING:Leader选举阶段。
    · FOLLOWING:Follower服务器和Leader服务器保持同步状态。
    · LEADING:Leader服务器作为主进程领导状态。
    所有进程初始状态都是LOOKING状态,此时不存在Leader,此时,进程会试图选举出一个新的Leader,之后,如果进程发现已经选举出新的Leader了,那么它就会切换到FOLLOWING状态,并开始和Leader保持同步,处于FOLLOWING状态的进程称为Follower,LEADING状态的进程称为Leader,当Leader崩溃或放弃领导地位时,其余的Follower进程就会转换到LOOKING状态开始新一轮的Leader选举。
    一个Follower只能和一个Leader保持同步,Leader进程和所有与所有的Follower进程之间都通过心跳检测机制来感知彼此的情况。若Leader能够在超时时间内正常收到心跳检测,那么Follower就会一直与该Leader保持连接,而如果在指定时间内Leader无法从过半的Follower进程那里接收到心跳检测,或者TCP连接断开,那么Leader会放弃当前周期的领导,比你转换到LOOKING状态,其他的Follower也会选择放弃这个Leader,同时转换到LOOKING状态,之后会进行新一轮的Leader选举,并在选举产生新的Leader之后开始新的一轮主进程周期。
    2.5 ZAB与Paxos的联系和区别
    联系:
  2. 都存在一个类似于Leader进程的角色,由其负责协调多个Follower进程的运行。
  3. Leader进程都会等待超过半数的Follower做出正确的反馈后,才会将一个提议进行提交。
  4. 在ZAB协议中,每个Proposal中都包含了一个epoch值,用来代表当前的Leader周期,在Paxos算法中,同样存在这样的一个标识,名字为Ballot。
    区别:
    Paxos算法中,新选举产生的主进程会进行两个阶段的工作,第一阶段称为读阶段,新的主进程和其他进程通信来收集主进程提出的提议,并将它们提交。第二阶段称为写阶段,当前主进程开始提出自己的提议。
    ZAB协议在Paxos基础上添加了同步阶段,此时,新的Leader会确保存在过半的Follower已经提交了之前的Leader周期中的所有事务Proposal。
    ZAB协议主要用于构建一个高可用的分布式数据主备系统,而Paxos算法则用于构建一个分布式的一致性状态机系统。

Zab 如何保证强一致

Zab 通过约束事务先后顺序达到强一致性,先广播的事务先 commit、FIFO,Zab 称之为**primary order(以下简称 PO)**。实现 PO 的核心是zxid
Zab 中每个事务对应一个 zxid,它由两部分组成:<e, c>,e 即 Leader 选举时生成的 epoch,c 表示当次 epoch 内事务的编号、依次递增。假设有两个事务的 zxid 分别是 z、z’,当满足 z.e < z'.e 或者 z.e = z'.e && z.c < z'.c 时,定义 z 先于 z’发生(z < z')。
为实现 PO,Zab 对 Follower、Leader 有以下约束:

  1. 有事务 z 和 z’,如果 Leader 先广播 z,则 Follower 需保证先 commit z 对应的事务
  2. 有事务 z 和 z’,z 由 Leader p 广播,z’由 Leader q 广播,Leader p 先于 Leader q,则 Follower 需保证先 commit z 对应的事务
  3. 有事务 z 和 z’,z 由 Leader p 广播,z’由 Leader q 广播,Leader p 先于 Leader q,如果 Follower 已经 commit z,则 q 需保证已 commit z 才能广播 z’

第 1、2 点保证事务 FIFO,第 3 点保证 Leader 上具备所有已 commit 的事务。
相比 Paxos,Zab 约束了事务顺序、适用于有强一致性需求的场景。

Paxos、Raft、Zab 比较

除 Paxos、Raft 和 Zab 外,Viewstamped Replication(简称 VR)也是讨论比较多的一致性协议。这些协议包含很多共同的内容(Leader、quorum、state machine 等),因而我们不禁要问:Paxos、Raft、Zab 和 VR 等分布式一致性协议区别到底在哪,还是根本就是一回事?
Paxos、Raft、Zab 和 VR 都是解决一致性问题的协议,Paxos 协议原文倾向于理论,Raft、Zab、VR 倾向于实践,一致性保证程度等的不同也导致这些协议间存在差异。下图帮助我们理解这些协议的相似点和区别
Paxos、Raft、Zab比较

参考

Paxos

  1. 分布式系统理论进阶 - Paxos
  2. Paxos Made Simple
  3. The Part-Time Parliament
  4. Tencent/phxpaxos

Raft

  1. [1] Paxos made live - An engineering perspective, Tushar Chandra, Robert Griesemer and Joshua Redstone, 2007
  2. [2] In Search of an Understandable Consensus Algorithm, Diego Ongaro and John Ousterhout, 2013
  3. [3] In Search of an Understandable Consensus Algorithm (Extended Version), Diego Ongaro and John Ousterhout, 2013
  4. [4] Implementing Fault-Tolerant Services Using the State Machine, Fred B. Schneider, 1990

Zab

  1. [5] Zab:High-performance broadcast for primary-backup systems, FlavioP.Junqueira,BenjaminC.Reed,andMarcoSerafini, 2011
  2. [6] ZooKeeper’s atomic broadcast protocol: Theory and practice, Andr´e Medeiros, 2012
  3. [7] Viewstamped Replication A New Primary Copy Method to Support Highly-Available Distributed Systems, Brian M.Oki and Barbar H.Liskov, 1988
  4. [8] Viewstamped Replication Revisited, Barbara Liskov and James Cowling, Barbara Liskov and James Cowling ,2012
  5. [9] Can’t we all just agree? The morning paper, 2015
  6. [10] Vive La Difference: Paxos vs. Viewstamped Replication vs. Zab, Robbert van Renesse, Nicolas Schiper and Fred B. Schneider, 2014

Paxos 变种和优化

  1. [1] Mencius: Building Efficient Replicated State Machines for WANs, Yanhua Mao,Flavio P. Junqueira,Keith Marzullo, 2018
  2. [2] Fast Paxos, Leslie Lamport, 2005
  3. [3] Generalized Consensus and Paxos, Leslie Lamport, 2004
  4. [4] There Is More Consensus in Egalitarian Parliaments, Iulian Moraru, David G. Andersen, Michael Kaminsky, 2013