背景

如果你有一份会随时变动的数据,要确保它正确地存储于网络中的几台不同机器之上,你会怎么做?

相信最容易想到的答案一定是“数据同步”:每当数据有变化,把变化情况在各个节点间的复制视作一种事务性的操作,只有系统里每一台机器都反馈成功地完成硬盘写入后,数据的变化才宣告成功,笔者曾经在“全局事务”中介绍过,使用 2PC/3PC 就可以实现这种同步操作。同步的其中一种真实应用场景是数据库的主从全同步复制(Fully Synchronous Replication),譬如 MySQL Cluster,进行全同步复制时,会等待所有 Slave 节点的 Binlog 都完成写入后,Master 节点的事务才进行提交(这个场景中 Binlog 本身就是要同步的状态数据,不应将它看作是指令日志的集合)。然而这里有一个显而易见的缺陷,尽管可以确保 Master 节点和 Slave 节点中的数据是绝对一致的,但任何一个 Slave 节点因为任何原因未响应均会阻塞整个事务,每增加一个 Slave 节点,都导致造成整个系统可用性风险增加一分。

以同步为代表的数据复制方法,被称为状态转移(State Transfer),这类方法是较符合人类思维的可靠性保障手段,但通常要以牺牲可用性为代价。我们在建设分布式系统的时候,往往不能承受这样的代价,一些关键系统,必须保障数据正确可靠的前提下,对可用性的要求也非常苛刻,譬如系统要保证数据要达到 99.999999%可靠,同时系统也要达到 99.999%可用的程度,这就引出了我们的第三个问题:

如果你有一份会随时变动的数据,要确保它正确地存储于网络中的几台不同机器之上,并且要尽可能保证数据是随时可用的,你会怎么做?

可靠性与可用性的矛盾造成了增加机器数量反而带来可用性的降低,为缓解这个矛盾,在分布式系统里主流的数据复制方法是以操作转移(Operation Transfer)为基础的。我们想要改变数据的状态,除了直接将目标状态赋予它之外,还有另一种常用的方法是通过某种操作,令源状态转换为目标状态。能够使用确定的操作,促使状态间产生确定的转移结果的计算模型,在计算机科学中被称为状态机(State Machine)。

额外知识:状态机复制
状态机有一个特性:任何初始状态一样的状态机,如果执行的命令序列一样,则最终达到的状态也一样。如果将此特性应用在多参与者进行协商共识上,可以理解为系统中存在多个具有完全相同的状态机(参与者),这些状态机能最终保持一致的关键就是起始状态完全一致和执行命令序列完全一致。

根据状态机的特性,要让多台机器的最终状态一致,只要确保它们的初始状态是一致的,并且接收到的操作指令序列也是一致的即可,无论这个操作指令是新增、修改、删除抑或是其他任何可能的程序行为,都可以理解为要将一连串的操作日志正确地广播给各个分布式节点。广播指令与指令执行期间,允许系统内部状态存在不一致的情况,即并不要求所有节点的每一条指令都是同时开始、同步完成的,只要求在此期间的内部状态不能被外部观察到,且当操作指令序列执行完毕时,所有节点的最终的状态是一致的,这种模型就被称为状态机复制(State Machine Replication)。

考虑到分布式环境下网络分区现象是不可能消除的,甚至允许不再追求系统内所有节点在任何情况下的数据状态都一致,而是采用“少数服从多数”的原则,一旦系统中过半数的节点中完成了状态的转换,就认为数据的变化已经被正确地存储在系统当中,这样就可以容忍少数(通常是不超过半数)的节点失联,使得增加机器数量对系统整体的可用性变成是有益的,这种思想在分布式中被称为“Quorum 机制”。

根据上述讨论,我们需要设计出一种算法,能够让分布式系统内部暂时容忍存在不同的状态,但最终能够保证大多数节点的状态达成一致;同时,能够让分布式系统在外部看来始终表现出整体一致的结果。这个让系统各节点不受局部的网络分区、机器崩溃、执行性能或者其他因素影响,都能最终表现出整体一致的过程,就被称为各个节点的协商共识(Consensus)。

最后,笔者还要提醒你共识(Consensus)与一致性(Consistency) 的区别:一致性是指数据不同副本之间的差异,而共识是指达成一致性的方法与过程。由于翻译的关系,很多中文资料把 Consensus 同样翻译为一致性,导致网络上大量的“二手中文资料”将这两个概念混淆起来,如果你在网上看到“分布式一致性算法”,应明白其指的其实是“Distributed Consensus Algorithm”。

共识算法

1、Paxos 算法

Lamport 提出的一种基于消息传递的分布式一致性算法,使其获得2013年图灵奖。自 Paxos 问世以来就持续垄断了分布式一致性算法,Paxos 这个名词几乎等同于分布式一致性, 很多分布式一致性算法都由 Paxos 演变而来

为了解释清楚 Paxos 算法,Lamport 虚构了一个名为“Paxos”的希腊城邦,这个城邦按照民主制度制定法律,却又不存在一个中心化的专职立法机构,立法靠着“兼职议会”(Part-Time Parliament)来完成,无法保证所有城邦居民都能够及时地了解新的法律提案、也无法保证居民会及时为提案投票。Paxos 算法的目标就是让城邦能够在每一位居民都不承诺一定会及时参与的情况下,依然可以按照少数服从多数的原则,最终达成一致意见。但是 Paxos 算法并不考虑拜占庭将军问题问题,即假设信息可能丢失也可能延迟,但不会被错误传递。

其中节点分为 3 类:

  • 提案节点:称为 Proposer,提出对某个值进行设置操作的节点,设置值这个行为就被称之为提案(Proposal),值一旦设置成功,就是不会丢失也不可变的。请注意,Paxos 是典型的基于操作转移模型而非状态转移模型来设计的算法,这里的“设置值”不要类比成程序中变量赋值操作,应该类比成日志记录操作,在后面介绍的 Raft 算法中就直接把“提案”叫作“日志”了。
  • 决策节点:称为 Acceptor,是应答提案的节点,决定该提案是否可被投票、是否可被接受。提案一旦得到过半数决策节点的接受,即称该提案被批准(Accept),提案被批准即意味着该值不能再被更改,也不会丢失,且最终所有节点都会接受该它。
  • 记录节点:被称为 Learner,不参与提案,也不参与决策,只是单纯地从提案、决策节点中学习已经达成共识的提案,譬如少数派节点从网络分区中恢复时,将会进入这种状态。

1.1 Multi Paxos

Multi Paxos 对 Basic Paxos 的核心改进是增加了“选主”的过程,提案节点会通过定时轮询(心跳),确定当前网络中的所有节点里是否存在有一个主提案节点,一旦没有发现主节点存在,节点就会在心跳超时后使用 Basic Paxos 中定义的准备、批准的两轮网络交互过程,向所有其他节点广播自己希望竞选主节点的请求,希望整个分布式系统对“由我作为主节点”这件事情协商达成一致共识,如果得到了决策节点中多数派的批准,便宣告竞选成功。当
选主完成之后,除非主节点失联之后发起重新竞选,否则从此往后,就只有主节点本身才能够提出提案。
此时,无论哪个提案节点接收到客户端的操作请求,都会将请求转发给主节点来完成提案,而主节点提案的时候,也就无需再次经过准备过程,因为可以视作是经过选举时的那一次准备之后,后续的提案都是对相同提案 ID 的一连串的批准过程。
也可以通俗理解为选主过后,就不会再有其他节点与它竞争,相当于是处于无并发的环境当中进行的有序操作,所以此时系统中要对某个值达成一致,只需要进行一次批准的交互即可

2、Raft 算法

由斯坦福大学的 Diego Ongaro 和 John Ousterhout 在 2014 年提出 In Search of an Understandable Consensus Algorithm
因为 Paxos 算法较为难以理解,所以简化后形成的算法。属于 Multi-Paxos
分布式共识算法-Raft

3、ZAB 算法

ZAB 也是对 Multi Paxos 算法的改进,大部分和 raft 相同,也属于 Multi-Paxos

和raft算法的主要区别:

  • 对于 Leader 的任期,raft 叫做 term,而 ZAB 叫做 epoch
  • 在状态复制的过程中,raft 的心跳从 Leader 向 Follower 发送,而 ZAB 则相反。

4、Gossip 协议

笔者按照习惯也把 Gossip 也称作是“共识协议”,但首先必须强调它所解决的问题并不是直接与 Paxos、Raft 这些共识算法等价的,只是基于 Gossip 之上可以通过某些方法去实现与 Paxos、Raft 相类似的目标而已。一个最典型的例子是比特币网络中使用到了 Gossip 协议,用它来在各个分布式节点中互相同步区块头和区块体的信息,这是整个网络能够正常交换信息的基础,但并不能称作共识;比特币使用工作量证明(Proof of Work,PoW)来对“这个区块由谁来记账”这一件事情在全网达成共识,这个目标才可以认为与 Paxos、Raft 的目标是一致的。

下面,我们来了解 Gossip 的具体工作过程。相比 Paxos、Raft 等算法,Gossip 的过程十分简单,它可以看作是以下两个步骤的简单循环:

  • 如果有某一项信息需要在整个网络中所有节点中传播,那从信息源开始,选择一个固定的传播周期(譬如 1 秒),随机选择它相连接的 k 个节点(称为 Fan-Out)来传播消息。

  • 每一个节点收到消息后,如果这个消息是它之前没有收到过的,将在下一个周期内,选择除了发送消息给它的那个节点外的其他相邻 k 个节点发送相同的消息,直到最终网络中所有节点都收到了消息,尽管这个过程需要一定时间,但是理论上最终网络的所有节点都会拥有相同的消息。

|525

上图是 Gossip 传播过程的示意图,根据示意图和 Gossip 的过程描述,我们很容易发现 Gossip 对网络节点的连通性和稳定性几乎没有任何要求,它一开始就将网络某些节点只能与一部分节点部分连通(Partially Connected Network)而不是以全连通网络(Fully Connected Network)作为前提;能够容忍网络上节点的随意地增加或者减少,随意地宕机或者重启,新增加或者重启的节点的状态最终会与其他节点同步达成一致。Gossip 把网络上所有节点都视为平等而普通的一员,没有任何中心化节点或者主节点的概念,这些特点使得 Gossip 具有极强的鲁棒性,而且非常适合在公众互联网中应用。

同时我们也很容易找到 Gossip 的缺点,消息最终是通过多个轮次的散播而到达全网的,因此它必然会存在全网各节点状态不一致的情况,而且由于是随机选取发送消息的节点,所以尽管可以在整体上测算出统计学意义上的传播速率,但对于个体消息来说,无法准确地预计到需要多长时间才能达成全网一致。另外一个缺点是消息的冗余,同样是由于随机选取发送消息的节点,也就不可避免的存在消息重复发送给同一节点的情况,增加了网络的传输的压力,也给消息节点带来额外的处理负载。

达到一致性耗费的时间与网络传播中消息冗余量这两个缺点存在一定对立,如果要改善其中一个,就会恶化另外一个,由此,Gossip 设计了两种可能的消息传播模式:反熵(Anti-Entropy)和传谣(Rumor-Mongering),这两个名字都挺文艺的。熵(Entropy)是生活中少见但科学中很常用的概念,它代表着事物的混乱程度。反熵的意思就是反混乱,以提升网络各个节点之间的相似度为目标,所以在反熵模式下,会同步节点的全部数据,以消除各节点之间的差异,目标是整个网络各节点完全的一致。但是,在节点本身就会发生变动的前提下,这个目标将使得整个网络中消息的数量非常庞大,给网络带来巨大的传输开销。而传谣模式是以传播消息为目标,仅仅发送新到达节点的数据,即只对外发送变更信息,这样消息数据量将显著缩减,网络开销也相对较小。

5、一致性

“强一致性”的分布式共识协议: “尽管系统内部节点可以存在不一致的状态,但从系统外部看来,不一致的情况并不会被观察到,所以整体上看系统是强一致性的”。

  • Paxos
  • Raft
  • ZAB
    “最终一致性”的分布式共识协议,这表明系统中不一致的状态有可能会在一定时间内被外部直接观察到。
  • DNS: 在各节点缓存的 TTL 到期之前,都有可能与真实的域名翻译结果存在不一致。
  • Gossip

分布式共识算法 | 凤凰架构