CAP 理论断言任何基于网络的数据共享系统,最多只能知足数据一致性、可用性、分区容忍性三要素中的两个要素。可是经过显式处理分区情形,系统设计师能够作到优化数据一致性和可用性,进而取得三者之间的平衡。算法
自打引入 CAP 理论的十几年里,设计师和研究者已经以它为理论基础探索了各式各样新颖的分布式系统,甚至到了滥用的程度。NoSQL 运动也将 CAP 理论看成对抗传统关系型数据库的依据。数据库
CAP 理论主张任何基于网络的数据共享系统,都最多只能拥有如下三条中的两条:编程
CAP 理论的表述很好地服务了它的目的,即开阔设计师的思路,在多样化的取舍方案下设计出多样化的系统。在过去的十几年里确实涌现了不可胜数的新系统,也随之在数据一致性和可用性的相对关系上产生了至关多的争论。“三选二”的公式一直存在着误导性,它会过度简单化各性质之间的相互关系。如今咱们有必要辨析其中的细节。实际上只有“在分区存在的前提下呈现完美的数据一致性和可用性”这种不多见的状况是 CAP 理论不容许出现的。api
虽然设计师仍然须要在分区的前提下对数据一致性和可用性作取舍,但具体如何处理分区和恢复一致性,这里面有不可胜数的变通方案和灵活度。当代 CAP 实践应将目标定为针对具体的应用,在合理范围内最大化数据一致性和可用性的“协力”。这样的思路延伸为如何规划分区期间的操做和分区以后的恢复,从而启发设计师加深对 CAP 的认识,突破过去因为 CAP 理论的表述而产生的思惟局限。缓存
理解 CAP 理论的最简单方式是想象两个节点分处分区两侧。容许至少一个节点更新状态会致使数据不一致,即丧失了 C 性质。若是为了保证数据一致性,将分区一侧的节点设置为不可用,那么又丧失了 A 性质。除非两个节点能够互相通讯,才能既保证 C 又保证 A,这又会致使丧失 P 性质。通常来讲跨区域的系统,设计师没法舍弃 P 性质,那么就只能在数据一致性和可用性上作一个艰难选择。不确切地说,NoSQL 运动的主题实际上是创造各类可用性优先、数据一致性其次的方案;而传统数据库坚守 ACID 特性(原子性、一致性、隔离性、持久性),作的是相反的事情。下文“ACID、BASE、CAP”小节详细说明了它们的差别。服务器
事实上,CAP 理论自己就是在相似的讨论中诞生的。早在 1990 年代中期,我和同事构建了一系列的基于集群的跨区域系统(实质上是早期的云计算),包括搜索引擎、缓存代理以及内容分发系统1。从收入目标以及合约规定来说,系统可用性是首要目标,于是咱们常规会使用缓存或者过后校核更新日志来优化系统的可用性。尽管这些策略提高了系统的可用性,但这是以牺牲系统数据一致性为代价的。网络
关于“数据一致性 VS 可用性”的第一回合争论,表现为 ACID 与 BASE 之争2。当时 BASE 还不怎么被人们接受,主要是你们看重 ACID 的优势而不肯意放弃。提出 CAP 理论,目的是证实有必要开拓更广阔的设计空间,所以才有了“三选二”公式。CAP 理论最先在 1998 年秋季提出,1999 年正式发表3,并在 2000 年登上 Symposium on Principles of Distributed Computing 大会的主题演讲4,最终确立了该理论的正确性。数据结构
“三选二”的观点在几个方面起了误导做用,详见下文“CAP 之惑”小节的解释。首先,因为分区不多发生,那么在系统不存在分区的状况下没什么理由牺牲 C 或 A。其次,C 与 A 之间的取舍能够在同一系统内以很是细小的粒度反复发生,而每一次的决策可能由于具体的操做,乃至由于牵涉到特定的数据或用户而有所不一样。最后,这三种性质均可以在程度上衡量,并非非黑即白的有或无。可用性显然是在 0% 到 100% 之间连续变化的,一致性分不少级别,连分区也能够细分为不一样含义,如系统内的不一样部分对因而否存在分区能够有不同的认知。多线程
要探索这些细微的差异,就要突破传统的分区处理方式,而这是一项根本性的挑战。由于分区不多出现,CAP 在大多数时候容许完美的 C 和 A。但当分区存在或可感知其影响的状况下,就要预备一种策略去探知分区并显式处理其影响。这样的策略应分为三个步骤:探知分区发生,进入显式的分区模式以限制某些操做,启动恢复过程以恢复数据一致性并补偿分区期间发生的错误。并发
ACID 和 BASE 表明了两种截然相反的设计哲学,分处一致性 - 可用性分布图谱的两极。ACID 注重一致性,是数据库的传统设计思路。我和同事在 1990 年代晚期提出 BASE,目的是抓住当时正逐渐成型的一些针对高可用性的设计思路,而且把不一样性质之间的取舍和消长关系摆上台面。现代大规模跨区域分布的系统,包括云在内,同时运用了这两种思路。
这两个术语都好记有余而精确不足,出现较晚的 BASE 硬凑的感受更明显,它是“Basically Available, Soft state, Eventually consistent(基本可用、软状态、最终一致性)”的首字母缩写。其中的软状态和最终一致性这两种技巧擅于对付存在分区的场合,并所以提升了可用性。
CAP 与 ACID 的关系更复杂一些,也所以引发更多误解。其中一个缘由是 ACID 的 C 和 A 字母所表明的概念不一样于 CAP 的 C 和 A。还有一个缘由是选择可用性只部分地影响 ACID 约束。ACID 四项特性分别为:
原子性(A)。全部的系统都受惠于原子性操做。当咱们考虑可用性的时候,没有理由去改变分区两侧操做的原子性。并且知足 ACID 定义的、高抽象层次的原子操做,实际上会简化分区恢复。
一致性(C)。ACID 的 C 指的是事务不能破坏任何数据库规则,如键的惟一性。与之相比,CAP 的 C 仅指单一副本这个意义上的一致性,所以只是 ACID 一致性约束的一个严格的子集。ACID 一致性不可能在分区过程当中保持,所以分区恢复时须要重建 ACID 一致性。推而广之,分区期间也许不可能维持某些不变性约束,因此有必要仔细考虑哪些操做应该禁止,分区后又如何恢复这些不变性约束。
隔离性(I)。隔离是 CAP 理论的核心:若是系统要求 ACID 隔离性,那么它在分区期间最多能够在分区一侧维持操做。事务的可串行性(serializability)要求全局的通讯,所以在分区的状况下不能成立。只要在分区恢复时进行补偿,在分区先后保持一个较弱的正确性定义是可行的。
持久性(D)。牺牲持久性没有意义,理由和原子性同样,虽然开发者有理由(持久性成本过高)选择 BASE 风格的软状态来避免实现持久性。这里有一个细节,分区恢复可能由于回退持久性操做,而无心中破坏某项不变性约束。但只要恢复时给定分区两侧的持久性操做历史记录,破坏不变性约束的操做仍是能够被检测出来并修正的。一般来说,让分区两侧的事务都知足 ACID 特性会使得后续的分区恢复变得更容易,而且为分区恢复时事务的补偿工做奠基了基本的条件。
CAP 理论的经典解释,是忽略网络延迟的,但在实际中延迟和分区紧密相关。CAP 从理论变为现实的场景发生在操做的间歇,系统须要在这段时间内作出关于分区的一个重要决定:
依靠屡次尝试通讯的方法来达到一致性,好比 Paxos 算法或者两阶段事务提交,仅仅是推迟了决策的时间。系统终究要作一个决定;无限期地尝试下去,自己就是选择一致性牺牲可用性的表现。
所以以实际效果而言,分区至关于对通讯的时限要求。系统若是不能在时限内达成数据一致性,就意味着发生了分区的状况,必须就当前操做在 C 和 A 之间作出选择。这就从延迟的角度抓住了设计的核心问题:分区两侧是否在无通讯的状况下继续其操做?
从这个实用的观察角度出发能够导出若干重要的推论。第一,分区并非全体节点的一致看法,由于有些节点检测到了分区,有些可能没有。第二,检测到分区的节点即进入分区模式——这是优化 C 和 A 的核心环节。
最后,这个观察角度还意味着设计师能够根据指望中的响应时间,有意识地设置时限;时限设得越短,系统进入分区模式越频繁,其中有些时候并不必定真的发生了分区的状况,可能只是网络变慢而已。
有时候在跨区域的系统,放弃强一致性来避免保持数据一致所带来的高延迟是很是有意义的。Yahoo 的 PNUTS 系统由于以异步的方式维护远程副本而带来数据一致性的问题5。但好处是主副本就放在本地,减少操做的等待时间。这个策略在实际中很实用,由于通常来说,用户数据大都会根据用户的(平常)地理位置作分区。最理想的情况是每一位用户都在他的数据主副本附近。
Facebook 使用了相反的策略6:主副本被固定在一个地方,所以远程用户通常访问到的是离他较近,但可能已通过时的数据副本。不过当用户更新其页面的时候是直接对主副本进行更新,并且该用户的全部读操做也被短暂转向从主副本读取,尽管这样延迟会比较高。20 秒后,该用户的流量被从新切换回离他较近的副本,此时副本应该已经同步好了刚才的更新。
CAP 理论常常在不一样方面被人误解,对于可用性和一致性的做用范围的误解尤其严重,可能形成不但愿看到的结果。若是用户根本获取不到服务,那么其实谈不上 C 和 A 之间作取舍,除非把一部分服务放在客户端上运行,即所谓的无链接操做或称离线模式7。离线模式正变得愈来愈重要。HTML5 的一些特性,特别是客户端持久化存储特性,将会促进离线操做的发展。支持离线模式的系统一般会在 C 和 A 中选择 A,那么就不得不在长时间处于分区状态后进行恢复。
“一致性的做用范围”其实反映了这样一种观念,即在必定的边界内状态是一致的,但超出了边界就无从谈起。好比在一个主分区内能够保证完备的一致性和可用性,而在分区外服务是不可用的。Paxos 算法和原子性多播(atomic multicast)系统通常符合这样的场景8。像 Google 的通常作法是将主分区归属在单一个数据中内心面,而后交给 Paxos 算法去解决跨区域的问题,一方面保证全局协商一致(global consensus)如 Chubby9,一方面实现高可用的持久性存储如 Megastore10。
分区期间,独立且能自我保证一致性的节点子集合能够继续执行操做,只是没法保证全局范围的不变性约束不受破坏。数据分片(sharding)就是这样的例子,设计师预先将数据划分到不一样的分区节点,分区期间单个数据分片多半能够继续操做。相反,若是被分区的是内在关系密切的状态,或者有某些全局性的不变性约束非保持不可,那么最好的状况是只有分区一侧能够进行操做,最坏状况是操做彻底不能进行。
“三选二”的时候取 CA 而舍 P 是否合理?已经有研究者指出了其中的要害——怎样才算“舍 P”含义并不明确11,12。设计师能够选择不要分区吗?哪怕原来选了 CA,当分区出现的时候,你也只能回头从新在 C 和 A 之间再选一次。咱们最好从几率的角度去理解:选择 CA 意味着咱们假定,分区出现的可能性要比其余的系统性错误(如天然灾难、并发故障)低不少。
这种观点在实际中颇有意义,由于某些故障组合可能致使同时丢掉 C 和 A,因此说 CAP 三个性质都是一个度的问题。实践中,大部分团体认为(位于单一地点的)数据中心内部是没有分区的,所以在单一数据中心以内能够选择 CA;CAP 理论出现以前,系统都默认这样的设计思路,包括传统数据库在内。然而就算可能性不高,单一数据中心彻底有可能出现分区的状况,一旦出现就会动摇以 CA 为取向的设计基础。最后,考虑到跨区域时出现的高延迟,在数据一致性上让步来换取更好性能的作法相对比较常见。
CAP 还有一个方面不少人认识不清,那就是放弃一致性其实有隐藏负担,即须要明确了解系统中存在的不变性约束。知足一致性的系统有一种保持其不变性约束的天然倾向,即使设计师不清楚系统中全部的不变性约束,至关一部分合理的不变性约束会自动地维持下去。相反,当设计师选择可用性的时候,由于须要在分区结束后恢复被破坏的不变性约束,显然必须将各类不变性约束一一列举出来,可想而知这件工做颇有挑战又很容易犯错。放弃一致性为何难,其核心仍是“并发更新问题”,跟多线程编程比顺序编程难的缘由是同样的。
怎样缓和分区对一致性和可用性的影响是对设计师的挑战。其关键是以很是明确、公开的方式去管理分区,不只须要主动察觉分区的发生,还须要为分区期间全部可能受侵害的不变性约束预备专门的恢复过程和计划。管理分区有三个步骤:
(点击看大图)
最后一步的目的是恢复一致性,以及补偿在系统分区期间程序产生的错误。
图 1 可见分区的演变过程。普通的操做都是顺序的原子操做,所以分区老是在两笔操做之间开始。一旦系统在操做间歇检测到分区发生,检测方一侧即进入分区模式。若是确实发生了分区的状况,那么通常分区两侧都会进入到分区模式,不过单方面完成分区也是可能的。单方面分区要求在对方按须要通讯的时候,本方要么能正确响应,要么不须要通讯;总之操做不得破坏一致性。但无论怎么样,因为检测方可能有不一致的操做,它必须进入分区模式。采起了 quorum 决定机制的系统即为单方面分区的例子。其中一方拥有“法定经过节点数”,所以能够执行操做,而另外一方不能够执行操做。支持离线操做的系统明显地含有“分区模式”的概念,一些支持原子多播(atomic multicast)的系统也含有这个概念,如 Java 平台的 JGroups。
当系统进入到分区模式,它有两种可行的策略。其一是限制部分操做,所以会削弱可用性。其二是额外记录一些有利于后面分区恢复的操做信息。系统可经过持续尝试恢复通讯来察觉分区什么时候结束。
决定限制哪些操做,主要取决于系统须要维持哪几项不变性约束。在给定了不变性约束条件以后,设计师须要决定在分区模式下,是否坚持不触动某项不变性约束,抑或以过后恢复为前提去冒险触犯它。例如,对于“表中键的唯一性”这项不变性约束,设计师通常都选择在分区期间放宽要求,允许重复的键。重复的键很容易在恢复阶段检查出来,假如重复键能够合并,那么设计师不难恢复这项不变性约束。
对于分区期间必须维持的不变性约束,设计师应当禁止或改动可能触犯该不变性约束的操做。(通常而言,咱们没办法知道操做是否真的会破坏不变性约束,由于没法知道分区另外一侧的状态。)信用卡扣费等具备外部化特征的事件常以这种方式工做。适合这种状况的策略,是记录下操做意图,而后在分区恢复后再执行操做。这类事务每每从属于一些更大的工做流,在工做流明确含有相似“订单处理中”状态的状况下,将操做推迟到分区结束并没有明显的坏处。设计师以用户不易察觉的方式牺牲了可用性。用户只知道本身下了指令,系统稍后会执行。
说得更归纳一点,分区模式给用户界面提出了一种根本性的挑战,即如何传达“任务正在进行还没有完成”的信息。研究者已经从离线操做的角度对此问题进行了一些深刻的探索,离线操做能够当作时间很长的一次分区。例如 Bayou 的日历程序用颜色来区分显示可能(暂时)不一致的条目13。工做流应用和带离线模式的云服务中也常见相似的提醒,前者的例子如交易中的电子邮件通知,后者的例子如 Google Docs。
在分区模式的讨论中,咱们将关注点放在有明确意义的原子操做而非单纯的读写,其中一个缘由是操做的抽象级别越高,对不变性约束的影响一般就越容易分析清楚。大致来讲,设计师要创建一张全部操做与全部不变性约束的叉乘表格(cross product),观察并肯定其中每一处操做可能与不变性约束相冲突的地方。对于这些冲突状况,设计师必须决定是否禁止、推迟或修改相应的操做。在实践中,这类决定还受到分区前状态和 / 或环境参数的影响。例若有的系统为特定的数据设立了主节点,那么通常容许主节点执行操做,不容许其余节点操做。
对分区两侧跟踪操做历史的最佳方式是使用版本向量,版本向量能够反映操做间的因果依赖关系。向量的元素是(节点, 逻辑时间)数值对,分别对应一个更新了对象的节点和它最后更新的时间。对于同一对象的两个给定的版本 A 和 B,当全部结点的版本向量一致有 A 的时间大于或等于 B 的时间,且至少有一个节点的版本向量有 A 的时间较大,则 A 新于 B。
若是不可能对版本向量排序,那么更新操做是并发的,并且有可能出现不一致的状况。只要知道分区两侧版本向量的沿革。系统不难判断哪些操做的执行顺序是肯定的,哪些操做是并发的。最近的研究成果证实14,当设计师选择可用性优先,通常最多只能将一致性收紧到这样的程度。
到了某个时刻,通讯恢复,分区结束。因为每一侧在分区期间都是可用的,其状态仍继续向前进展,可是分区会推迟某些操做并侵犯一些不变性约束。分区结束的时刻,系统知道分区两侧的当前状态和历史记录,由于它在分区模式下记录了详尽的日志。当前状态不如历史记录有价值,由于经过历史记录,系统能够判断哪些操做违反了不变性约束,产生了何种外在的后果(如发送了响应给用户)。在分区恢复过程当中,设计师必须解决两个问题:
一般状况,矫正当前状态最简单的解决方法是回退到分区开始时的状态,以特定方式推动分区两侧的一系列操做,并在过程当中一直保持一致的状态。Bayou 就是这个实现机制,它会回滚数据库到正确的时刻并按无歧义的、肯定性的顺序从新执行全部的操做,最终使全部的节点达到相同的状态15。一样地,并发版本控制系统 CVS 在合并分支的时候,也是从从一个共享的状态一致点开始,逐步将更新合并上去。。
大部分系统都存在不能自动合并的冲突。好比,CVS 时不时有些冲突须要手动介入,带离线模式的 wiki 系统老是把冲突留在产生的文档里给用户处理16。
相反,有些系统用了限制操做的办法来保证冲突总能合并。一个例子就是 Google Docs 将其文本编辑操做17精简为应用样式、添加文本和删除文本。所以,虽然总的来讲冲突问题不可解,但现实中设计师能够选择在分区期间限制使用部分操做,以便系统在恢复的时候可以自动合并状态。若是要实施这种策略,推迟有风险的操做是相对简单的实现方式。
还有一种办法是让操做能够交换顺序,这种办法最接近于造成一种解决自动状态合并问题的通用框架。此类系统将线性合并各日志并重排操做的顺序,而后执行。操做知足交换率,意味着操做有可能从新排列成一种全局一致的最佳顺序。不幸的是,只容许知足交换率的操做这个想法实现起来没那么容易。好比加法操做能够交换顺序,可是加入了越界检查的加法就不行了。
Marc Shapiro 及其 INRIA 同事最近的工做18,19对于可交换顺序的操做在状态合并方面的应用起了很大的促进做用。该团队提出一种从理论上证实能够保证分区后合并的数据类型,称为可交换多副本数据类型(commutative replicated data types,CRDTs)。他们介绍了如何使用此类数据结构来
用后一种方法合并状态会汇总分区两边的最大集合。这种方法是对亚马逊购物车合并算法20的形式化总结和改良,合并后的数据是两边购物车的并集,而并运算是一种单调的集合运算。这种策略的坏处是删掉的购物车商品有可能再次出现。
其实 CRDTs 彻底能够实现同时支持增、删操做的分区耐受集合。此方法的本质是维护两个集合:一个放增长的项目,一个放删除的项目,两集合之差即为真正的集合成员。增集合、删集合分别合并起来都不困难,于是增删集合之差合并起来也不困难。在某个时间点上,系统能够从两个集合中清理掉删除的数据项。假如按照通常的设计,像这种清理操做仅在系统没分区的时候才可行,属于设计师必须在分区期间禁止或推迟的特定操做,可是 CRDTs 的清理操做并不会对可用性产生外在的影响。所以经过 CRDTs 来实现状态,设计师既保证了可用性,又保证了分区后系统自动合并状态。
比计算分区后状态更难解决的问题是如何弥补分区期间形成的错误。跟踪和限制分区模式下的操做,这两种措施足以使设计师确知哪些不变性约束可能被违反,而后分别为它们制定恢复策略。通常系统在分区恢复期间检查违反状况,修复工做也必须在这段时间内完成。
恢复不变性约束的方法有不少,粗陋一点的办法如“最后写入者胜”(所以会忽略部分更新),聪明一点的办法如合并操做和人为跟进事态(human escalation)。人为跟进事态的例子如飞机航班“超售”的情形:能够把乘客登机看做是对以前售票状况的分区恢复,必须恢复“座位数很多于乘客数”这项不变性约束。那么当乘客太多的时候,有些乘客将失去座位,客服最好能设法补偿他们。
航班的例子揭示了一个外在错误(externalized mistake):假如航空公司没说过乘客必定有座位,这个问题会好解决得多。所以咱们看到推迟有风险的操做的又一个理由——到了分区恢复的时候,咱们才知道真实的状况。矫正此类错误的核心概念是“补偿(compensation)”;设计师必须设立补偿操做,除了恢复不变性约束,还要纠正外在错误。
技术上 CRDTs 只容许局部可验证的不变性约束,因此没有补偿的必要,虽然这种限制下降了 CRDTs 方法自己的能力。用了 CRDTs 来处理状态合并的设计方案能够容许暂时违反全局性的不变量约束,分区结束后才合并状态,以及履行必要的补偿。
恢复外在错误一般要求知道一些有关外在输出的历史信息。以“喝醉酒打电话”为例,一位老兄不记得本身昨晚喝高了的时候打过几个电话,虽然他次日白天恢复了正常状态,但通话日志上的记录都还在,其中有些通话极可能是错误的。拨出的电话就是这位老兄的状态(喝高了)的外在影响。而因为这位老兄不记得打过什么电话,也就很难补偿其中可能形成的麻烦。
又以机器为例,电脑可能在分区期间把一份订单执行了两次。若是系统能区分两份同样的订单是有意的仍是重复了,它就能取消掉一份重复的订单。若是此次错误产生了外在影响,补偿策略能够是自动生成一封电子邮件,向顾客解释系统意外将订单执行了两次,如今错误已经被纠正,附上一张优惠券下次能够用。假如没有完善的历史记录,就只好靠顾客亲自去发现错误了。
曾经有人正式研究过将补偿性事务做为处理长寿命事务(long-lived transactions)的一种手段21,22。长时间运行的事务会面临另外一种形态的分区决策:是长时间持有锁来保证一致性比较好呢?仍是及早释放锁向其余事务暴露未提交的数据,提升并发能力比较好呢?好比在单笔事务中更新全部的员工记录就是一个典型例子。按照通常的方式串行化这笔事务,将致使全部的记录都被锁定,阻止并发。而补偿性事务采起另外一种方式,它将大事务拆成多个分别提交的子事务。若是要停止大事务,系统必须发起一笔新的、起纠正做用的事务,逐一撤销全部已经提交的子事务,这笔新事务就是所谓的补偿性事务。
总的来讲,补偿性事务的目的是避免停止其余用了未正确提交数据的事务(即不容许级联取消)。这种方案不依赖串行化或隔离的手段来保障正确性,其正确性取决于事务序列对状态和输出所产生的净影响。那么,通过补偿,数据库的状态到底是不是至关于那些子事务根本没执行过同样呢?考虑等价必须连外在行为也包括在内;举个例子,把重复扣取的交易款退还给顾客,很难说成等于一开始就没多收顾客的钱,但从结果上看勉强算扯平了。分区恢复也延续一样的思路。虽然服务不必定总能直接撤销其错误,但起码认可错误并作出新的补偿行为。怎样在分区恢复中运用这种思路效果最好,这个问题没有固定的答案。“自动柜员机上的补偿问题”小节以一个很小的应用领域为例点出了一些思考方向。
当系统中存在分区,系统设计师不该该盲目地牺牲一致性或可用性。运用以上讨论的方法,设计师经过细致地管理分区期间的不变性约束,两方面的性质均可以取得最佳的表现。随着版本向量和 CRDTs 等比较新的技术逐渐被归入一些简化其用法的框架,这方面的优化手段会获得比较广泛的应用。但引入 CAP 实践毕竟不像引入 ACID 事务那么简单,实施的时候须要对过去的策略进行全面的考虑,最佳的实施方案极大地依赖于具体服务的不变性约束和操做细节。
以自动柜员机(ATM)的设计来讲,强一致性看似符合逻辑的选择,但现实状况是可用性远比一致性重要。理由很简单:高可用性意味着高收入。无论怎么样,讨论如何补偿分区期间被破坏的不变性约束,ATM 的设计很适合做为例子。
ATM 的基本操做是存款、取款、查看余额。关键的不变性约束是余额应大于或等于零。由于只有取款操做会触犯这项不变性约束,也就只有取款操做将受到特别对待,其余两种操做随时均可以执行。
ATM 系统设计师能够选择在分区期间禁止取款操做,由于在那段时间里没办法知道真实的余额,固然这样会损害可用性。现代 ATM 的作法正相反,在 stand-in 模式下(即分区模式),ATM 限制净取款额不得高于 k,好比 k 为 $200。低于限额的时候,取款彻底正常;当超过限额的时候,系统拒绝取款操做。这样,ATM 成功将可用性限制在一个合理的水平上,既容许取款操做,又限制了风险。
分区结束的时候,必须有一些措施来恢复一致性和补偿分区期间系统所形成的错误。状态的恢复比较简单,由于操做都是符合交换率的,补偿就要分几种状况去考虑。最后的余额低于零违反了不变性约束。因为 ATM 已经把钱吐出去了,错误成了外部实在。银行的补偿办法是收取透支费并期望顾客偿还。由于风险已经受到限制,问题并不严重。还有一种状况是分区期间的某一刻余额已经小于零(但 ATM 不知道),此时一笔存款从新将余额变为正的。银行能够追溯产生透支费,也能够由于顾客已经缴付而忽略该违反状况。
总而言之,由于通讯延迟的存在,银行系统不依靠一致性来保证正确性,而更多地依靠审计和补偿。“空头支票诈骗”也是相似的例子,顾客赶在多家分行对帐以前分别取出钱来而后逃跑。透支的错误事后才会被发现,对错误的补偿也许体现为法律行动的形式。
感谢 Mike Dahlin、Hank Korth、Marc Shapiro、Justin Sheehy、Amin Vahdat、Ben Zhao 以及 IEEE Computer Society 的志愿者们,感谢他们对本文的有益反馈。
Eric Brewer是 University of California, Berkeley 的计算机科学教授,在 Google 担任基础设施方面的 VP。他的研究兴趣包括云计算、可伸缩的服务器、传感器网络,还有适合发展中地区应用的技术。他还帮助创建了美国联邦政府的门户网站 USA.gov。Brewer 从 MIT 得到电子工程和计算机科学的博士学位。他是 National Academy of Engineering 的院士。联系方式:brewer@cs.berkeley.edu