概述
在正式探讨分布式环境中面临的各种技术问题和解决方案前,我 们先把目光从工业界转到学术界,学习几种具有代表性的分布式共识 算法,为后续在分布式环境中操作共享数据准备好理论基础。下面笔 者从一个最浅显的场景开始,引出本章的主题:
如果你有一份很重要的数据,要确保它长期存储在电脑上不会丢 失,你会怎么做?
这不是什么脑筋急转弯的古怪问题,答案就是去买几块硬盘,在 不同硬盘上多备份几个副本。假设一块硬盘每年损坏的概率是5%,把 文件复制到另一块备份盘上,两块硬盘同时损坏而丢失数据的概率就 只有0.25%,如果使用三块硬盘存储则丢失数据的概率是0.00125%,四 块是0.0000625%,换言之,四块硬盘就可以保证数据在一年内有超过 99.9999%的概率是安全可靠的。
在软件系统里,要保障系统的可靠性,采用的办法与上面用几个 备份硬盘来保障的方法并没有什么区别。单个节点的系统宕机导致数 据无法访问的原因可能有很多,譬如程序出错、硬件损坏、网络分 区、电源故障,等等,一年中出现系统宕机的概率也许还要高于5%, 这决定了软件系统也必须有多台机器,并且它们拥有一致的数据副 本,才有可能对外提供可靠的服务。
在软件系统里,要保障系统的可用性,面临的困难与硬盘备份面 临的困难又有着本质的区别。硬盘之间是孤立的,不需要互相通信, 备份数据是静态的,初始化后状态就不会发生改变,由人工进行的文 件复制操作,很容易就保障了数据在各个备份盘中的一致性。然而在 分布式系统中,我们必须考虑动态的数据如何在不可靠的网络通信条 件下,依然能在各个节点之间正确复制的问题。将我们要讨论的场景 做如下修改:
如果你有一份会随时变动的数据,要确保它正确地存储于网络中 的几台不同机器之上,你会怎么做?
相信最容易想到的答案一定是"数据同步":每当数据发生变 化,把变化情况在各个节点间的复制视作一种事务性的操作,只有系 统里每一台机器都反馈成功、完成磁盘写入后,数据的变化才宣告成 功。使用2PC/3PC就可以实现这种同步操 作。一种真实的数据同步应用场景是数据库的主从全同步复制(Fully Synchronous Replication),譬如MySQL集群,它在进行全同步复制 时,会等待所有Slave节点的Binlog都完成写入后,才会提交Master节 点的事务。(这个场景中Binlog本身就是要同步的状态数据,不应将 它看作指令日志的集合。)然而这里有一个明显的缺陷,尽管可以确 保Master节点和Slave节点中的数据是绝对一致的,但任何一个Slave 节点因为任何原因未响应均会阻塞整个事务,每增加一个Slave节点, 都会造成整个系统可用性风险增加一分。
以同步为代表的数据复制方法,被称为状态转移(State Transfer),是较符合人类思维的可靠性保障手段,但通常要以牺牲 可用性为代价。我们在建设分布式系统的时候,往往不能承受这样的 代价,一些关键系统,在必须保障数据正确可靠的前提下,也对可用 性有非常高的要求,譬如系统要保证数据达到99.999999%可靠,同时 系统自身也要达到99.999%可用的程度。这就引出了我们的第三个问 题:
如果你有一份会随时变动的数据,要确保它正确地存储于网络中 的几台不同机器之上,并且要尽可能保证数据是随时可用的,你会怎 么做?
可靠性与可用性的矛盾造成了增加机器数量反而带来可用性的降 低。为缓解这个矛盾,在分布式系统里主流的数据复制方法是以操作 转移(Operation Transfer)为基础的。我们想要改变数据的状态, 除了直接将目标状态赋予它之外,还有另一种常用的方法是通过某种 操作,令源状态转换为目标状态。能够使用确定的操作促使状态间产 生确定的转移结果的计算模型,在计算机科学中被称为状态机(State Machine)。
状态机有一个特性:任何初始状态一样的状态机,如果执行的命 令序列一样,则最终达到的状态也一样。如果将此特性应用在多参与 者的协商共识上,可以理解为系统中存在多个具有完全相同的状态机 (参与者),这些状态机能最终保持一致的关键就是起始状态完全一 致和执行命令序列完全一致。
根据状态机的特性,要让多台机器的最终状态一致,只要确保它 们的初始状态是一致的,并且接收到的操作指令序列也是一致的即 可,无论这个操作指令是新增、修改、删除抑或是其他任何可能的程 序行为,都可以理解为要将一连串的操作日志正确地广播给各个分布 式节点。在广播指令与指令执行期间,允许系统内部状态存在不一致 的情况,即并不要求所有节点的每一条指令都是同时开始、同步完成 的,只要求在此期间的内部状态不能被外部观察到,且当操作指令序 列执行完毕时,所有节点的最终状态是一致的,则这种模型就被称为 状态机复制(State Machine Replication)
考虑到分布式环境下网络分区现象是不可能消除的,甚至允许不 再追求系统内所有节点在任何情况下的数据状态都一致,而是采用 "少数服从多数"的原则,一旦系统中过半数的节点完成了状态的转 换,就认为数据的变化已经被正确地存储在了系统中,这样就可以容 忍少数(通常是不超过半数)的节点失联,减弱增加机器数量对系统 整体可用性的影响,这种思想在分布式中被称为"Quorum机制"。
根据上述讨论,我们需要设计出一种算法,能够让分布式系统内 部暂时容忍不同的状态,但最终保证大多数节点的状态达成一致;同 时,能够让分布式系统在外部看来始终表现出整体一致的结果。这个 让系统各节点不受局部的网络分区、机器崩溃、执行性能或者其他因 素影响,都能最终表现出整体一致的过程,就被称为各个节点的协商 共识(Consensus)。
Paxos
世界上只有一种共识协议,就是Paxos,其他所有共识算法都是 Paxos的退化版本。
------Mike Burrows,Google Chubby作者
Paxos是由Leslie Lamport(就是大名鼎鼎的LaTeX中的"La") 提出的一种基于消息传递的协商共识算法,是当今分布式系统最重要 的理论基础,几乎就是"共识"二字的代名词。这个极高的评价出自 于提出Raft算法的论文,更显分量十足。虽然笔者认为Mike Burrows所言有些夸张,但是如果没有Paxos,那后续的Raft、ZAB等算 法,ZooKeeper、etcd等分布式协调框架、Hadoop、Consul等在此基础 上的各类分布式应用都很可能会延后好几年面世。
为了解释清楚Paxos算法,Lamport虚构了一个名为"Paxos"的希 腊城邦,这个城邦按照民主制度制定法律,却没有一个中心化的专职 立法机构,而是靠着"兼职议会"(Part-Time Parliament)来完成 立法,无法保证所有城邦居民都能够及时了解新的法律提案,也无法 保证居民会及时为提案投票。Paxos算法的目标就是让城邦能够在每一 位居民都不承诺一定会及时参与的情况下,依然可以按照少数服从多 数的原则,最终达成一致意见。但是Paxos算法并不考虑拜占庭将军问 题,即假设信息可能丢失也可能延迟,但不会被错误传递。 Lamport在1990年首次发表了Paxos算法,选的论文题目就是"The Part-Time Parliament"。由于算法本身极为复杂,用希腊城邦作为 比喻反而使得描述更晦涩,论文的三个审稿人一致要求他把希腊城邦 的故事删掉。这令Lamport感觉颇为不爽,干脆就撤稿不发了,所以 Paxos刚刚被提出的时候并没有引起什么反响。八年之后(1998年), Lamport将此文章重新整理后投到ACM Transactions on Computer Systems。这次论文成功发表,Lamport的名气也确实吸引了一些人去 研究,但并没有多少人能弄懂他在说什么。时间又过去了三年(2001 年),Lamport认为前两次的论文没有引起反响,是因为同行们无法理 解他以"希腊城邦"来讲故事的幽默感,所以这一次他以"Paxos Made Simple"为题,在SIGACT News杂志上发表文章,放弃了"希腊 城邦"的比喻,尽可能用(他认为)简单直接、(他认为)可读性较 强的方式去介绍Paxos算法。情况虽然比前两次要好上一些,但以 Paxos本应获得的重视程度来说,这次依然只能算是应者寥寥。
这一段 听起来如同网络段子一般的经历被Lamport以自嘲的形式放到了他的个 人网站上。尽管我们作为后辈应该尊重Lamport老爷子,但当笔者 翻开"Paxos Made Simple"的论文,见到只有"The Paxos algorithm,when presented in plain English,is very simple."这 一句话的"摘要"时,心里实在是不得不怀疑Lamport这样写论文是不 是在恶搞审稿人和读者,在嘲讽"你们这些愚蠢的人类"。 虽然Lamport本人连发三篇文章都没能让大多数同行理解Paxos, 但2006年,在Google的Chubby、Megastore以及Spanner等分布式系统 都使用Paxos解决了分布式共识的问题,并将其整理成正式的论文发表 之后,得益于Google的行业影响力,辅以Chubby作者Mike Burrows那 略显夸张但足够吸引眼球的评价推波助澜,Paxos算法一夜间成为计算 机科学分布式这条分支中最炙手可热的概念,开始被学术界众人争相 研究。Lamport本人因其对分布式系统的杰出理论贡献获得了2013年的 图灵奖,随后才有了Paxos在区块链、分布式系统、云计算等多个领域 大放异彩的故事。
算法流程
下面,我们来正式学习Paxos算法(在本节中Paxos均特指最早的 Basic Paxos算法)。Paxos算法将分布式系统中的节点分为三类。