大约一年前,我在一致性模型上写了这篇文章的第一个版本,但我从来没有对它感到满意,因为它写得很匆忙,而且这个主题足够重要,需要得到更彻底的处理。ACM Queue要求我修改它以便在他们的杂志中使用,我利用这个机会改进了这篇文章。这是那个新版本。
最终的一致性——在全球范围内构建可靠的分布式系统需要在一致性和可用性之间进行权衡。
亚马逊云计算的基础是基础设施服务,如Amazon的S3(简单存储服务)、SimpleDB和EC2(弹性计算云),它们为构建互联网规模的计算平台和各种应用程序提供了资源。对这些基础设施服务的要求非常严格;他们需要在安全性、可伸缩性、可用性、性能和成本效益方面取得高分,并且他们需要在满足这些需求的同时,持续地为全球各地的数百万客户服务。
在这些服务的背后是在全球范围内运行的大规模分布式系统。这种规模带来了额外的挑战,因为当系统处理数万亿、数万亿的请求时,通常发生概率很低的事件现在保证会发生,需要在系统的设计和体系结构中预先考虑。考虑到这些系统的全球范围,我们广泛地使用复制技术来保证一致的性能和高可用性。尽管复制使我们更接近我们的目标,但它不能以完全透明的方式实现它们;在许多情况下,这些服务的客户将面临在服务内部使用复制技术的后果。
其表现方式之一是所提供的数据一致性类型,特别是当底层分布式系统为数据复制提供最终一致性模型时。在Amazon设计这些大规模系统时,我们使用一组与大规模数据复制相关的指导原则和抽象,并关注高可用性和数据一致性之间的权衡。在本文中,我将介绍一些相关的背景知识,这些背景知识为我们提供了交付需要在全球范围内运行的可靠分布式系统的方法。这篇文章的早期版本出现在2007年12月的All Things Distributed weblog上,并在读者的帮助下得到了极大的改进。
历史的角度
在理想的世界中,只有一个一致性模型:当进行更新时,所有观察者都将看到该更新。第一次出现这种难以实现的情况是在70年代末的数据库系统中。关于这个主题最好的“时期文章”是Bruce Lindsay等人写的“分布式数据库的注释”5。它列出了数据库复制的基本原则,并讨论了实现一致性的许多技术。这些技术中的许多都试图实现分布透明性—也就是说,对于系统的用户来说,看起来好像只有一个系统,而不是有许多协作系统。这一时期的许多系统采取的方法是,与其破坏这种透明度,不如让整个系统失灵
在90年代中期,随着大型互联网系统的兴起,这些做法被重新审视。那时,人们开始考虑可用性可能是这些系统最重要的属性,但他们也在为它应该与什么进行交换而挣扎。系统教授Eric Brewer的加州大学伯克利分校,当时Inktomi,带来了不同的交换在主题演讲PODC 2000.1(分布式计算的原则)会议上他提出上限定理,即三个属性的数据共享系统数据一致性、系统可用性和公差网络partition-only两个可以实现在任何给定的时间。Seth Gilbert和Nancy lynch在2002年的一篇论文中给出了更正式的确认
不能容忍网络分区的系统可以实现数据一致性和可用性,通常是通过使用事务协议实现的。要做到这一点,客户端和存储系统必须是同一个环境的一部分;在某些场景下,它们作为一个整体失败,因此,客户端无法观察分区。一个重要的观察结果是,在较大的分布式系统中,网络分区是给定的;因此,一致性和可用性不能同时实现。这意味着对于放弃什么有两种选择:放松一致性将允许系统在可分区条件下保持高可用性,而将一致性作为优先级意味着在某些条件下系统将不可用。
这两个选项都要求客户端开发人员知道系统提供了什么。如果系统强调一致性,开发人员就必须处理这样一个事实,即系统可能无法进行写入操作。如果由于系统不可用而导致写入失败,那么开发人员将不得不处理如何处理要写入的数据。如果系统强调可用性,它可能总是接受写操作,但在某些条件下,读操作不会反映最近完成的写操作的结果。然后开发人员必须决定客户端是否一直需要访问绝对最新的更新。有一系列应用程序可以处理稍微陈旧的数据,它们在此模型下得到了很好的服务。
原则上,ACID属性(原子性、一致性、隔离性、持久性)中定义的事务系统的一致性属性是一种不同类型的一致性保证。在ACID中,一致性指的是保证事务完成时数据库处于一致状态;例如,当从一个账户向另一个账户转账时,两个账户中的总金额不应改变。在基于acid的系统中,这种一致性通常是编写事务的开发人员的责任,但是数据库管理完整性约束可以帮助实现这种一致性。
一致性:客户端和服务器
有两种观察一致性的方法。一个是从开发人员/客户的角度:他们如何观察数据更新。第二种方法来自服务器端:更新如何流经系统,以及系统对更新可以提供哪些保证。
客户端一致性
客户端有以下组件:
- 一个存储系统。目前,我们将把它看作一个黑盒,但是我们应该假设它是一个大规模的、高度分布式的东西,并且构建它是为了保证持久性和可用性。
- 进程a。这是一个读写存储系统的进程。
- 进程B和进程c是独立于进程A的两个进程,它们对存储系统进行读写。它们是同一个进程中的进程还是线程无关紧要;重要的是,他们是独立的,需要交流来共享信息。
- 客户端一致性与观察者(在本例中是进程A、B或C)如何以及何时看到存储系统中数据对象的更新有关。在下面演示不同类型一致性的例子中,进程A对数据对象进行了更新:
- 强烈的一致性。更新完成后,任何后续访问(A、B或C)都将返回更新后的值。
- 弱一致性。系统不保证后续访问将返回更新后的值。在返回值之前,需要满足许多条件。从更新到保证任何观察者都能看到更新值这段时间被称为不一致窗口。
- 最终一致性。这是弱一致性的一种特殊形式;存储系统保证,如果没有对对象进行新的更新,最终所有访问都将返回最后更新的值。如果没有发生故障,可以根据通信延迟、系统负载和复制方案中涉及的副本数量等因素确定不一致窗口的最大大小。实现最终一致性的最普遍的系统是DNS(域名系统)。对名称的更新根据配置的模式进行分发,并与时间控制的缓存相结合;最终,所有客户端都将看到更新。
最终一致性模型有许多重要的变化需要考虑:
- 因果一致性。如果进程A通知进程B它已经更新了一个数据项,那么进程B的后续访问将返回更新后的值,并且保证一次写操作将取代之前的写操作。进程C的访问与进程A没有因果关系,遵循通常的最终一致性规则。
- “读己之所写”一致性。这是一个重要的模型,流程A在更新了数据项之后,总是访问更新后的值,永远不会看到旧的值。这是因果一致性模型的一个特例。
- 会话一致性。这是前一个模型的实际版本,其中进程在会话上下文中访问存储系统。只要会话存在,系统就保证“读己之所写”一致性。如果会话因为某种失败场景而终止,则需要创建一个新的会话,并且保证会话不会重叠。
- 单调读一致性。如果进程已经看到了该对象的特定值,那么任何后续访问都不会返回任何以前的值。
- 单调写一致性。在这种情况下,系统保证序列化同一个进程的写操作。不能保证这种级别一致性的系统是出了名的难以编程。
这些属性中有许多是可以组合的。例如,可以结合会话级别一致性获得单调的读取。从实际的角度来看,这两个属性(单调读取和read-your-write)在最终的一致性系统中是最理想的,但并不总是必需的。这两个属性使开发人员更容易构建应用程序,同时允许存储系统放松一致性并提供高可用性。
正如您可以从这些变化中看到的,相当多的不同场景是可能的。能否处理这些后果取决于特定的应用程序。
最终一致性并不是极端分布式系统的神秘属性。许多提供主备份可靠性的现代rdbms(关系数据库管理系统)同时以同步和异步模式实现它们的复制技术。在同步模式下,副本更新是事务的一部分。在异步模式下,更新以延迟的方式到达备份,通常通过日志传送。在后一种模式中,如果主备份在发送日志之前发生故障,从提升后的备份读取数据将产生旧的、不一致的值。另外,为了支持更好的可伸缩读性能,rdbms已经开始提供从备份中读取数据的能力,这是提供最终一致性保证的经典案例,在这种情况下,不一致性窗口取决于日志传送的周期。
服务器端一致性
在服务器端,我们需要更深入地研究更新如何流经系统,以理解是什么驱动了使用系统的开发人员可以体验不同的模式。在开始之前,让我们先建立一些定义:
N =存储数据副本的节点数
W =在更新完成之前需要确认已收到更新的副本的数量
R =通过读操作访问数据对象时所接触的副本数
如果W+R > N,那么写集和读集总是重叠的,可以保证强一致性。在实现同步复制的主备份RDBMS场景中,N=2、W=2和R=1。无论客户端从哪个副本读取数据,它都将得到一致的答案。在启用了从备份读取数据的异步复制中,N=2, W=1, R=1。在这种情况下,R+W=N,一致性无法保证。
这些配置是基本的quorum协议,它们的问题在于,当系统由于故障而无法写入W个节点时,写入操作必须失败,标志着系统不可用。当N=3 W=3且只有两个节点可用时,系统将不得不失败写操作。
在需要提供高性能和高可用性的分布式存储系统中,副本的数量通常大于两个。只关注容错的系统通常使用N=3 (W=2和R=2配置)。需要提供非常高读负载的系统经常复制超出容错要求的数据;N可以是数十个甚至数百个节点,R配置为1,这样一次读取就会返回一个结果。关注一致性的系统被设置为W=N进行更新,这可能会降低写入成功的可能性。这些系统关注容错性但不具有一致性,它们的一种常见配置是使用W=1运行以获得最小的更新持久性,然后依赖一种惰性(流行)技术来更新其他副本。
如何配置N、W和R取决于常见情况是什么,以及需要优化哪些性能路径。在R=1和N=W的情况下,我们优化读的情况,而在W=1和R=N的情况下,我们优化写的非常快。当然,在后一种情况下,在存在故障的情况下,持久性不能得到保证,如果W < (N+1)/2,那么当写集不重叠时,就有可能出现写冲突。
当W+R <= N时出现弱/最终一致性,这意味着读写集有可能不重叠。如果这是一个有意的配置,并且不是基于失败的情况,那么将R设为1以外的任何值都没有意义。这种情况通常发生在两种情况中:第一种是前面提到的为了读扩展而进行的大规模复制;第二个问题是数据访问更加复杂。在简单的键-值模型中,比较不同版本以确定写入系统的最新值很容易,但是在返回对象集的系统中,确定正确的最新值集就比较困难了。在大多数写集小于副本集的系统中,会有一种机制以一种惰性的方式将更新应用到副本集的其余节点。直到所有副本都被更新为止的时间段是前面讨论过的不一致窗口。如果W+R <= N,则系统容易从尚未接收到更新的节点读取数据。
“读你的写”、会话和单调一致性是否可以实现,通常取决于客户机对执行它们的分布式协议的服务器的“粘性”。如果每次都是相同的服务器,则相对容易保证“读己之所写”和单调的读取。这使得管理负载平衡和容错稍微困难一些,但这是一个简单的解决方案。使用会话,这是粘性的,使这一点显式,并提供了一个客户端可以推理的公开级别。
有时客户端实现read-your-write和单调读取。通过在写操作上添加版本,客户端将丢弃对版本在最后一个版本之前的值的读取。
当系统中的一些节点无法到达其他节点时,就会发生分区,但两个节点集都可以被客户端组访问。如果您使用传统的多数仲裁方法,那么具有W个副本集节点的分区可以继续进行更新,而另一个分区变得不可用。对于读集也是如此。给定这两个集重叠,根据定义,少数集变得不可用。分区并不经常发生,但确实会发生在数据中心之间以及数据中心内部。
在某些应用程序中,任何分区的不可用性都是不可接受的,重要的是能够到达该分区的客户机能够取得进展。在这种情况下,双方分配一组新的存储节点来接收数据,并在分区愈合时执行合并操作。例如,在Amazon中购物车使用这样的write-always系统;在分区的情况下,客户可以继续将商品放入购物车,即使原来的购物车存在于其他分区上。一旦分区恢复,cart应用程序将帮助存储系统合并购物车。
亚马逊的Dynamo
Amazon的Dynamo系统将所有这些属性置于应用程序体系结构的显式控制之下,这是一个键值存储系统,在组成Amazon电子商务平台的许多服务以及Amazon的Web服务内部使用该系统。Dynamo的设计目标之一是允许创建Dynamo存储系统实例(通常跨越多个数据中心)的应用程序服务所有者在一致性、持久性、可用性和性能之间以一定的成本进行权衡
总结
在大规模可靠分布式系统中,必须容忍数据不一致性,原因有二:提高高并发条件下的读写性能;以及处理大多数模型会导致部分系统不可用的分区情况,即使节点已经启动并运行。
不一致是否可以接受取决于客户机应用程序。在所有情况下,开发人员都需要意识到,存储系统提供了一致性保证,并且在开发应用程序时需要考虑到这一点。对于最终一致性模型有许多实际的改进,比如会话级一致性和单调读取,它们为开发人员提供了更好的工具。很多时候,应用程序都能够毫无问题地处理存储系统的最终一致性保证。一个特定的流行案例是一个网站,在其中我们可以有用户感知一致性的概念。在此场景中,不一致性窗口需要小于客户返回下一页加载的预期时间。这允许更新在预期下一次读取之前在系统中传播。
本文的目标是提高对工程系统复杂性的认识,这些系统需要在全球范围内运行,并且需要仔细调优,以确保它们能够交付应用程序所需的持久性、可用性和性能。系统设计者拥有的工具之一是一致性窗口的长度,在此期间,系统的客户可能暴露在大规模系统工程的现实中。
原文:https://www.allthingsdistributed.com/2008/12/eventually_consistent.html
本文:http://jiagoushi.pro/node/1387
讨论:请加入知识星球【首席架构师圈】或者小号【jiagoushi_pro】或者QQ群【11107777】
最新内容
- 17 hours ago
- 19 hours ago
- 20 hours ago
- 3 days 10 hours ago
- 3 days 18 hours ago
- 3 days 18 hours ago
- 3 days 19 hours ago
- 3 days 19 hours ago
- 1 week 1 day ago
- 1 week 1 day ago