[译] 最终一致性 - 修订版(Eventually Consistent - Revisited)

译者注:这是AWS CTO Werner Vogels写的一致性模型的经典文章在其博客All Things Distributed上亦有发布。

大约一年前,我发表了关于一致性模型文章的第一个版本,但是我对它一直不是很满意,因为它写的很匆忙,而且这个太重要值得更充分的研究。 ACM Queue让我修订一下发表在他们的杂志上,我利用这个机会改进了这篇文章。这篇是那篇的修订版。

最终一致性 - 在全球范围构建可靠的分布式系统需要在一致性和

支撑Amazon云计算的基础服务,例如Amazon的S3(Simple Storage Service),SimpleDB和EC2(Elastic Compute Cloud)提供构建互联网规模计算平台和各种应用所需的资源。放在这些基础服务的需求非常严格;它们需要在安全性、 可伸缩性、 可用性、 性能和成本效率方面都非常优秀,同时为全球数以百万计的客户持续提供服务。

这些服务底层是全球范围运行的大规模分布式系统。这样的规模带来了额外的挑战,因为当一个系统处理海量请求时,那些低概率事件就会变成必然事件,并且需要在系统设计和架构中预先列出。考虑到这些全球范围的系统,我们普遍使用副本(Replication)技术来保证一致的性能和高可用性。 虽然副本技术让我们更接近目标,但是它不能以完全透明的方式实现它们;在一些条件下,这些服务的用户将会面临使用副本技术带来的后果。

这些方面的表现方式之一就是提供的数据一致性类型,特别当底层的分布式系统为数据副本提供一种最终一致性模型。当在Amazon设计这些大规模系统时,我们用一组与大规模数据副本(data replication)相关的指导原则和抽象,并集中在高可用性与数据一致性的权衡上。 这篇文章我介绍了一些相关背景展示了我们交付需要在全球范围运行的可靠的分布式系统的方法。这篇文章的早期版本于2017年12月发表在网络博客All Things Distributed,并且在读者的帮助下改进了很多。

历史观点

在理想世界中,只会有一种一致性模型:当更新完成,所有的观察者都会看到。 70年代末期,在数据系统中第一次发现这个模型难以实现。关于这个主题最好的历史文献是Bruce Lindsay等人的*分布式数据库注释 5*。 它阐述了数据库副本的基本原则,并讨论了一些找实现一致性的技术。 这些技术中许多试图实现分布式透明,就是对用户来说它似乎只在使用一个系统而不是多个协作系统。这段时期许多系统采用这种方法,即使让整个系统失效也不破坏透明性 2

在90年代中期,随着大型互联网的兴起,这些方法被重新审视。 当时人们开始意识到对于这些系统而言可用性是最重要的属性,但是他们也在纠结牺牲什么来保证可用性。 Eric Brewer,加州伯克利的系统学教授,Inktomi的头,在2001年的PODC(Principles of Distributed Computing)会议的主题演讲上提出了一并提出了不同的取舍方案 1。 他演示CAP理论,指出共享数据系统的三大属性即数据一致性,系统可用性和网络分区容忍性,在任何时刻只能同时保证两个。 Seth Gilbert和Nancy Lynch在他们2002年的一篇论文 4中给出了正式确认。

不能容忍网络分区的系统可以同时实现数据一致性和可用性,这个通常是通过事务协议来实现。 为了实现这样的系统,客户端和存储系统必须在相同环境中; 他们在某些场景下会整体失效,只有这样客户端才无法感知分区。 一个重要发现是在大型分布式系统中,网络分区无法避免,因此,一致性和可用性无法同时实现。 这意味着有两个放弃的选项: 放宽一致性保证系统的可用性,或者保证一致性接受某些场景下系统不可用。

两个选项都需要客户端开发者清楚系统可以提供什么。 如果强调一致性,开发者需要面对系统可能不可用的现实,例如,一次写操作。 如果这次写操作因为系统不可用,客户端开发者需要处理数据无法写入。 如果系统强调可用性,它可能会一直接受写操作,但是一些条件下读操作无法返回最新写入的数据。 开发者需要决定客户端是否始终访问最新的更新。 有一批应用可以处理稍微陈旧的数据,它们在这个模型下可以很好的提供服务。

原则上事务系统的ACID(原子性,一致性,隔离线,持久性)中的一致性属性是一种不同的一致性保证。 在ACID中,一致性是指事务完成后数据库是处于一种一致状态;例如,一个账户转钱给另一个账户,两个账户总额不应该改变。 在ACID-based系统中,这样的一致性通常是编写事务的开发者的责任,但是可以由数据库管理完整性约束来辅助。

一致性 —— 客户端与服务端

有两种视角看待一致性。 一种是开发者/客户端的视角:他们如何观察数据更新。 另一种是服务端视角:更新如何在系统中流通,以及系统能给这些更新的保证。

客户端一致性

客户端有以下部分:

  • 一种存储系统。 我们先把它当作一个黑盒,但是要了解它底层是大规模分布式系统,为保证持久性和可用性而构建。
  • 进程A。 这个进程读写存储系统。
  • 进程B和进程C。 这两个进程独立于进程A且读写存储系统。这与他们是否真是进程或者同一进程中的线程无关;真正重要的是他们相互独立且需要通信共享信息。

客户端一致性需要处理如何和何时观察者们(这个例子中进程A,B或者C)看到数据对象在存储系统中更新。 接下来的例子演示了不同的类型的一致性,流程A对数据对象进行了更新:

  • 强一致性。 更新完成后,任何后续访问(来自A,B或C)将会返回更新的值。
  • 弱一致性。 系统不保证后续访问会得到更新的值。 获取更新值之前需要满足一些条件。 从更新到保证任何观察者将会一直看到更新值的时期被称为不一致窗口。
  • 最终一致性。 这是弱一致性的一种特例; 存储系统保证如果对象没有新的更新,最终所以的访问将会返回最近更新的值。 如果没有失败发生,不一致窗口的大小由通信延迟,系统负载和复制方案的副本数量决定。 最广泛的最终一致性系统是DNS(Nomain Name System)。 一个域名的更新根据配置模式以及时间控制缓存被分布;最终,所以客户端将会看到更新。

最终一致性模型有一些重要的变种需要被考虑到:

  • 因果一致性。 如果进程A通知进程B它更新了一项数据,后续进程B的访存会返回更新的值,并且保证写入将取代之前的写入。与过程A没有因果关系的进程C的访存是遵循一般的最终一致性规则。
  • 读你所写一致性。 这是一种重要的模型,进程A更新一个数据项之后总会得到更新的值永远不会得到旧值。这是因果一致性的一种特例。
  • 会话一致性。 这是上个模型的实践版本,其中是一个进程在一个会话上下文中访问存储系统。 只要这个会话存在,系统保证读你所写一致性。 如果会话因为一些特定场景失效,一个新的会话需要被创建且一致性保证不会跨会话。
  • 单调读一致性。 如果一个进程读取到了一个对象的值,任何后续访问都不会得到之前的值。
  • 单调写一致性。 在这种情况下,系统保证由相同的进程序列化写操作。系统不保证这样的一致性非常难以编程。

以上一致性特性中的一部分可以被组合。例如,单调读一致性可以和会话一致性组合。从实践角度来看,这两个特性(单调读和读你所写)是最终一致性系统中最需要的,但不是一直必须的。这两个特性让开发者更容易构建应用,同时允许存储系统放松一致性且提供高可用性。

从这些变化中可以看出,很多场景是可能的。相应的后果能否被接受取决于特定的应用程序。

最终一致性不是什么极限分布式系统的深奥特性。许多现代关系型数据库管理系统(RDBMSs),提供主备(primary-backup)可靠性,在同步和异步模式实现副本技术。在同步模式副本更新是事务的一部分。在异步模式会延迟到达副本,通常通过日志传输的方式。在异步模式如果主服务在log送达之前宕机,从切换的备份服务读取的将会是旧的,不一致的值。同时,为了支持更好大规模读性能,RDBMSs已经提供从备份服务读取的功能,这是一个经典的保证最终一致性的案例,其不一致性窗口取决于日志传送的周期。

服务端一致性

在服务端我们需要更深入地理解更新在系统中执行的流程,从而理解是什么让开发者遇到不同模式。开始前,让我们先建立一些定义:

N = 存储数据副本的节点的数量
W = 更新成功所需的副本更新成功的数量 (译者注:如果副本更新没有达到这个数量就是更新失败)
R = 一次数据对象读取要访问的副本的数量

如果$W + R > N$,那么写集和读集会一直重叠,这样可以保证强一致性。在使用同步副本的主备RDBMSs场景中,$N = 2, W = 2, R = 1$。客户端无论从哪个副本读都会得到一致的结果。在异步模式且可从备份读取的场景中,$N = 2, W = 1, R = 1$,一致性不能被保证。

这些配置方案就是基本的仲裁协议(quorum protocols),当系统以为故障无法写入W个节点时,写操作失败,意味着系统的不可用。例如$N = 3, W = 3$且只有2个节点可用,系统的写操作失败。

在需要提供高性能和高可用性的分布式存储系统中,副本数量基本上要大于2。只关注容错的系统通常用$N = 3$且$W =2, R = 2$的配置。需要提供非常高的读负载的系统通常会使用超过关注容错系统的副本数量;N可以是几十甚至几百,R配置成1,这样读一次副本就可以返回结果。关注一致性的系统通常为更新设置W = N,这样就降低了写成功的可能性。关注容错而不是一致性的系统通常配置是$W = 1$,获取最小更新持久性(译者注:这是什么鬼)并通过惰性(lazy/epidemic,译者注:跟GFS的primary-secondary更新很像)更新机制去更新其他副本。

如何更新$N, W, R$,依赖于常见的操作是什么和哪个性能指标需要被优化。当$R = 1, N = W$时我们优化读操作,当$W = 1, R = N$我们优化快速写操作。当然对于后者持久性在发生异常时不被保证,如果$W < (N + 1) / 2$,冲突写(译者注:比如,不同客户端发起的写操作)的集合可能不会重叠。

弱或最终一致性产生于$W + R <= N$,意味着存在读写集没有重叠的可能性。如果不是刻意的配置或者基于容错用例,几乎没有任何理由设置R为1而不是其它值。这发生在两种场景中:第一个为了前面提到的读操作扩展的大规模副本;第二个是数据获取更复杂。在简单键值模型非常容易通过版本对比来决定最新写入系统的值,但是在返回对象集的系统中非常难决定最新正确的集合应该是哪个。在大多数写集合小于副本集合的系统中,有一种机制就是,用惰性更新的方式更新副本中剩余未更新的节点。所有副本更新之前的时间段被称为前面提到的不一致性窗口。如果$W + R <= N$,那么系统容易从尚未更新的节点读取数据。

读你所写一致性,会话一致性和单调一致性能否实现,大体上依赖于客户端与对它们执行分布式协议服务端的“粘性”。如果每次都是相同的服务器,那么比较容易保证读你所写和单调一致性读。这样会让负载均衡和容错稍微难以管理,但是是一个简单解决方案。用粘性的会话,让这个显示和提供了一个客户端可以推断的暴露级别。(译者注:什么鬼)

有时候在客户端实现了读你所写和单调读。通过为写添加版本,客户端丢弃读取的版本号比上次所见低的数据。

当系统中一些节点与其它节点断开时分区发生了,但是这两批节点可以被一组客户端访问。如果使用经典的多数仲裁方法,那么当其它副本无法访问时,拥有W个节点的副本集依旧可以继续接受更新。读集合亦是如此。考虑到两个集合的重叠,根据定义,少数集合变得不可用。分区不会经常发生,但是他们的确发生在数据中心之间,以及数据中心内部。

在一些应用中任何分区的不可达都是不可接受的,重要的是能够访问该分区的客户端可以取得进展。在这种场景下两个分区都分配一组新的存储节点接收数据,并且在分区恢复后执行合并操作。例如,在亚马逊购物车就是使用这样的永远可写系统;这样即使发生分区,客户可以继续添加商品到购物车,即使原来的购物车位于其它分区。一旦分区恢复购物车应用(译者注:客户端?)会帮助购物车数据合并。

亚马逊的Dynamo

亚马逊的Dynamo就是这样一个把所有这些特性显示控制的应用架构的系统,它是一个键值存储系统,跟AWS(Amazon’s Web Service)一样,它在内部被广泛用于构建亚马逊电子商务平台的服务。一个重要的设计目标就是让创建Dynamo存储系统(它通常分布在多个数据中心)实例的应用服务的拥有者能在一致性,持久性,可用性和性能之间在一定代价上作出权衡。3

总结

在大规模可靠的分布式系统中,必须容忍数据不一致的原因有两个:在高并发条件下改善读写性能;处理分区场景中大部分模型让系统不可用,即使节点都在运行。

能否接受非一致性取决于客户端应用。在所有场景中,开发者需要意识到一致性保证由存储系统提供,需要在开发应用时考虑到。有许多对最终一致性模型实际的改进,比如会话一致性和单调读,它们提供了更好的工具给开发者。很多时候应用可以很好的处理存储系统的最终一致性。一个流行的用例就是网站,我们可以有用户感知一致性的概念。在这个场景下,不一致性窗口需要小于返回客户加载的下一页所需的时间。这个允许在下次读之前更新可以传播到整个系统。

这篇文章的目的就是引起运行在全球范围的工程系统复杂性的认识,这个系统需要仔细调优来保证它们可以交付应用所需要的持久性,可用性和性能。系统设计者拥有的一个工具之一就是一致性窗口的长度,在这个时间段内系统的客户端可能暴露在大规模工程系统的现实中。

参考文献

  1. Brewer, E. A. 2000. Towards robust distributed systems (abstract). In Proceedings of the 19th Annual ACM Symposium on Principles of Distributed Computing (July 16-19, Portland, Oregon): 7
  2. A Conversation with Bruce Lindsay. 2004. ACM Queue 2(8): 22-33.
  3. DeCandia, G., Hastorun, D., Jampani, M., Kakulapati, G., Lakshman, A., Pilchin, A., Sivasubramanian, S., Vosshall, P., Vogels, W. 2007. Dynamo: Amazon’s highly available key-value store. In Proceedings of the 21st ACM Symposium on Operating Systems Principles (Stevenson, Washington, October).
  4. Gilbert , S., Lynch, N. 2002. Brewer’s conjecture and the feasibility of consistent, available, partition-tolerant Web services. ACM SIGACT News 33(2).
  5. Lindsay, B. G., Selinger, P. G., et al. 1980. Notes on distributed databases. In Distributed Data Bases, ed. I. W. Draffan and F. Poole, 247-284. Cambridge: Cambridge University Press. Also available as IBM Research Report RJ2517, San Jose, California (July 1979).