分布式一致性算法简介

文章目录

  • 基础理论
    • ACID
      • Atomic 原子性
      • Consistency 一致性
      • Isolation 隔离性
      • Durability 持久性
    • CAP
      • Consistency 一致性
      • Availability 可用性
      • Partition tolerance 分区容错性
    • BASE
      • Basically Available 基本可用
      • Soft state 软状态
      • Eventually consistent 最终一致性
  • 分布式一致性算法
    • 时间戳和向量时钟
      • 时间戳
      • 向量时钟
    • 2PC
      • 阶段1 提交事务请求,即投票阶段
      • 阶段2 执行事务提交,即执行阶段
      • 优缺点
    • 3PC
      • 阶段1 CanCommit
      • 阶段2 PreCommit
      • 阶段3 DoCommit
      • 优缺点
    • Paxos
      • 协议角色
      • 协议流程
    • Raft
      • 协议角色
      • 协议流程
        • Leader选举
        • 日志复制
    • ZAB
      • Zab节点状态及相关术语说明
      • Zab协议流程
    • NWR
    • ISR
  • Reference

基础理论

  尽管分布式一致性算法强调了算法首要解决的问题是一致性,但其实这些算法不会仅仅考虑一致性问题。在介绍具体算法前先简要介绍一下相关的基础理论[1]

ACID

  ACID是事务的四大特性[2],分别为:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。

Atomic 原子性

  事务必须是一个原子的操作序列单元,事务中包含的各项操作在一次执行过程中,要么全部执行成功,要么全部不执行,任何一项失败,整个事务回滚,只有全部都执行成功,整个事务才算成功。

Consistency 一致性

  事务的执行不能破坏数据库数据的完整性和一致性,事务在执行之前和之后,数据库都必须处于一致性状态。
  个人感觉一致性是比较难以理解的,因为一致性和其他的特性有本质上的差别:一致性是针对业务的特性,而其他的AID都是针对数据库本身的特性[3]。举个例子,在一个银行系统中,如果A有100元,B有200元,当A向B转账50元后,A应当为50元,B应当为250元。注意,为什么这里就应当这样呢?因为这样才能保证A+B的总量保持不变。为什么A+B的总量保持不变呢?因为这是业务的特性,银行不能凭空增加总量或者减少总量。所以,所谓的一致性,不能脱离具体的应用场景,它是和业务绑定的,它需要满足一定的约束条件。

Isolation 隔离性

  在并发环境中,并发的事务是相互隔离的,一个事务的执行不能被其他事务干扰。
  SQL中4个事务隔离级别:

  1. 读未提交。如果一个事务正在处理某一数据,并对其进行了更新,但同时尚未完成事务,因此事务没有提交,与此同时,允许另一个事务也能够访问该数据。
  2. 读已提交。只允许读到已经提交的数据。
  3. 可重复读。保证在事务处理过程中,多次读取同一个数据时,其值都和事务开始时刻时是一致的。
  4. 串行化。最严格的事务,要求所有事务被串行执行,不能并发执行。

Durability 持久性

  一个事务一旦提交,它对数据库中对应数据的状态变更就应该是永久性的,即使发生系统崩溃或机器宕机,只要数据库能够重新启动,那么一定能够将其恢复到事务成功结束时的状态。

CAP

  一个分布式系统不可能同时满足一致性Consistency、可用性Availability、分区容错性Partition tolerance这三个基本需求,最多只能同时满足其中的两项[4]

Consistency 一致性

  分布式环境中,一致性是指多个副本之间保持一致的特性。

Availability 可用性

  系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。

Partition tolerance 分区容错性

  分布式系统在遇到任何网络分区故障时,仍然需要能够保证对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障。

BASE

  BASE即指代Basically Available(基本可用)、Soft state(软状态)、Eventually consistent(最终一致性),基于CAP定理演化而来,核心思想是即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性[1]

Basically Available 基本可用

  基本可用是指分布式系统在出现不可预知的故障的时候,允许损失部分可用性,但不等于系统不可用。如响应时间上的损失,当出现故障时,响应时间增加;功能上的损失,当流量高峰期时,屏蔽一些功能的使用以保证系统稳定性(服务降级)。

Soft state 软状态

  与硬状态相对,即是指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。

Eventually consistent 最终一致性

  强调系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。其本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。
  最终一致性可分为如下几种:

  1. 因果一致性(Causal consistency)。即进程A在更新完数据后通知进程B,那么之后进程B对该项数据的读取都是进程A更新后的最新值。
  2. 读更新(Read your writes)。进程A更新一项数据后,它自己总是能访问到自己更新过的最新值。
  3. 会话一致性(Session consistency)。将数据一致性框定在会话当中,在一个会话当中实现读更新的一致性。即执行更新后,客户端在同一个会话中始终能读到该项数据的最新值。
  4. 单调读一致性(Monotonic read consistency)。如果一个进程从系统中读取出一个数据项的某个值后,那么系统对于该进程后续的任何数据访问都不应该返回更旧的值。
  5. 单调写一致性(Monotoic write consistency)。一个系统需要保证来自同一个进程的写操作被顺序执行。

分布式一致性算法

  本节将简单介绍几种常见的分布式一致性算法,具体包括:Lamport时间戳[5]、向量时钟[6]、2PC[7][8]、3PC[9][10][11]、Paxos[12]、Raft[13]、Zab[14]、NWR[15]及ISR[16]。图1展示了这些算法的提出时间,不过并不完全准确。有的算法在某个具体工具中使用,则参考其所在工具的发布时间作为提出时间,如ISR是在Kafka 0.8.0版本提出[16],NWR是在Amazon DynamoDB中提出[15]。而有的算法在在被赋予正式的名称前,其核心问题就已在研究了,如Paxos[17]
分布式一致性算法简介_第1张图片

图1 常见一致性算法提出时间。

时间戳和向量时钟

  因为分布式系统中的数据存放在不同的节点上,那么如何比较不同节点上数据的版本问题就成为一个重要问题。时间戳和向量图的出现就是为了解决这个问题[18]。时间戳和向量时钟都忽略了各个节点上的具体时间,而将按照某个规则不断增长的数字作为系统时间。不同点在于,时间戳设定了一个全局时钟;而向量时钟则是让每一个节点单独存储其他节点的更新时间。

时间戳

  Leslie Lamport在1978年提出逻辑时钟的概念,并描述了一种逻辑时钟的表示方法,这个方法被称为Lamport时间戳,本文简称时间戳。
  分布式系统中按是否存在节点交互可分为三类事件,一类发生于节点内部,二是发送事件,三是接收事件[19]。时间戳原理如下:
分布式一致性算法简介_第2张图片

图2 时间戳示意图。
  1. 每个事件对应一个时间戳,初始值为0。
  2. 如果事件在节点内发生,时间戳加1。
  3. 如果事件属于发送事件,时间戳加1并在消息中带上该时间戳。
  4. 如果事件属于接收事件,时间戳 = Max(本地时间戳,消息中的时间戳) + 1。

  假设有事件a、b,C(a)、C(b)分别表示事件a、b对应的时间戳,如果C(a) < C(b),则有a发生在b之前(happened before),记作a -> b,例如上图中有C1 -> B1。通过该定义,事件集中不同时间戳的事件可以进行比较,获得事件的偏序关系(Partial order[20])。

  通过以上定义,我们可以对所有事件排序、获得事件的全序关系(Total order[21])。

向量时钟

  时间戳帮助我们得到事件顺序关系,但还有一种顺序关系不能用时间戳很好地表示出来,那就是同时发生的事件。
  向量时钟(Vector clock)是在时间戳基础上演进的另一种逻辑时钟方法,通过向量结构记录本节点的时间戳,同时也记录其他节点的时间戳。向量时钟的原理与时间戳类似,使用图例如下:
分布式一致性算法简介_第3张图片

图3 向量时钟示意图
  假设有事件a、b分别在节点P、Q上发生,向量时钟分别为Ta、Tb,如果`Tb[Q] > Ta[Q]`并且`Tb[P] >= Ta[P]`,则a发生于b之前,记作`a -> b`。到目前为止还和时间戳差别不大,那向量时钟怎么判别同时发生关系呢?如果`Tb[Q] > Ta[Q]`并且`Tb[P] < Ta[P]`,则认为a、b同时发生,记作`a <-> b`。例如上图中节点B上的第4个事件(A:2,B:4,C:1)与节点C上的第2个事件(B:3,C:2)没有因果关系、属于同时发生事件。

2PC

  Two-Phase Commit,两阶段提交[5]。简化的时序图如下:

用户 协调者 参与者 阶段1 提交事务请求 提交事务 提交事务请求 反馈事务执行结果 阶段2 执行事务提交 事务执行成功 事务执行失败 回滚事务 alt [全部成功] [is 有某个失败] 用户 协调者 参与者
图4 2PC时序图。

阶段1 提交事务请求,即投票阶段

  1. 事务询问。协调者向所有的参与者发送事务内容,询问是否可以执行事务提交操作,并开始等待各参与者的响应。
  2. 执行事务。各参与者节点执行事务操作,并将Undo和Redo信息计入事务日志中。
  3. 各参与者向协调者反馈事务询问的响应。如果参与者成功执行了事务操作,那么就反馈给协调者Yes响应,表示事务可以执行;如果参与者没有成功执行事务,那么就反馈给协调者No响应,表示事务不可以执行。

阶段2 执行事务提交,即执行阶段

  如果所有参与者的反馈都是Yes响应,那么就会执行事务提交;任何一个参与者反馈了No响应,或者在等待超时之后,协调者尚无法接收到所有参与者的反馈响应,那么就会中断事务。

  执行事务提交:

  1. 发送提交请求。协调者向所有参与者节点发出Commit请求。
  2. 事务提交。参与者接收到Commit请求后,会正式执行事务提交操作,并在完成提交之后释放在整个事务执行期间占用的事务资源。
  3. 反馈事务提交结果。参与者在完成事务提交之后,向协调者发送ACK信息。
  4. 完成事务。协调者接收到所有参与者反馈的ACK消息后,完成事务。

  中断事务:

  1. 发送回滚请求。协调者向所有参与者节点发出回滚请求。
  2. 事务回滚。参与者接收到回滚请求后,会利用其在阶段一中记录的Undo信息来执行事务回滚操作,并在完成回滚之后释放整个事务执行期间占用的资源。
  3. 反馈事务回滚结果。参与者在完成事务回滚之后,向协调者发送ACK信息。
  4. 中断事务。协调者接收到所有参与者反馈的ACK信息后,完成事务中断

优缺点

  优点:原理简单、实现方便。
  缺点:

  1. 同步阻塞。同步阻塞会极大地限制分布式系统的性能。在二阶段提交的执行过程中,所有参与该事务操作的逻辑都处于阻塞状态,各个参与者在等待其他参与者响应的过程中,将无法进行其他任何操作。
  2. 单点问题。一旦协调者出现问题,那么整个二阶段提交流程将无法运转,更为严重的是,如果是在阶段二中出现问题,那么其他参与者将会一直处于锁定事务资源的状态中,无法继续完成事务操作。
  3. 数据不一致。在阶段二,当协调者向所有参与者发送commit请求之后,发生了局部网络异常或协调者在尚未发完commit请求之前自身发生了崩溃,导致最终只有部分参与者接收到了commit请求,于是这部分参与者执行事务提交,而没收到commit请求的参与者则无法进行事务提交,于是整个分布式系统出现了数据不一致性现象。
  4. 缺乏容错性。如果参与者在与协调者通信期间出现故障,协调者只能靠超时机制来判断是否需要中断事务。这个策略比较保守,需要更为完善的容错机制,避免任意一个节点的失败都会导致整个事务的失败。

3PC

  Three-Phase Commit,三阶段提交[22]。为了避免在2PC中通知所有参与者提交事务时,其中一个参与者crash导致不一致,就出现了三阶段提交的方式。三阶段提交在两阶段提交的基础上增加了一个PreCommit的过程,当所有参与者收到PreCommit后,并不执行动作,直到收到commit或超过一定时间后才完成操作。简化的时序图如下:

用户 协调者 参与者 阶段1 CanCommit 提交事务 提交事务请求 反馈事务执行结果 阶段2 PreCommit 事务执行成功 事务执行失败 回滚事务 alt [全部成功] [is 有某个失败] 阶段2 DoCommit 事务执行成功 事务执行失败 回滚事务 alt [全部成功] [is 有某个失败] 用户 协调者 参与者
图5 3PC时序图。

阶段1 CanCommit

  1. 事务询问。协调者向各参与者发送CanCommit的请求,询问是否可以执行事务提交操作,并开始等待各参与者的响应。
  2. 参与者向协调者反馈询问的响应。参与者收到CanCommit请求后,正常情况下,如果自身认为可以顺利执行事务,那么会反馈Yes响应,并进入预备状态,否则反馈No。

阶段2 PreCommit

  如果协调者接收到各参与者反馈都是Yes,那么执行事务预提交;如果任何一个参与者向协调者反馈了No响应,或者在等待超时后,协调者无法接收到所有参与者的反馈,那么就会中断事务。

  执行事务预提交:

  1. 发送预提交请求。协调者向各参与者发送preCommit请求,并进入prepared阶段。
  2. 事务预提交。参与者接收到preCommit请求后,会执行事务操作,并将Undo和Redo信息记录到事务日记中。
  3. 各参与者向协调者反馈事务执行的响应。如果各参与者都成功执行了事务操作,那么反馈给协调者Ack响应,同时等待最终指令,提交commit或者终止abort。

  中断事务:

  1. 发送中断请求。协调者向所有参与者发送abort请求。
  2. 中断事务。无论是收到来自协调者的abort请求,还是等待超时,参与者都中断事务

阶段3 DoCommit

  同PreCommit阶段,如果协调者接收到各参与者反馈都是Yes,那么执行事务提交;如果任何一个参与者向协调者反馈了No响应,或者在等待超时后,协调者无法接收到所有参与者的反馈,那么就会中断事务。

  执行事务提交:

  1. 发送提交请求。假设协调者正常工作,接收到了所有参与者的ack响应,那么它将从预提交阶段进入提交状态,并向所有参与者发送DoCommit请求。
  2. 事务提交。参与者收到DoCommit请求后,正式提交事务,并在完成事务提交后释放占用的资源
  3. 反馈事务提交结果。参与者完成事务提交后,向协调者发送ack信息。
  4. 完成事务。协调者接收到所有参与者ack信息,完成事务。

  中断事务:

  1. 发送中断请求。协调者向所有参与者节点发送abort请求。
  2. 事务回滚。参与者接收到abort请求后,利用undo日志执行事务回滚,并在完成事务回滚后释放占用的资源
  3. 反馈事务回滚结果。参与者在完成事务回滚之后,向协调者发送ack信息。
  4. 中断事务。协调者接收到所有参与者反馈的ack信息后,中断事务。

优缺点

  优点:由于参与者执行事务前进行了CanCommit询问,在一定程度上保证了后续PreCommit的正常执行;而参与者有问题的情况也可以及时中断。所以会比2PC的容错性好一些。
  缺点:和2PC的情况一致。

Paxos

  Mike Burrows(Burrows–Wheeler transform[23]的共同作者)曾说:“there is only one consensus protocol, and that’s Paxos”。Paxos算法是基于消息传递且具有高度容错特性的一致性算法,是目前公认的解决分布式一致性问题最有效的算法之一[24]。但是Paxos也比较难懂,倒不是说它的流程太过复杂,而是其推导过程比较复杂。如果对这部分感兴趣可以参考资料《Paxos算法原理与推导》[24]

协议角色

  • Proposer:提案者。Proposer 可以有多个,Proposer 提出议案(value)。所谓 value,在工程中可以是任何操作,例如“修改某个变量的值为某个值”、“设置当前 primary 为某个节点”等等。 Paxos协议中统一将这些操作抽象为 value,同一轮 Paxos过程,最多只有一个 value 被批准。
  • Acceptor:接收者。Acceptor 有 N 个,Proposer 提出的 value 必须获得超过半数(N/2+1)的 Acceptor 批准后才能通过。Acceptor 之间完全对等独立。
  • Learner:学习者。不参与决策,学习被批准的 value(即获得 N/2 + 1 的 Acceptor 批准)。

  上述三类角色只是逻辑上的划分,实践中一个节点可以同时充当这三类角色。

协议流程

  Paxos协议流程是一轮一轮的进行,每轮都有一个编号。每轮Paxos协议可能会批准一个value,也可能无法批准一个value。如果某一轮Paxos协议批准了某个value,则以后各轮Paxos只能批准这个value(这是整个协议正确性的基础)。
定义B为每个Acceptor中记录的最近一轮编号,b表示某个Proposer提出的轮数。Paxos协议的简化流程如下:

用户 Proposer Acceptor Learner 提交请求 准备阶段 提交第b轮提案 是否同意提案:B<=b同意;否则同意 准备批准阶段 b=b+1,重新提交提案 alt [全部Yes] [is 任一No] 接收阶段 广播b轮提案通过 是否接受提案:B<=b不同意;否则同意 请求通过 第b轮提案通过 b=b+1,重新提交提案 alt [全部Yes] [is 任一No] 用户 Proposer Acceptor Learner
图6 Paxos时序图。

Raft

  为了让一致性协议变得简单可理解,Raft协议主要使用了两种策略。一是将复杂问题进行分解,在Raft协议中,一致性问题被分解为:leader election、log replication、safety三个简单问题[25]

  1. leader选举:当已有的leader故障时必须选出一个新的leader。
  2. 日志复制:leader接受来自客户端的命令,记录为日志,并复制给集群中的其他服务器,并强制其他节点的日志与leader保持一致。
  3. 安全safety措施:通过一些措施确保系统的安全性,如确保所有服务器按照相同顺序执行相同命令的措施。

协议角色

  • Leader:客户端的请求则全部由leader处理,即使有客户端请求了一个follower也会将请求重定向到leader。
  • Follower:复制Leader日志,并在未收到leader心跳时转变为candidate。
  • Candidate:集群刚启动时,所有节点都是follower,之后在time out信号的驱使下,follower会转变成candidate去拉取选票,获得大多数选票后就会成为leader,这时候如果其他候选人发现了新的leader已经诞生,就会自动转变为follower。

  这三种角色的转换关系如下[13]
分布式一致性算法简介_第4张图片

图7 Raft协议状态机。

协议流程

  在Raft协议中,将时间分成了一些任意长度的时间片,称为term,term使用连续递增的编号的进行识别,如下图所示:
分布式一致性算法简介_第5张图片

图8 Raft协议时间周期。
  server之间的交流是通过RPC进行的。只需要实现两种RPC就能构建一个基本的Raft集群: - RequestVote RPC:它由选举过程中的candidate发起,用于拉取选票。 - AppendEntries RPC:它由leader发起,用于复制日志或者发送心跳信号。

Leader选举

  Raft通过心跳机制发起leader选举。节点都是从follower状态开始的,如果收到了来自leader或candidate的RPC,那它就保持follower状态,避免争抢成为candidate。Leader会发送空的AppendEntries RPC作为心跳信号来确立自己的地位,如果follower一段时间(election timeout)没有收到心跳,它就会认为leader已经挂了,发起新的一轮选举。
选举发起后,一个follower会增加自己的当前term编号并转变为candidate。它会首先投自己一票,然后向其他所有节点并行发起RequestVote RPC,之后candidate状态将可能发生如下三种变化:

  • 赢得选举,成为leader: 如果它在一个term内收到了大多数的选票,将会在接下的剩余term时间内成为leader,然后就可以通过发送心跳确立自己的地位。(每一个server在一个term内只能投一张选票,并且按照先到先得的原则投出)
  • 其他server成为leader:在等待投票时,可能会收到其他server发出AppendEntries RPC心跳信号,说明其他leader已经产生了。这时通过比较自己的term编号和RPC过来的term编号,如果比对方大,说明leader的term过期了,就会拒绝该RPC,并继续保持候选人身份; 如果对方编号不比自己小,则承认对方的地位,转为follower。
  • 选票被瓜分,选举失败: 如果没有candidate获取大多数选票, 则没有leader产生, candidate们等待超时后发起另一轮选举。为了防止下一次选票还被瓜分,必须采取一些额外的措施,raft采用随机election timeout的机制防止选票被持续瓜分。通过将timeout随机设为一段区间上的某个值, 因此很大概率会有某个candidate率先超时然后赢得大部分选票。

日志复制

  一旦leader被选举成功,就可以对客户端提供服务了。客户端提交每一条命令都会被按顺序记录到leader的日志中,每一条命令都包含term编号和顺序索引,然后向其他节点并行发送AppendEntries RPC用以复制命令(如果命令丢失会不断重发)。当复制成功也就是大多数节点成功复制后,leader就会提交命令,即执行该命令并且将执行结果返回客户端。Raft保证已经提交的命令最终也会被其他节点成功执行。leader会保存有当前已经提交的最高日志编号。顺序性确保了相同日志索引处的命令是相同的,而且之前的命令也是相同的。当发送AppendEntries RPC时,会包含leader上一条刚处理过的命令,接收节点如果发现上一条命令不匹配,就会拒绝执行。
  在这个过程中可能会出现一种特殊故障:如果leader崩溃了,它所记录的日志没有完全被复制,会造成日志不一致的情况。follower相比于当前的leader可能会丢失几条日志,也可能会额外多出几条日志,这种情况可能会持续几个term。如下图所示:
分布式一致性算法简介_第6张图片

图9 Raft协议异常说明。

  在上图中,框内的数字是term编号,a、b丢失了一些命令,c、d多出来了一些命令,e、f既有丢失也有增多,这些情况都有可能发生。比如f可能发生在这样的情况下:f节点在term2时是leader,在此期间写入了几条命令,然后在提交之前崩溃了,在之后的term3中它很快重启并再次成为leader,又写入了几条日志,在提交之前又崩溃了,等他苏醒过来时新的leader来了,就形成了上图情形。在Raft中,leader通过强制follower复制自己的日志来解决上述日志不一致的情形,那么冲突的日志将会被重写。为了让日志一致,先找到最新的一致的那条日志(如f中索引为3的日志条目),然后把follower之后的日志全部删除,leader再把自己在那之后的日志一股脑推送给follower,这样就实现了一致。而寻找该条日志,可以通过AppendEntries RPC,该RPC中包含着下一次要执行的命令索引,如果能和follower的索引对上,那就执行;否则拒绝,然后leader将会逐次递减索引,直到找到相同的那条日志。
  然而这样也还是会有问题,比如某个follower在leader提交时宕机了,也就是少了几条命令,然后它又经过选举成了新的leader,这样它就会强制其他follower跟自己一样,使得其他节点上刚刚提交的命令被删除,导致客户端提交的一些命令被丢失了。Raft通过为选举过程添加一个限制条件,解决了上面提出的问题,该限制确保leader包含之前term已经提交过的所有命令。Raft通过投票过程确保只有拥有全部已提交日志的candidate能成为leader。由于candidate为了拉选票需要通过RequestVote RPC联系其他节点,而之前提交的命令至少会存在于其中某一个节点上,因此只要candidate的日志至少和其他大部分节点的一样新就可以了, follower如果收到了不如自己新的candidate的RPC,就会将其丢弃。

ZAB

  Zab协议的全称是Zookeeper Atomic Broadcast(Zookeeper原子广播)[26]
  Zab借鉴了Paxos算法,是为分布式协调服务Zookeeper专门设计的一种支持崩溃恢复的原子广播协议,是Zookeeper保证数据一致性的核心算法。本节将介绍Zab算法的几个关键阶段,关于更多细节与异常处理建议阅读《ZAB-一致性算法》[26]

Zab节点状态及相关术语说明

  Zab节点有三种状态:

  • Following:当前节点是跟随者,服从 Leader 节点的命令。
  • Leading:当前节点是 Leader,负责协调事务。
  • Election/Looking:节点处于选举状态,正在寻找 Leader。

  相关术语说明:

  • Epoch: 区分leader周期的递增数字。
  • Proposal: 事务提交。
  • History:当前节点接收到事务proposal的Log。
  • Zxid: 全局单递增的唯一ID,每个proposal会分配一个。
  • AcceptedEpoch:follower已经接受的leader更改epoch的newEpoch提议。
  • CurrentEpoch: 当前所处的leader周期。
  • LastZxid: history中最近接收到的proposal的zxid(最大zxid)。

Zab协议流程

  1. 选举阶段。节点在一开始都处于选举节点,只要有一个节点得到超过半数节点的票数,它就可以当选准leader,只有到达第三个阶段(也就是同步阶段),这个准leader才会成为真正的leader。
  2. 发现阶段。在这个阶段,followers和上一轮选举出的准leader进行通信,同步followers最近接收的事务proposal。这个阶段的主要目的是发现当前大多数节点接收的最新proposal,并且准leader生成新的epoch,让followers接收,更新它们的acceptedEpoch。
  3. 同步阶段。同步阶段主要是利用leader前一阶段获得的最新proposal历史,同步集群中所有的副本。只有当超过半数的节点都同步完成,准leader才会成为真正的leader。
  4. 广播阶段。到了这个阶段,Zookeeper集群才能正式对外提供事务服务,并且leader可以进行消息广播。同时,如果有新的节点加入,还需要对新节点进行同步。

NWR

  在Amazon的Dynamo云存储系统中,使用了NWR来控制一致性[27]。其中,N代表同一份数据的Replica的份数,W是更新一个数据对象时需要确保成功更新的份数;R代表读取一个数据需要读取的Replica的份数。 公式W+R>N,保证某个数据不被两个不同的事务同时读和写;公式W>N/2保证两个事务不能并发写某一个数据。 在分布式系统中,数据的单点是不允许存在的。即线上正常存在的Replica数量为1的情况是非常危险的,因为一旦这个Replica再次出错,就可能发生数据的永久性错误。假如我们把N设置成为2,那么只要有一个存储节点发生损坏,就会有单点的存在,所以N必须大于2。N越高,系统的维护成本和整体成本就越高。工业界通常把N设置为3。例如,对于MySQL主从结构,其NWR数值分别是N= 2, W = 1, R = 1,没有满足NWR策略。NWR策略是在平衡读、写、备份的效率中使用到的一个非常经典的策略,在Amazon Dynamo等分布式存储系统中都应用的很关键。
  配置NWR的时候要求W+R>N。 因为W+R>N,所以R>N-W,这个是什么意思呢?就是读取的份数一定要比总备份数减去确保写成功的份数的差值要大。也就是说,每次读取,都至少读取到一个最新的版本,从而不会读到一份旧数据。比如N=5W=3N-W=2,即保证5个备份中有3个是正确的,另外2个可能没有写成功。如果只读取2份,那么这两份有可能都不是写成功的。
  当我们需要高可写的环境的时候(例如amazon的购物车的添加请求应该是永远不被拒绝的)我们可以配置W=1,如果N=3,那么R=3。这个时候只要写任何节点成功就认为成功,但是读的时候必须从所有的节点都读出数据。如果我们要求读的高效率,我们可以配置W=N,R=1。这个时候任何一个节点读成功就认为成功,但是写的时候必须写所有三个节点成功才认为成功。需要注意的是,一个操作的耗时是几个并行操作中最慢一个的耗时。比如R=3的时候,实际上是向三个节点同时发了读请求,要三个节点都返回结果才能认为成功。假设某个节点的响应很慢,它就会严重拖累一个读操作的响应速度。
  不同的NWR取值代表了不同的倾向,如果设定N=3,W=3,R=1,那么强调的是强一致性,写数据的时候一定要把所有的副本刷新,杜绝中间状态。如果N=3,R=1,W=1,则代表的是可用性,这种情况下一致性就被牺牲掉了。

ISR

  ISR(In-Sync Replicas)是Kafka中同步副本时使用的一致性同步策略[28]。在Kafka的同步策略中有几个关键概念需要搞清楚:

  1. AR(Assigned Repllicas)一个partition的所有副本(就是replica,不区分leader或follower)。
  2. ISR(In-Sync Replicas)能够和 leader 保持同步的 follower + leader本身 组成的集合。
  3. OSR(Out-Sync Relipcas)不能和 leader 保持同步的 follower 集合。

  其中AR = ISR + OSR。ISR策略的核心是动态调整。处于ISR内部的follower都是可以和leader进行同步的,一旦出现故障或延迟,就会被踢出ISR;而OSR中的follower一旦赶上leader就会被加入ISR中。

Reference

[1] segmentfault 从ACID到CAP到BASE
[2] 知乎 事务的四大特性ACID
[3] 知乎 如何理解数据库事务中的一致性的概念?
[4] 知乎 谈谈分布式系统的CAP理论
[5] PDF Time, Clocks and the Ordering of Events in a Distributed System
[6] WIKIPEDIA Vector clock
[7] 博客园 Paxos 协议简单介绍
[7] PDF CONCURRENCY CONTROL AND RECOVERY IN DATABASE SYSTEMS
[8] WIKIPEDIA Two-phase commit protocol
[8] PDF Paxos Made Simple
[9] WIKIPEDIA Three-phase commit protocol
[10] PDF A QUORUM-BASED COMMIT PROTOCOL
[11] PDF INCREASING THE RESILIENCE OF DISTRIBUTED AND REPLICATED DATABASE SYSTEMS
[12] Paxos官网 Paxos
[13] PDF In Search of an Understandable Consensus Algorithm
[14] IEEE官网 Zab: High-performance broadcast for primary-backup systems
[15] WIKIPEDIA Amazon DynamoDB
[16] Kafka官网 DOWNLOAD
[17] WIKIPEDIA Paxos (computer science)
[18] CSDN 一致性算法之四: 时间戳和向量图
[19] 博客园 分布式系统理论基础 - 时间、时钟和事件顺序
[20] WIKIPEDIA Partially ordered set
[21] WIKIPEDIA Total order
[22] segmentfault 2PC到3PC到Paxos到Raft到ISR
[23] WIKIPEDIA Burrows–Wheeler transform
[24] 博客园 Paxos算法原理与推导
[25] 知乎 Raft协议原理详解
[26] 个人博客 ZAB-一致性算法
[27] 个人网站 Cap理论和nwr策略
[28] CSDN Kafka之ISR机制的理解

你可能感兴趣的:(分布式一致性算法)