3.4.1 CAP与ACID
CAP定理(Consistency、Availability、Partition Tolerance Theorem),也称为Brewer定理,起源于2000年7月,是加州大学伯克利分校的Eric Brewer教授于“ACM分布式计算原理研讨会(PODC)”上提出的一个猜想,如图3-5所示。
图3-5 CAP理论原稿(那时候还只是猜想)[1]
两年后,麻省理工学院的Seth Gilbert和Nancy Lynch以严谨的数学推理证明了CAP猜想。自此,CAP正式从猜想变为分布式计算领域所公认的著名定理。这个定理描述了在一个分布式系统中,涉及共享数据问题时,以下三个特性最多只能同时满足其中两个。
·一致性(Consistency):代表数据在任何时刻、任何分布式节点中所看到的都是符合预期的。一致性在分布式研究中是有严肃定义、有多种细分类型的概念,以后讨论分布式共识算法时,我们还会提到一致性,但那种面向副本复制的一致性与这里面向数据库状态的一致性从严格意义来说并不完全等同,具体差别我们将在第6章再作探讨。
·可用性(Availability):代表系统不间断地提供服务的能力。理解可用性要先理解与其密切相关的两个指标:可靠性(Reliability)和可维护性(Serviceability)。可靠性使用平均无故障时间(Mean Time Between Failure,MTBF)来度量;可维护性使用平均可修复时间(Mean Time To Repair,MTTR)来度量。可用性衡量系统可以正常使用的时间与总时间之比,其表征为:A=MTBF/(MTBF+MTTR),即可用性是由可靠性和可维护性计算得出的比例值,譬如99.9999%可用,即代表平均年故障修复时间为32秒。
·分区容忍性(Partition Tolerance):代表分布式环境中部分节点因网络原因而彼此失联后,即与其他节点形成“网络分区”时,系统仍能正确地提供服务的能力。
单纯只列概念,CAP是比较抽象的,笔者仍以本章开头所列的场景事例来说明这三种特性对分布式系统来说将意味着什么。假设Fenix’s Bookstore的服务拓扑如图3-6所示,一个来自最终用户的交易请求,将交由账号、商家和仓库服务集群中的某一个节点来完成响应。
图3-6 Fenix’s Bookstore的服务拓扑示意图
在这套系统中,每一个单独的服务节点都有自己的数据库[2],假设某次交易请求分别由“账号节点1”“商家节点2”“仓库节点N”联合进行响应。当用户购买一件价值100元的商品后,账号节点1首先应给该用户账号扣减100元货款,它在自己数据库扣减100元很容易,但它还要把这次交易变动告知本集群的节点2到节点N,并要确保能正确变更商家和仓库集群其他账号节点中的关联数据,此时将可能面临以下情况。
·如果该变动信息没有及时同步给其他账号节点,将有可能导致用户购买另一商品时,被分配给另一个节点处理,由于看到账号上有不正确的余额而错误地发生了原本无法进行的交易,此为一致性问题。
·如果由于要把该变动信息同步给其他账号节点,必须暂时停止对该用户的交易服务,直至数据同步一致后再重新恢复,将可能导致用户在下一次购买商品时,因系统暂时无法提供服务而被拒绝交易,此为可用性问题。
·如果由于账号服务集群中某一部分节点因网络问题,无法正常与另一部分节点交换账号变动信息,此时服务集群中无论哪一部分节点对外提供的服务都可能是不正确的。整个集群不能承受由于部分节点之间的连接中断而不断继续正确地提供服务,此为分区容忍性问题。
以上仅仅分析了用户服务集群自身的CAP问题,对于整个Fenix’s Bookstore站点来说,它更是面临着来自于用户、商家和仓库服务集群带来的CAP问题。譬如,用户账号扣款后,由于未及时通知仓库服务中的全部节点,导致另一次交易中看到仓库里有不正确的库存数据而发生超售。又譬如因涉及仓库中某个商品的交易正在进行,为了同步用户、商家和仓库的交易变动,而暂时锁定该商品的交易服务,导致可用性问题,等等。
由于CAP定理已有严格的证明,本节不去探讨为何CAP不可兼得,而是直接分析舍弃C、A、P时所带来的不同影响。
·如果放弃分区容忍性(CA without P),意味着我们将假设节点之间的通信永远是可靠的。永远可靠的通信在分布式系统中必定是不成立的,这不是你想不想的问题,而是只要用到网络来共享数据,分区现象就始终存在。在现实中,最容易找到放弃分区容忍性的例子便是传统的关系数据库集群,这样的集群虽然依然采用由网络连接的多个节点来协同工作,但数据却不是通过网络来实现共享的。以Oracle的RAC集群为例,它的每一个节点均有自己独立的SGA、重做日志、回滚日志等部件,但各个节点是通过共享存储中的同一份数据文件和控制文件来获取数据,通过共享磁盘的方式来避免出现网络分区。因而Oracle RAC虽然也是由多个实例组成的数据库,但它并不能称作分布式数据库。
·如果放弃可用性(CP without A),意味着我们将假设一旦网络发生分区,节点之间的信息同步时间可以无限制地延长,此时,问题相当于退化到前面3.2节讨论的一个系统使用多个数据源的场景之中,我们可以通过2PC/3PC等手段,同时获得分区容忍性和一致性。在现实中,选择放弃可用性的情况一般出现在对数据质量要求很高的场合中,除了DTP模型的分布式数据库事务外,著名的HBase也属于CP系统。以HBase集群为例,假如某个RegionServer宕机了,这个RegionServer持有的所有键值范围都将离线,直到数据恢复过程完成为止,这个过程要消耗的时间是无法预先估计的。
·如果放弃一致性(AP without C),意味着我们将假设一旦发生分区,节点之间所提供的数据可能不一致。选择放弃一致性的AP系统是目前设计分布式系统的主流选择,因为P是分布式网络的天然属性,你再不想要也无法丢弃;而A通常是建设分布式的目的,如果可用性随着节点数量增加反而降低的话,很多分布式系统可能就失去了存在的价值,除非银行、证券这些涉及金钱交易的服务,宁可中断也不能出错,否则多数系统是不能容忍节点越多可用性反而越低的。目前大多数NoSQL库和支持分布式的缓存框架都是AP系统,以Redis集群为例,如果某个Redis节点出现网络分区,那仍不妨碍各个节点以自己本地存储的数据对外提供缓存服务,但这时有可能出现请求分配到不同节点时返回客户端的是不一致的数据的情况。
读到这里,不知道你是否对“选择放弃一致性的AP系统是目前设计分布式系统的主流选择”这个结论感到一丝无奈,本章讨论的话题“事务”原本的目的就是获得“一致性”,而在分布式环境中,“一致性”却不得不成为通常被牺牲、被放弃的那一项属性。但无论如何,我们建设信息系统,终究还是要确保操作结果至少在最终交付的时候是正确的,这句话的意思是允许数据在中间过程出错(不一致),但应该在输出时被修正过来。为此,人们又重新给一致性下了定义,将前面我们在CAP、ACID中讨论的一致性称为“强一致性”(Strong Consistency),有时也称为“线性一致性”(Linearizability,通常是在讨论共识算法的场景中),而把牺牲了C的AP系统又要尽可能获得正确结果的行为称为追求“弱一致性”。不过,如果单纯只说“弱一致性”那其实就是“不保证一致性”的意思。在弱一致性里,人们又总结出了一种稍微强一点的特例,被称为“最终一致性”(Eventual Consistency),它是指如果数据在一段时间之内没有被另外的操作更改,那它最终会达到与强一致性过程相同的结果,有时候面向最终一致性的算法也被称为“乐观复制算法”。
在本节讨论的主题“分布式事务”中,目标同样也不得不从之前三种事务模式追求的强一致性,降低为追求获得“最终一致性”。由于一致性的定义变动,“事务”一词的含义其实也同样被拓展了,人们把使用ACID的事务称为“刚性事务”,而把笔者下面将要介绍的几种分布式事务的常见做法统称为“柔性事务”。
[1] 图片来源:https://people.eecs.berkeley.edu/~brewer/cs262b-2004/PODC-keynote.pdf。
[2] 这里的假设是为了便于说明问题,在实际生产系统中,一般应避免将用户余额这样的数据存储在多个可写的数据库中。