CAP 定理
1. CAP 定理
CAP 定理(以下简称 CAP)指出,在异步网络中构建同时满足以下三个特性的读写存储实现是不可能的:
- 可用性(Availability)- 向数据存储发出的请求是否最终都能完成?
- 一致性(Consistency)- 所有节点看到的所有读写执行是原子或线性一致的?
- 分区容错性(Partition tolerance)- 允许网络丢失消息。
CAP 定理说明无法构建既能响应每个请求,又能每次都返回预期结果的数据库。
2. 读写存储
CAP 定理特别关注名为寄存器(register)的理论模型。寄存器是支持两种操作的数据结构:
set(X)
将寄存器的值设置为 X
get()
返回寄存器中最近设置的值
可以将键值存储建模为寄存器集合。尽管寄存器看起来非常简单,但它们抓住了许多分布式系统想要做的事情的本质 - 写数据,以及将其读出。
3. 原子(或线性)一致性
原子(或线性)一致性是一种保证,它规定当客户端执行 get()
操作时,返回什么值合适。核心思想是对于所有客户端而言,寄存器看起来就像运行在单台机器上,并且按照操作到达的顺序,进行响应。
考虑到包含由所有客户端执行的操作集的执行过程可能并发。在原子一致性下,这些操作的结果必须等价于所有操作的单个串行(按顺序,一个接一个)执行。
该保证非常严格。它排除其它保证机制,比如最终一致性(EC,Eventual Consistency),EC 允许写入操作在可见之前存在延迟。因此在 EC 下,可能遇到如下情况:
set(10), set(5), get() = 10
但在原子一致性下,该执行无效。
原子一致性还确保关于寄存器值的外部通信可靠。如果我读 X,并且告诉你,然后你去寄存器,自己读 X。在稍弱的保证条件下(比如串行化),这种情况可能不成立。下面我们写 A:意味着客户端 A 执行如下操作。
B:set(5), A:set(10), A:get() = 10, B:get() = 10
这是原子的历史记录。但下面则不是:
B:set(5), A:set(10), A:get() = 10, B:get() = 5
尽管它等价于下面的串行历史记录:
B:set(5), B:get() = 5, A:set(10), A:get() = 10
在第二个例子中,如果 A 在执行 get()
操作后告诉 B 寄存器的值是 10,B 将错误地认为在 A:get()
和 B:get()
之间某个第三方写入了值 5。如果不允许外部通信,B 将不知道 A:set()
,因此 B 看到一致的寄存器状态视图;就好像 B:get()
操作确实发生在 A:set()
之前。
4. 异步网络
异步网络是指网络传递消息或者机器处理消息花费的时间没有上限。该特性的重要结果是无法区分机器发生故障,还是消息延迟。
5. 可用性
当且仅当所有 get 和 set 请求最终返回符合规范的响应时,才认为数据存储可用。该定义不允许返回错误响应,因为通过返回错误即可轻易地满足可用性。
对响应时间没有固定的上限要求,所以系统可以花任意长时间处理请求。但是系统最终必须做出响应。
该要求既强又弱。强是因为 100% 的请求都必须返回响应(此处没有“可用性程度”的说法),弱是因为响应时间可以无界(但必须有限)。
6. 分区
分区是指由于消息丢失(不是延迟 - 最终送达不算分区),网络无法将某些消息传递到一个或多个节点。
该术语有时用于指代两组节点间在一段时间内完全无法传递任何消息。这是更加严格的故障模型。这种类型的分区被称为完全分区。
7. 为什么当将系统描述为 CA 时,有些人很恼火?
Brewer 的 keynote、Gilbert 的论文以及许多其它文献都将 C、A 和 P 置于同等重要的地位,将它们视为系统实现的理想特性,强调“选择两个!”。然而,这种表述方式具有误导性,因为实际上无法构建或选择“分区容错性”:系统要么可能遇到分区,要么遇不到。
CAP 最好被理解为描述当构建可能遭受网络分区的系统时必须做出的权衡。实际上,这适用于每个分布式系统:因为不存在 100% 可靠的网络。所以(至少在分布式环境下)不存在真正的 CA 系统。系统终将面临网络分区的可能性,因此必须在 C 和 A 之间做出妥协。
因此,将该定理重写为以下形式可能更有启发性:
如果系统可能遇到分区,那么不可能始终同时 C 和 A。
确实存在一些不会遇到分区的系统 - 比如单机数据库。但这些系统通常与 CAP 理论最有用的场景无关。如果将分布式数据库描述为“CA 系统”,那么说明对某些概念存在误解。