前情回顾
在上一个博客中已经讨论了分布式系统的第一章节,主要涉及了分布式系统的基本概念,基本特性,设计目标,以及设计技巧。简言之,由于分布式系统的两个基本限制:信息的有限速度传播以及故障的独立性,导致分布式系统在设计上为了达成可扩展性,高性能,高可用,低延迟,高容错的道路上,要付出很多额外的代价。分布式系统设计问题可以抽象成通用的model来解决,从而能够在系统设计之初就可以有更好的理解力以及对系统设计各项参数的洞察力。对于系统的数据管理,有两种常见的技巧,数据划分和副本机制,遍布于各种各样的分布式系统中,这两个在提高系统的可用性以及性能方面发挥着其无与伦比的优势,但是不可避免的也带来了新的问题,例如多副本的数据一致性问题。欲了解更多详情,欢迎戳它:分布式系统基本概念-(1)
抽象
在本章,我们主要关注各层的抽象。如果你是一个编程人员,那么你对抽象概念一定不陌生。编程里面内部逻辑,通过包装之后抽象成API给外部调用者,思考一下网络的OSI七层模型。我们知道越上层的抽象,越容易理解,它剥离了底层的实现细节。分布式系统这个概念本身就是抽象,它是由多个节点构成,但是我们却想象它如一个单机系统一样工作,这是最high-level的抽象,理解上虽然简单,但却忽略了很多细节。因此,我们需要一个好的系统抽象能够在可理解性与细节表现之间进行权衡。
对于抽象一个系统,我们的目标是尽可能将系统中显得不是那么重要
(essential)的部分从抽象中剥离出去,只留下分布式系统最本质的东东,但是什么是它的本质,这本身又是另外一个问题。其次,在剥离系统的某一方面的时候,我们可能人为的引入一种错误风险,这可能又需要我们向更底层去看,来进一步了解真实世界,从而修正我们的抽象。不过不管怎么样,只要我们的抽象做到不忽略一些essential的部分,能够涵盖大部分的特性,那么这种抽象一般是applicable,而且还是general。
系统模型
分布式系统的特征
分布式系统的关键特征就是分布式
,也就是说
- 在多个节点独立并行执行
- 节点之间的通过网络通信,网络的连接状态是不确定的,可能导致message loss
- 没有共享内存或者叫shared lock(全局共享锁)
也就是说
- 每一个节点独立执行程序;
- 每个节点的knowledge是local的,它所知道的global的state可能是过期的,也可能是错误的;
- 节点故障事件的发生以及恢复是独立的;
- 消息可能延迟,可能丢失(无法辨别是由于网络故障还是节点故障导致的丢失);
- 没有全局的同步时钟(每个节点有自己的时钟,但是它并不与全局时钟完全一致)。
上面说了那么多条,总结起来就是知识是local的,网络是不可靠的,节点也是不可靠的。
什么叫System Model
a set of assumptions about the environment and facilities on which a distributed system is
implemented
当我们说系统模型,我们强调的是关于一个系统下的环境等条件的假设,这些假设包括:
- 节点
- 网络链路
- 整个系统属性,包括时钟以及全局序
一个可靠的系统意味着更弱的假设,需要更强的算法来应对系统的各种环境。当然,我们也可以创建一个具有强假设的系统,例如假设系统中节点不会故障,那么系统就不需要处理node failures,不过这样的假设是不现实的,从而在这样假设下的系统模型也就比较难应用到实际产品中。
让我们来更具体的这些模型假设吧
- 节点假设
- 执行程序的能力
- 存储数据到内存以及持久存储数据到硬盘的能力
- 时钟,虽然可能并不精准
也就是说,一个节点是在本地执行确定的算法,并能够临时或者持久存储本地状态。一个节点的local状态,计算之后的local状态以及要发送的消息只被local 状态以及它所接收到的消息唯一决定。说起来很绕,其实它是在说明这个节点自身不会干坏事
,它会按照给定的程序逻辑正确的确定的执行。
这里我想简单扩展的来说一下分布式系统的故障模型,具体请参考论文
- byzantitne failures (拜占庭故障):这是网络中处理起来最困难的模型,在这种情况下,节点可以
造假
,可以不按照程序逻辑来执行,对它的调用操作可以返回正确的,错误的,不一致的各种结果。要解决拜占庭错误,需要同步网络以及集群中的小于1/3的造假节点;
引用一段话:Lamport对拜占庭将军问题的研究表明,当n>3m时,即叛徒的个数m小于将军总数n的1/3时,通过口头同步通信(假设通信是可靠的),可以构造同时满足IC1和IC2的解决方案,即将军们可以达成一致的命令。但如果通信是可认证、防篡改伪造的(如采用PKI认证,消息签名等),则在任意多的叛徒(至少得有两个忠诚将军)的情况下都可以找到解决方案。
- crash-recovery failures: 它相比于拜占庭加入了一层限制,要求节点必须
按照程序逻辑正确的执行
,不会瞎胡闹。这其实就是对应于上面的“节点假设”,但是有一点是它不能保证消息返回的时间
,可能因为故障重启,网络中断以及异步网络导致延迟很高。对于crash会分为两种情况,一种是健忘(amnesia)和非健忘(omission),这其实对应于是否能够持久化本地状态,以恢复执行的能力。如果是非健忘的,那么crash重启之后,依然能够恢复以前的状态,继续执行; - omission failures:相比于crash-recovery模型,增加了再一层限制,必须是
非健忘的
。也就是说,必须能够在crash之后恢复上次的状态; - crash-stop failures:也叫作crash failure或者fail-stop failure,从字面上理解,如果发生故障就直接stop。它要求发生故障(节点故障或者网络故障)之后就停止一切响应请求。
- 链路假设
- 常见的分布式系统对链路有以下假设:任意两点可达,消息FIFO,发送的消息可能丢失;
- 也有一些算法假设链路是可靠,消息从不丢失,延迟是有限的;
- 网络分区故障是很特别的一种,节点OK,但是节点之间的链路断了,不同分区内的server都能够服务请求,可能导致不一致
- 时序假设
节点本身的distribution使得每个节点按照自己的方式来执行,这是无法避免的。在对待时序的问题上,主要有两种角度:
- Synchronous system model (同步系统模型)
网络传输时延有上界,精确的时钟控制,程序以round的方式进行,执行的步调一致。发送的消息能够确定在限制的上界内收到,如果不能收到,那么一定是丢了,不会延迟到第二个round。这种模型不是真实世界的模型。 - Asynchronous system model (异步系统模型)
程序执行时间不可预料,传输时延不可预料,时钟漂移不可预料。在异步网络中,判定异常原因是很困难的,例如消息的丢失可能是由于网络时延,节点计算的慢,网络故障,节点故障等任何一个原因导致。这种模型更符合现实世界。
Consensus问题
Consensus问题是分布式系统的基本问题,描述的是多个节点针对某个value达成一致,有几大要素:
- Agreement: 所有的正常节点都必须同意同一个value;
- Integrity:所有的节点最多只认可一个value,而且如果一个value被认可,那么这个value已经被propose过;
- Termination:所有的节点最终会达成决定,不会无限制下去;
- Validity:如果所有的节点都propose value V,那么所有的节点都认可V。
关于这个方面的研究,有两个最重要的结论:
- FLP不可能(需要更详细的阅读)
FLP在学术上的影响力更大一些。它假定在异步系统模型中,采用crash-recovery的故障模型,且网络是可靠的(不丢包),延迟无边界,最多一个节点故障,那么不存在一种确定性的算法来解决consensus问题。FLP给异步系统模型下的consensus问题的答案加了一个强约束:如果假定latency无上界,算法设计者必须在safety和liveness做出权衡。 - CAP不可能
- Consistency:所有节点在任何时刻看到都是相同的数据
- Availability:对于外部的请求,系统一定能够响应。
- Partition Tolerance:网络分区故障或者节点系统依然可以对外提供服务
只有3个中间的两个可以同时满足,如果只考虑两个的话,对应三种系统设计,如下面的大饼图听说是:
- CA(一致性和可用性):假定没有分区故障,可以使用一种严格full quorum协议,必须写全部成功才算成功,例如2PC两阶段提交协议;
- CP(一致性和分区容错):牺牲可用性,可以使用一种majority的quorum协议,例如Paxos;
- AP(可用性和分区容错):不要求强一致性,例如Dynamo使用了冲突解决的方案。
CA和CP都提供了强一致性的解决方案,不同点是CA系统不能容忍任何的节点错误,而CP系统能在2f+1个节点下最多容忍f个错误,也就是说需要多数派的节点,原因很简单:
- CA系统无法区分node failure和network failure(网络分区),如果出现了网络分区,那么写操作就可能发生在不同的分区从而导致数据分叉,发生不一致;
- CP系统解决网络分区的方案是让多数派主导写操作,让少数派的分区节点不可用,从而在确保数据一致的同时,还保证系统的部分可用(多数派所在的分区依然可用,但是少数派就不再可用了)。
总结来说CP系统将network partition融入到系统模型中,并且使用了多数派算法(例如Paxos,raft,viewstamped Replication)来区分网络分区。早期的系统规模较小(例如关系型数据系统),不考虑网络分区(CA),以较小代价获得高可用以及强一致。现代分布式系统规模更大,甚至于跨地区部署,网络分区成为常态,因此基本都把网络分区纳入考虑范围。如果要求系统强一致,那就必须牺牲一部分的可用性,因为如果让两个分区的服务器都来服务客户端请求,那么数据分叉就无法避免。
现实情况下,我们一方面可以通过强化假设(例如假设没有网络分区),另一方面通过弱化保证(例如弱一致性)来获得较好的系统性能。强一致性与高性能不可兼得,为了确保强一致性,系统的nodes需要启动consensus协议,通信并对每个写操作达成协议,带来很大的系统延迟。强一致与高可用不可兼得,当数据经过跨区域的散布,且中间的链路中断,我们依然希望两个分区都能对外提供服务,这意味着网络发生了分叉,当分叉在可控范围的时候,我们倾向于容忍分叉,维持高可用。
关于safety和liveness
safety,例如同一时刻,只有一个进程能够进入互斥临界区
something bad will never happen
liveness:例如gossip协议具有最终一致性
something good will must happen (do not know when)
一致性模型
强一致性模型
- 线性一致性
系统的所有操作的执行顺序遵循全局时钟的步调,完全像单机一样。 - 顺序一致性
所有的操作看起来像遵循一个时钟,以原子的方式执行,但是这个时钟不需要与全局时钟一致,只要所有的节点执行的步调一致即可。其实就是说,只要你能找到一种顺序(不一定等于操作发生的绝对时间),满足各个client和server的运行过程,那么这就是顺序一致的。
P1和P2代表两个不同的进程与server的交互结果,试问上述两种情形,能否满足顺序一致性?我们发现针对这两种情况都可以找到满足上述约束的执行顺序,即:P1 x.write(2) -> P1 x.write(3) -> P2 x.write(5) -> P1 x.read(5),这表明其执行结果满足顺序一致性。请参见文章 一致性模型。
如何区分呢?只有在确定知道每个操作的确切时间的情况下,才可以区分上述的两种模型;如果仅关注客户端与server的交互情况,上述两种模型表现完全相同。
弱一致性模型
对于弱一致性模型,并没有统一的定义,不强也就是弱。这就不得不提最终一致性。
最终一致性:if you stop changing values, then after some undefined amount of time all replicas will agree on the same value
也就是数据的不一致只会持续一个有限的时间,数据最终一定会一致,至于这个时间持续多久,并没有保证,这其实就是前面说的liveness的保证,something good will must be happen!
关于最终一致性,有两个关注点:
- 什么时候到达最终? 系统多久趋向一致,需要有个严格的上界或者指导意义的上界。
- 如何达成一致?对于一个分布式文件系统,请求任何文件都返回File Not Found,也是一种最终一致性:所有的副本都agree同一个值,虽然是无意义的值。因此,系统设计者需要考虑最终到达的情形,例如只有latest udpate会被记为最终结果。
进一步的阅读
这些论文往往才是精华,但精华往往是最难得到的。
- Brewer’s Conjecture and the Feasibility of Consistent, Available, Partition-Tolerant
Web Services - Gilbert & Lynch, 2002 - Impossibility of distributed consensus with one faulty process - Fischer, Lynch and
Patterson, 1985 - Perspectives on the CAP Theorem - Gilbert & Lynch, 2012
- CAP Twelve Years Later: How the “Rules” Have Changed - Brewer, 2012
- Uniform consensus is harder than consensus - Charron-Bost & Schiper, 2000
- Replicated Data Consistency Explained Through Baseball - Terry, 2011
- Life Beyond Distributed Transactions: an Apostate’s Opinion - Helland, 2007
- If you have too much data, then ‘good enough’ is good enough - Helland, 2011
- Building on Quicksand - Helland & Campbell, 2009
- [1] Distributed systems for fun and profit
- [2] 网络模型
- [3] 一致性模型