无论设计何种架构,我们都希望实现高性能、高可用的系统。为了突破性能瓶颈,我们从单核转向多核,从单机转向多机;为了防止意外导致系统无法正常运行,我们通过“冗余”来应对各种天灾人祸。分布式系统的目的在于突破单机性能瓶颈并建立不间断的服务,而分布式设计带来的好处,也正是其困难之处。
想象一下这样一个场景:在使用某银行的移动支付转账给朋友时,恰巧遇到某路段修路导致银行台北机房与高雄机房之间的光纤网络被挖断,造成台北机房有转账记录,而高雄机房没有(如图一)。更糟糕的是,此时台北机房对外的网络也意外中断,导致用户查看转账结果时,被导向高雄机房,造成用户误以为转账未成功,再次转账(如图二)。最后,光纤网络修复后,用户才发现原来有两笔转账记录(如图三)。上述情况只是分布式系统可能面临的一种情况。即使网络线路没有被挖断,系统也可能因为网络延迟导致数据短暂不一致。现实中,各种意外都需要应对。此时,如果我们有一个可以帮助我们思考分布式系统的思想框架,将有助于我们设计分布式系统。 分布式系统的思考框架——CAP定理介绍: CAP定理由Eric Brewer于1998年提出,在2000年的PODC会议上发表,2002年由Seth Gilbert和Nancy Lynch证实,因此CAP定理(CAP theorem)也被称为Brewer定理。 CAP定理由一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)组成。由于作者在提出时并未给出这三者的明确定义,因此不同人对CAP的定义存在分歧。这里我们采用Robert Greiner的定义,但需要注意的是,Robert Greiner曾两次定义CAP,这里我们选择新的定义进行解释,因为作者已经将旧的定义标记为“过时”,读者可以自行比较两次定义的差异。 CAP定理: 在一个分布式系统(一组相互连接的节点,共享数据)中,你只能在以下三个保证中拥有两个:一致性、可用性和分区容错性——其中一个必须被牺牲。 以下是更多p标签的内容:在分布式系统(一组相互连接的节点,共享数据)中,你只能在以下三个保证中拥有两个:一致性、可用性和分区容错性——其中一个必须被牺牲。
首先,Robert定义适用于CAP定理的系统,这个系统必须是一个分布式系统。然而,分布式系统有很多种,有些系统只涉及分布计算,但不涉及数据存储,而有些系统则两者都有。因此,作者特别指出,需要的是有“共享数据”的分布式系统。在这个系统内,根据CAP定理,我们的每一次读写最多只能满足Consistency、Availability或Partition Tolerance三者中的两个。后面我们将在说明为什么只能满足其中的两项时进行说明,这里我们先看定义。
对于一致性,Robert的定义是:系统必须保证客户端的读操作会返回给定客户端的最新写入。
对于一致性,Robert的定义是:系统必须保证客户端的读操作会返回给定客户端的最新写入。作者选择从用户的角度而不是从系统的角度描述,原因是在每次事务(Transaction)发生的过程中,系统都处于不一致的状态,因此从系统的角度来看,系统不可能始终保持一致性。从用户的角度来看,我们可以通过一些设计,如Quorum NWR,每次用户读取都至少从R个节点读取,并从中选择最新的数据返回,以此来实现对用户的一致性。
对于可用性,Robert的定义是:一个健康的节点必须在合理的时间内返回合理的响应(没有错误或超时)。
对于可用性,Robert的定义是:一个健康的节点必须在合理的时间内返回合理的响应(没有错误或超时)。作者使用“合理的”而不是“正确的”来描述响应,原因在于系统会面临各种无法控制的风险,但只要系统设计合理,在意外发生的情况下,仍然可以给出合理的响应,那么这个系统就具有可用性。
对于分区容错性,Robert的定义是——在网络分区的情况下,系统仍然可以继续运行。
对于分区容错性,Robert的定义是——在网络分区的情况下,系统仍然可以继续运行。在网络发达的今天,我们的手机几乎始终处于连接状态,因此网络分区对一般用户来说比较难以想象。对于机房来说,不同机房之间的连接通常使用专门的网络线,为了确保机房的网络稳定性和安全性,这些网络线是不公开的。如果这些网络线被挖断,对于机房来说,就等于机房与机房之间的网络被切断,因此机房与机房就会产生分区。而在分区的情况下,不同的机房仍然可以对外继续运行的能力,就叫分区容错性。在实际设计上,分区容错性不仅代表在分区的情况下仍然可以继续运行,还包括在连接恢复后,如何同步和修正两个分区的数据差异,才算完整地达到分区容错性。
CAP定理看似可以从三个中任选两个,但在现实世界中,网络是最无法保证的,因此分区容错性(P)是一定要保障的。所以,在实际设计系统时,我们必须在一致性(C)和可用性(A)之间做出权衡。
ACID是关系型数据库的基石,代表每次事务(Transaction)都需要满足原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。CAP定理是对系统的描述,而ACID是对事务的描述。虽然这两种理论针对不同的层面进行描述,但这里想强调的是ACID是对强一致性的描述,如果在分布式情况下满足ACID,也等于满足了CP模型。
在分布式系统中,要满足系统层面的强一致性,通常会使用两阶段提交(Two-Phase Commit,2PC)。通常步骤如下:
设计两阶段提交时要注意,在准备阶段(Prepare Phase),节点如果回复“允许”进行操作,那么不管发生什么意外,节点都必须保证在执行阶段(Commit Phase)进行此操作。即使准备阶段后,节点因意外关机,节点也必须在意外恢复后,根据协调者的指令完成准备阶段承诺的操作。另外,由于强一致性的关系,所以协调者会在执行阶段(Commit Phase)结束后回复使用者,因为如果协调者在准备阶段(Prepare Phase)结束后就回复给使用者,那么可能因为协调者还没发送执行消息给其他节点前,就意外关机,造成使用者接收到的消息与整个集群不一致。
两阶段操作被应用于许多分布式算法和系统中,比较出名的有MySQL XA、Raft等。在更复杂的情况下,如需要拜占庭容错(Byzantine Fault Tolerance,BFT)的区块链中,甚至进一步使用三阶段提交(Three-Phase Commit,3PC)来防止节点作恶,详细可以参考PBFT。
BASE代表的是基本可用(Basically Available)、最终一致性(Eventually Consistent)和软状态(Soft State)。从字面上看,BASE以可用性为主,对一致性的要求较低,只要最终状态是一致的即可。因此,满足了BASE,基本上就符合AP模型。
对于网络的用户来说,如果一个系统不可用,那么即使应用内部的状态有多么一致,对用户来说都是坏的。因此,我们在网络应用中经常看到基本可用(Basically Available)的手法。以Facebook的点赞数为例,热门帖子刚发表时,会涌入大量的人点赞,每次点赞对系统来说都是一次写入。如果系统为了强一致性而让一部分人暂时无法写入,那么大家一定会觉得系统是不是坏了。因此,Facebook并不需要急于统计出一个精确的数字,因为对大多数人来说,8个赞和10个赞没什么区别,只要系统可以在最后保持最终一致性即可。
然而,只满足基本可用,系统还是需要有方法可以恢复最终一致性(Eventually Consistent)。常见的方法有读时修复(Read Repair)、写时修复(Write Repair)以及反熵(Anti-Entropy)。读时修复在读取时同时从多个节点读取,并以最新的节点为主;写时修复,同时写入多个节点,若发现有写入失败则记录下来,定时重传,直到写入成功或是有新的写入为止;最后,反熵则是定期检查状态是否一致,如果不一致则通过特定的修复顺序,修正每个节点的数据,详细步骤将在之后讲解Gossip协议时提到。
这次我们介绍了CAP定理,以及CAP定理应用的模型。如果想对CAP定理有更深入的了解,可以参考Eric Brewer在2012年写的文章——《CAP定理12年来的回顾》。使用CAP定理有两个需要注意的地方:
首先,尽管系统分区(P)的情况并不常见,但我们一定要为分区容错性做好准备;反之,在没有分区的情况下,我们要尽力同时实现一致性和可用性。其次,尽管我们以CAP来描述整个系统,但实际上,系统内部可以设计成敏感数据满足CP模型,不重要的数据则满足AP模型。因此,CAP定理可以扩展到数据层面进行思考。
最后,理解CAP定理是理解分布式系统的重要入门,也是了解区块链共识机制的基本概念。没有CAP的概念,可能只能在方法与步骤的层面上理解共识机制,但有了CAP的概念,则可以进一步探讨,算法如何在一致性和可用性之间做出权衡。
标签: 数字货币