最终一致性

这是一篇关于亚马逊CTO所写文章的译文:

https://www.allthingsdistributed.com/2008/12/eventually_consistent.html

最终一致-构造一个世界范围级的可靠分布式系统需要在一致性和可用性之间做权衡

Amzon S3,SimpleDB,EC2构建在亚马逊云计算的基础服务上,为其提供诸如构造万维网级计算平台和众多应用的资源。根植于这些基础服务上的需求非常严苛;它们需要高评级的区域安全行,可扩展性,可用性,性能,及合理的费用,尤其是满足服务全球百万级的用户连续使用的需求。为了提供这些服务,就需要一个巨大的运转在全世界的分布式系统。这么巨大的规模带来了巨大的挑战,面对万亿级的请求,任何小概率事件都会出现,为了解释出现的问题,需要预先设计和架构系统。面对这种规模的系统,大多数的设计是用复制技术(replication techniques)去保证一致性和高可用性。尽管复制使我们接近目标,但是不能完美的无副作用的达成目标(注:不知道怎么翻译);在特定的条件下使用这些内部含有复制技术服务的用户将会面临复制带来的不好的结果。这篇文章列出的一系列分布式系统指出了这样一种类型的数据一致性,那就是使用数据复制技术来达到最终一致。当我们在亚马逊设计大型系统时,遵循一系列的原则和抽象概念使我们更专注于去权衡高性能和数据一致性。这篇文章,我将通过呈现一些背景相关的解决方案是如何让大规模系统达到可靠性的原理。(注:不知道怎么翻译,大体上是这个意思

历史观点

在理想世界里只有一种一致性模型:所有的更新都会被观测到。第一次达成这个目标的是,70年代出现数据库系统,但是很艰难。关于一致性最好的观点是Bruce Lindsay提出的“Notes on Distuibuted DataBases”。这篇论文列举了数据库复制技术的基本原则,并且讨论了一些达成一致性的若干技术。这些技术中的大多数都尝试让后端的分布式系统对用户来说是透明的,即用户以为后端只是单一的系统,而不是系统的集合。这段时期,大多数使用这项技术的系统认为,整体失败比打破这种透明性更值得。在90年代,随着网络的出现,70年代的观点被重新审视。在这段时期,大家开始考虑可用性或许是这些系统最终要的属性,但是他们需要权衡随之带来的问题。Eric Brewer,加利福尼亚伯克利大学的计算机教授,在PODC(Principles of Distributed Computing)会议上带来的不同的观点(注:著名的CAP)他认为分布式系统中只有三个属性,即数据一致性,系统可用性,和分区容忍性-在任意时间一个系统只能满足其中两个。

一个没有分区的系统的将会满足数据一致性和可用性,通常被用于事务协议(注:TX)。为了使其工作,客户端和存储系统必须运行在同样的环境下;但是必然会招致失败,因为客户端不能观测到分区出现。一个重要的发现是,在大型系统中,分区发生是必然事件。因此,数据一致性和可用性不可能同时满足。这意味着在分区必然会出现的情况下,要么选择高可用性,要么选择数据一致性。

客户端的开发者必须意识到系统可以提供什么。如果系统重点在一致性,那么开发者不得不面临这样一个事实,系统不一定会提供可用性,举个例子,一次写(注:英文a write),如果这次写由于可用性失败了,开发者不得不处理写其实在后端系统已经成功的事实。如果系统强调可用性,那么系统一直可写,但是在某些情况下,一次读不一定能读到最近的一次写。开发者面临这种情况,需要抉择是否需要一定要读到最近的一次写(注:一致性放宽),应用需要自行决定对过期数据的处理,并运行在这种处理模式下。

ACID属性是定义在事务系统下的一致性属性,它是另一种的一致性保证。在ACID下,一致性表现为,事务在结束后,数据一定是在一种一致性状态;举个例子,多个账户的转账过程中,账户总额一定是不变的。拥有ACID属性的系统,在完整性约束的协助下会对开发者的一次写事务的一致性保证。

一致性-客户端和服务端

有两种不同的角度讨论一致性。一种是客户端开发者视角:他们如何观测数据更新。另一种是服务端视角:更新是如何在系统中传播的,以及系统对于更新可以提供何种保证。

客户端一致性

客户端可以看到下面这些东西:

  • 存储系统。目前我们认为它是个黑盒子,但是可以假设它包含下面这些事实,规模很大,分布很广,并且可以保证耐久性和可用性。
  • 程序A(注:英文Process A)。程序A读/写存储系统
  • 程序B和C。B、C独立于A,也读写存储系统。不关心B、C是否是程序中真正的处理器或线程;我们只关注他们是独立的且通过通信去分享信息。

客户端一致性需要决定在观测到存储系统中数据变化时,何时以及怎么样处理。接下来的例子阐述,当A更新数据时不同类型的一致性

  • 强一致。当更新完成时,接下来的查询返回更新数据。
  • 弱一致。系统不保证读一定可以看到最新的读。在数据返回前一些条件需要被满足。在数据更新到所有观测者观察到数据更新这段时间被称为不一致窗口。
  • 最终一致。这是一种特殊的弱一致;存储系统保证如果没有新的更新发生,那么最终所有访问会返回最近的一次更新。不考虑失败的情况,最大的不一致窗口由下面几个因素决定,通信延迟,系统负载,和系统拷贝数量。DNS这种最流行的系统就是实现的最终一致性。名称的更新是分布式的,更新的步骤取决于配置模型和与之相结合的时间控制缓存,最终,所有的客户端都会观测到这次更新。

最终一致性模型有一些很重要的变量需要考虑:

  • 因果一致。如果A告诉B我更新了一个值,那么B会读到这个更新后的值,并且写一定先于读。C由于和A没有这种因果关系,将只保留最终一致性规则。
  • 读自己的写一致性。这是一个很重要的模型,A一定能读到自己最近一次写的数据。它是因果一致的特例。
  • 会话一致性。这是前一个模型的实践版,一个程序在一次会话中访问存储系统,只要会话存在,那么一定满足读自己的写一致性。如果会话失败,一个新的会话被创建,不一定保证提供上一个会话的服务。
  • 单调读一致性。如果一个过程观测到一个特殊值,那么接下来不会看到这个值以前的值。
  • 单调写一致性。对于一个程序发出的多个写,系统保证线性化。系统如果连这种的一致性都不能保证会严重损害系统的名声。

所有这些属性可以组合。举个例子,单调读一致可以和会话级一致联合。从实践调度来看,这两个属性(单调读和读自己的写)是最被期望包含在最终一致性系统中的,但也不是必须的。这两个属性让开发者很容易构建系统(注:不用操心各种分布式问题),通过允许存储系统放宽一致性来提供更高的可用性。

大家看到这些变化,很少有系统可以满足所有场景。一致性的选择主要还是依赖于特殊的应用可以处理什么的结果来保证的。(注:译者自己的理解)

最终一致性并不是什么神秘的东西。很多关系型数据库使用同步或异步复制技术提供主备可靠性。在同步模式下复制是事务的一部分。在异步模式下,复制到达备份是延时的,通常利用日志实现(注:binlog同步),在异步模式下,如果主从同步失败,读请求从从库读到旧值,和主库不一致的值。为了支持更好的读性能,关系型数据库开始支持从库读,这是一个经典的最终一致性场景,不一致的窗口大小依赖于主从同步的时间间隔。

服务端视角一致性

在服务端视角,我们必须更加深入的去讨论不同模式下数据更新是如何在系统中传播的,不同的模式会给使用者不同的体验。现在让我们在开始前先看一些定义:

N = 系统中节点的数量

W = 数据更新时系统内必须成功更新的节点的数量

R = 一次读操作系统必须成功返回响应数据的节点数量

如果W+R > N,读操作的节点集合和写操作的节点集合绝对会有重叠,这样重叠的节点会保证强一致性。在主备场景下使用同步复制模式的关系型数据库,可以设置N=2,W=2,R=1保证强一致。无论从那个节点响应读请求,都会返回一致的值。在异步复制模式且从从库读的情况下,N=2,W=1,R=1,将不能保证一致性,因为R+W=N。(注:这里只是讨论了再RDBMS的场景下,真实的分布式会更复杂)

在这种基于基础法定人数协议下会有说明问题呢,就是如果没有写够W个节点(不论由于什么原因),系统就会返回失败,导致系统不可用。假设N=3,W=3,但是只有2个几点可用的情形下,系统的写操作一定会失败。

一个分布式存储系统必须支持高性能和高可用,需要复制节点的数量一定是大于2的。当设置N=3,W=2,R=2时,系统可以容忍一个节点失败。系统必须超越其必须的错误容忍度以提供更高的读负载能力,即使是在数据复制的过程中。(注:大家可以看原文)N可以是10或者更多如100,而R的配置为1,这样只要一个节点读成功就会返回。这样的话要保证一致性W就要等于N,但是这样就会消减写的能力。像这种系统的一个通用配置是,不必要求一致性,只是设置W=1来提供最低限度的耐久性,而一致性就依赖于延迟技术来实现余下节点的更新了。(注:大家有没有点疑惑,东西只说了一半)

那么如何配置N,W,R呢,主要还是取决于系统想优化哪方面的东西,读还是写。R=1,N=W可以优化读性能,R=N,W=1可以让写性能最快。(注:基础是保证一致性)接下来的例子,有失败出现的话不能保证耐久性,设置W<(N+1)/2,那么有可能不重叠的读节点集合之间会出现数据冲突。

弱/最终一致性会出现在W+R<=N这种配置下,这样意味着读和写的节点不会有重叠。如果基于不会出现失败的情况下(注:失败,大体上分为两种,a)节点挂了或超时,b)节点逻辑不正常了),没有深入思考的话,是很难理解R的值是可以除了1以外的任意值。(注:不懂)这种配置常见于下面的场景:第一种是读重却节点巨多的系统,第二种是数据访问非常复杂。在一个简单key-value模型下,很容易通过比较版本号知道谁是最近一次写,但是在分布式系统下就很难决定谁是最后一次写(注:网络分区的情况,SMP结构就是并发,分布式结构就是无因果关系)在大多数这种系统中,写节点数量远小于复制节点数量,一种替代的机制是使用延迟复制技术让更新传播到所有复制节点。就想前面说到的这一段时期就是不一致窗口。如果W+R<=N,系统从节点读数据将会出现问题,因为变更还没有到达正在被读的节点。

在使用分布式协议执行操作时,客户端使用粘性技术,可以达成读自己的写,会话,单调一致性。如果每一次客户端的操作都在同样的服务节点执行,那么读自己的写,单调读这种关系性操作就很容易保持。(注:人为的制造因果关系)但是这种很容易造成轻微的负载和单点问题。会话一致性使用粘性技术,显示的达成上面的一致性。(注:大家看原文吧

有时客户端可以自行实现读自己的写和单调读一致性。通过在写操作上加上版本号,在执行读操作时丢弃先于这个版本的数据。

分区发生是这样一种情况,即一些几点不能和另一些节点互相可达,但是在客户端可以访问它们。如果你使用传统的法定人数协议,那么分区发生就是这种情况,W个节点可以持续的做更新,而另一些节点变的不可用。(注:客户端视角)同样的情况也会发生在读节点集合上。定义一个少数派的不可达集合,可以让分区发生是,这两类集合有交集。分区不会频繁发生,但是绝对会在数据中心被发现。

在一些应用中,分区发生将导致系统整体不可用,而在另一些中即使分区发生,系统照样可以运行。在第二种情况,系统重新定一些节点去接受数据,然后通过合并的方式解决分区不一致。举个例子,亚马逊的购物车就是一个永远可写的系统;分区发生时,客户可以持续把东西放入购物车,即使原始的购物车在另一个分区上。购物车应用会在分区恢复时合并存储系统中的数据。

Amazon's Dynamo

Amazon's Dynamo一个key-value形式的存储系统,显示的把上面的属性集成在应用中,并被内嵌入很多服务中,作为如亚马逊电子商务的,亚马逊网站的组成部分。Dynamo的一个设计目标是,使用方创建了一个Dynamo存储系统的实例-一个可扩展的多数据应用中心,可以作为中心节点,并且在一致性、耐久性、可用性、性能之间做权衡。

总结

大规模可靠分布式系统可以容忍数据不一致是由于以下两个原因:在高并发情境下提升读写性能;处理分区情况的发生,即系统中大多数不可达,即使这些节点依然在运行。

不一致是否被接受取决于客户应用。大多数情况下,开发者需要自己意识到存储系统提供的一致性保证是什么,以及在开发应用过程中考虑自己系统的一致性需求。这里有一些实践中关于提升最终一致性方法,例如会话一致性和单调读可以作为比较好的工具提供给用户。有时候应用有能力容忍存储系统提供的最终一致性,而不会导致问题。一个常见的例子,网站通常是处于用户视角下一种一致性。在这种场景下,不一致窗口的时间必须低于用户的期望的页面加载时间。这允许系统在下一个页面被访问时可以传播更新。

这篇文章的主要目标是,让大家意识到面对这种复杂的大规模分布式系统时,我们必须仔细考虑系统可以达成什么样的耐久性,可用性,和性能,不能盲目设计。系统设计者只有一种工具可以去利用,即在客户端系统可能去暴露系统过程中,一致性窗口的大小。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值