Infamous CAP

可用性是一个用来确保系统具有成功处理每个请求并给予响应能力的属性。可用性理论上的定义中提到了 Eventual Response 最终响应,但是,在现实世界中,我们希望避免一个服务需要花费无限长的时候才能回复响应。

理想情况下,我们希望每个操作都是一致的。一致性在这里的定义是原子性或是 Linearizble 可线性化的一致性 (Linearizability 中会介绍)。可线性化的意思是瞬间产生的一系列操作需要表现为他们原本的顺序。可线性化简化了系统可能会处于的状态,并将分布式的系统表现得看起来就像是一台单独的机器一般。

我们会期望系统能够在发生网络分区的情况下同时保证一致性跟可用性。处理器之间会因为网络被分割成多个分区导致无法进行通信:那些需要跨越不同分区间的消息会无法送达目的地。

可用性要求未崩溃的节点来送达对应结果,一致性则要求结果是可线性化的。Eric Brewer 构想的 CAP 推测在 Consistency 一致性、Availability 可用性跟 Partition tolerance 分区容忍性之间需要做出权衡。

可用性的要求没办法在完全异步的系统中满足,而且我们也没办法实现一个能够同时在产生网络分区时能同时对可用性跟一致性做出保证的系统。我们可以构建一个系统在保证强一致性的同时尽量提供可用性,或在保证可用性的时候尽量提供一致性。尽量的意思是在暗示如果所有组件都正常工作,系统就不会违反任何的保证,但在网络分区发生的情况下这个保证是可以降低或者被违反的。

换句话说,CAP 描述了一个潜在的关联选择,在不同的层面上看我们的系统是:

  • Consistent and partition tolerant

    CP 系统宁愿让请求失败也不会提供可能不一致的数据

  • Available and partition rolerant

    AP 系统会丢弃一些一致性的要求并可能会在请求中提供不一致的数据

一个 CP 系统的实例是实现一个一致性算法,要求大部分的节点都能够工作:始终保持一致性,但在网络分区发生时可能会不可用。数据库系统作为一个 AP 系统则始终会提供接收写入跟提供读取的功能,但可能会遇到丢失数据或是得到不一致的结果。

PACELEC conjecture 是 CAP 的一个扩展,在出现网络分区的状态下需要在一致性跟可用性中做出选择 (PAC) ,否则 E 尽管系统能够继续保持运行,我们还是需要在延迟跟一致性上做出一个选择。

Use CAP Carefully

有一点非常重要的是 CAP 讨论的是网络的分区,而不是节点的宕机或其他各种类型的故障 (如崩溃恢复)。一个跟集群内的其他节点分离的节点能够继续处理不一致的请求,但崩溃的节点则不能再有任何的响应。换句话说,这也意味着不需要让节点去面对一致性的问题。换句话说,这不是现实中的真实场景:现实中存在着许多不同的故障场景 (有一些可以通过网络分区来模拟)

CAP 意味着即使在所有节点都正常运行的情况下也需要去面对一致性的问题,但他们直接还是存在着连接性的问题,因为我们期望所有非宕机的节点,不管在有几个节点宕机的情况下都能够有正确的响应。

CAP 推测有时会被描绘成一个三角形,如果我们有一个转动的把手,则每次改变或多或少的会同时对这三个参数产生影响。但是,尽管我们可以在一致性跟可用性上面做协调,但是分区容忍性却是在实际中无法跟其他两样因素做出退让的。

我们在 CAP 中说的可用性跟通常说的 high availability 高可用是不同的。CAP 中没有对执行延迟的边界做出定义,另外,数据库中可用性跟 CAP 是不同的,他并不要求未崩溃的节点一定要为每个请求做出响应。

CAP 推论是用来对分布式系统进行解释的,他被用来对一些故障的场景跟可能的解决方案做评估,但需要记住的非常重要的是,在放弃一致性跟提供不可预测的结果之间存在着一条界限。

只要有足够的活跃副本在线,声称为高可用的数据库在正确的使用下,一样能够在副本中提供一致的结果。当然,还有更多复杂的故障场景,而且 CAP 也只是规则的一个概略,他并没有对整个真实的情况作出完整的解释。

Harvest and Yield

CAP 推测只会以最强的形式对一致性及可用性来进行讨论:Linerizability 可线性化以及系统最终响应每个请求的能力。

这让我们能够集中的去对这两个属性去做出艰难的权衡。但是有些应用可以从较为松懈的假设中得到一些好处,因此我们也需要考虑这些属性的一些较弱一些的形式。

与其在保持一致性或者保持可用性中二选一,系统还可以提供较为松懈的保证。我们可以定义两种可以进行调整的指标:Harvest 收货跟 Yield 产出,在他们之间做出的任何选择都能保持正确的行为:

  • Harvest

    定义了完成的查询是怎样的:如果查询需要返回 100 行数据,但在某些节点故障的情况下只能返回 99 行,也好过在发生故障的情况下让整个查询都失效跟不返回任何东西。

  • Yield

    指定能够成功完成的请求数量跟所有请求的总数的比例,Yield 跟机器的上线时间不同,比如,一个忙碌的节点并没有宕机,但他可能会因此无法对某些请求做出响应。

这将我们关注的权衡的点从绝对的转移到了相对的。我们可以为了 Yield 对 Harvest 进行调整来允许部分请求返回不完整的结果。其中一个提高 Yield 的方式是返回的结果只从当前可用的分区中获取。比如,如果为某些用户保存数据记录的节点宕机了,我们还可以继续为其他的用户提供服务。或者,我们可以要求重要的的应用数据一定要完整的返回,但允许其他的一些请求数据有一些偏离。

清晰的定义跟确保 Harvest 跟 Yield 能够保住我们构建出在应对故障方面更有弹性的系统。