DDIA:什么是分布式系统中的一致性?
DDIA 读书分享会,会逐章进行分享,结合我在工业界分布式存储和数据库的一些经验,补充一些细节。每两周左右分享一次,欢迎加入,Schedule 在这里。我们有个对应的分布式&数据库讨论群,每次分享前会在群里通知。如想加入,可以加我的微信号:qtmuniao,简单自我介绍下,并注明:分布式系统群。
本章的线性一致性是在铺垫了多副本、网络问题、时钟问题后的一个综合探讨。首先探讨了线性一致的内涵:让系统表现得好像只有一个数据副本。然后讨论如何实现线性一致性,以及背后所做出的的取舍考量。其间花了一些笔墨探讨 CAP,可以看出作者很不喜欢 CAP 的模糊性。
如前所述,分布式系统中很多事情都有可能出错。解决出错最简单粗暴的方法是让整个系统宕机,并给出出错原因。但在实际生产中,这种方式多不可接受,此时我们就需要找到容错(tolerating faults)的方法。即,即使系统构件出现了一些问题,我们能保证系统仍然正常运行。
本章我们将会讨论一些用于构建具有容错性分布式系统的算法和协议(alogrithm and protocol)。在设计算法和协议时,我们假设第八章提到的分布式系统中的问题都会存在:
- 数据包可能会丢失、乱序、重复和不确定延迟
- 多机时钟最好的情况也就是近似一致
- 机器节点可能会不确定停顿、宕机重启
构建一个容错系统最好的方法是:找到一些基本抽象,可以对上提供某些承诺,应用层可以依赖这些承诺来构建系统,而不必关心底层细节。在第七章中,通过使用事务,应用层可以假设不会发生宕机(原子性,意思是不会因为宕机出现让事务停留在半成功的状态),没有其他应用并发访问数据库数据(隔离性),且存储系统非常可靠(持久性)。事务模型会隐藏节点宕机、竞态条件(race conditions)、硬盘故障等底层细节,即使这些问题出现了,应用层也不必关心。
本章将继续讨论一些可以减轻应用层负担的分布式系统中的基本抽象。比如,分布式系统中最重要的一个抽象——共识(consensus),即,_让所有节点在某件事情上达成一致_。在本章稍后的讨论可以看出,让系统中的所有节点在有网络故障和节点宕机的情况下达成共识,是一件非常棘手的事情。
为什么共识协议如此重要呢?他和真实系统的连接点在于哪里?答曰,操作日志。而大部分数据系统都可以抽象为一系列数据操作的依次施加,即状态机模型。而共识协议可以让多机对某个确定的操作序列达成共识,进而对系统的任意状态达成共识。
一旦我们实现了共识协议,应用层可以依赖其做很多事情。例如,你有一个使用单主模型的数据库,如果主副本所在节点宕机,我们便可以使用共识协议选出新的主。在第五章处理节点下线(Handling Node Outages)一节中我们提到过,只有唯一的主,并且所有副本都认可该主,是一个需要确保的非常重要的特性。如果有超过一个节点都认为自己是主,我们称之为脑裂(split brain)。脑裂很容易导致数据丢失,而正确实现的共识协议能够避免该问题。
在本章稍后的地方,“分布式事务和共识协议”一小节里,我们将会详细讨论用以解决共识相关问题的算法。但在此之前,我们需要探索下分布式系统中我们可以提供的保证和抽象有哪些。
我们需要理解容错的边界,哪些事情可以做、哪些事情做不了:在某些情况下,系统可以容忍某些故障;但在另外情况下,系统却容忍不了。我们将通过理论证明和具体实现来深入探讨,可能与不可能的边界限制。我们会对诸多基本限制有个概览式串讲。
分布式系统领域针对这些主题的研究已经持续了数十载,因此积累了很多材料,但我们只能进行简要介绍其皮毛。由于篇幅所限,我们不会详细探究其严谨的模型描述和详细证明,相反,我们只会给一些其背后的直觉(informal intuitions)。如果你感兴趣,章节末尾的参考文献应该可以提供一些足够深入的细节。
一致性保证
在第五章中日志滞后问题(Problems with Replication Lag)小节,我们分析了一些多副本数据所遇到的时序问题。在相同时刻,如果对比多副本数据库中一份数据的两个副本,我们可能会看到不一致的数据。这是因为,写请求到达不同的数据节点,总会存在一个时间差。无论我们使用什么数据副本模型(单主、多主和无主),这种数据的不一致性都有可能会发生。
大部分多副本数据库(replicated databases)提供最终一致性(eventually consistency)的保证,这意味着,只要你对数据库停写,并等待足够长的时间,则所有对相同数据的读取请求最终会返回相同的结果。从另一个角度来说,所有的不一致都是暂时的,最终都会被解决(当然,这得是在网络故障能最终修复的假设之下)。描述相同意思的一个更好的名字可能:收敛性(convergence),即最终,所有副本都会收敛到相同的值。
但这是一个相当不靠谱的保证——没有提供任何关于何时收敛的信息。而在收敛之前,对于相同数据的读取,可能会返回任意值甚至不返回。举个例子,你向多副本数据库中写入了一条数据,并立即读取他。你能读到什么,最终一致性对此不会提供任何保证,因为读取请求可能会被路由到任何其他副本。
最终一致性对于应用层开发者很不友好,因为它表现出的行为和单线程程序中的变量完全不一致。在单线程模型里,如果对某个变量赋值后立即读取,我们默认一定会读到刚才的赋值,而不是读到旧值或者读取失败。数据库在对外表现上很像一组可读写的变量集,但具有复杂得多的语义。
在使用只提供弱保证的数据库时,我们需要时刻记得其限制,而不能偶尔自己增加额外假设,否则,会产生非常致命且难以察觉的 BUG。因为大部分时间里,应用层表现得毫无波澜,只有在系统中出现故障(网络拥塞、节点宕机)或在高负载场景下,这些边缘情况才会被触发。
本章我们会一起探究一些更强的一致性模型,但选择这些模型是有代价的。相对弱一致性模型系统来说,这么做要么会牺牲性能,要么会牺牲可用性。但提供强保证会让应用层能更加容易、正确的使用。但当然,我们最终还是得根据具体场景,来选择使用何等强度一致性模型。
在实践中,我们常会使用分层策略,让某些底层解决可用性、性能和容量的问题,让上层解决一致性的问题。比如云上各种基于 aws s3 的关系型数据库。另外,也有些系统会同时提供多种一致性模型供用户选择,在一致性和性能间进行取舍。
分布式系统中的一致性模型的强弱和第七章讲的事物的隔离级别层次有一些共通之处,比如在性能和隔离性/一致性间做取舍。但他们是相对独立的抽象:
- 事务隔离级别是为了解决并发所引起的竞态条件
- 分布式一致性是处理由于多副本间延迟和故障所引入的数据同步问题
本章涉及到很多主题,乍看起来很宽泛,但其内里是互相勾连的:
- 首先,我们从常用的最强的一致性模型:线性一致性(linearizability)开始,探究其优缺点。
- 接着,我们会考察分布式系统中时间的顺序问题,尤其是关于因果关系(causality)和全序问题(total ordering)。
- 最后,在第三部分,我们会探索如何原子性地提交一个分布式事务,最终导出共识问题的解决方法。
DDIA 学习会
这本书由于涵盖知识点实在太多,没有一定基础读的时候会有很多问题,如果没有人交流和解惑,往往坚持一半就容易弃掉。因此我依托小报童平台建立了一个针对本书的学习和答疑的学习会。主要目的,是结合我的一些工业经验,给大家提供一些细节的答疑,但能力所限,也难免有疏漏和谬误之处,如果你觉得我的回答有问题,欢迎 challenge,交流才能进步!次要目的,就是为所有喜欢这本书的同学创造一个交流讨论环境。

入会方式:订阅专栏:https://xiaobot.net/p/large-scale-sys (限时六折)。 如果有任何疑问和建议,订购前可以加我微信:qtmuniao。
加我微信(vx: qtmuniao)拉你入学习会的群,进行答疑、讨论和信息发布。大致形式:每两周过一章,发一篇以章节标题+时间段的空白文章。
- 留言答疑:大家在上述文章下面打卡和提问。
- 文字答疑:微信群,每天我会抽时间集中回复。
- 视频答疑:Zoom,每周一次,每次一小时。
- 答疑汇总:将大家的问题和我的回答整理处理,以文章形式发在小报童专栏中;答疑录音会发到小宇宙上。
最后欢迎关注我的公众号:木鸟杂记,专注分布式系统、数据库和存储等大规模数据系统,关注后可回复“资料”领取一份我总结的分布式系统和数据库的入门资料大全。