一、概述
在 20 世纪 90 年代初期 Berkerly 大学有位 Eric Brewer 教授提出了 CAP 定理,又称 CAP 原则 和 作布鲁尔定理。如下图所示:
全称是《Consistency Availability and Partition tolerance》:
- Consistency(一致性):所有节点在同一时刻的数据是相同的,即更新操作执行结束并响应用户完成后,所有节点存储的数据会保持相同。
- Availability(可用性):系统提供的服务一直处于可用状态,对于用户的请求可即时响应。
- Partition tolerance(分区容忍性):系统遇到网络分区的情况下,仍然可以响应用户的请求。
网络分区:指因为网络故障导致网络不连通,不同节点分布在不同的子网络中,各个子网络内网络正常。
Brewer 教授给出的定理是:
任何分布式系统只可同时满足两点,没法三者兼顾。
Brewer 教授给出的忠告是:
架构师不要将精力浪费在如何设计能满足三者的完美分布式系统,而是应该进行取舍。
二、特性
2.1 一致性(Consistence)
各节点的数据保证一致(每次成功写入之后,无论从哪个节点读取,都能读取到最新数据),相当于向所有节点的写操作是原子操作(要么全部失败要么全部成功)。
在分布式系统中要解决的一个重要问题就是数据的复制。在我们的日常开发经验中,相信很多开发人员都遇到过这样的问题:假设客户端 C1 将系统中的一个值 K 由 V1 更新为 V2,但客户端 C2 无法立即读取到 K 的最新值,需要在一段时间之后才能读取到。这很正常,因为数据库复制之间存在延时。
分布式系统对于数据的复制需求一般都来自于以下两个原因:
- 为了增加系统的可用性,以防止单点故障引起的系统不可用。
- 提高系统的整体性能,通过负载均衡技术,能够让分布在不同地方的数据副本都能够为用户提供服务。
数据复制在可用性和性能方面给分布式系统带来的巨大好处是不言而喻的,然而数据复制所带来的一致性挑战,也是每一个系统研发人员不得不面对的。
所谓分布一致性问题,是指在分布式环境中引入数据复制机制之后,不同数据节点之间可能出现的,并无法依靠计算机应用程序自身解决的数据不一致的情况。简单讲,数据一致性就是指在对一个副本数据进行更新的时候,必须确保也能够更新其他的副本,否则不同副本之间的数据将不一致。
那么如何解决这个问题?一种思路是"既然是由于延时动作引起的问题,那我可以将写入的动作阻塞,直到数据复制完成后,才完成写入动作"。 没错,这似乎能解决问题,而且有一些系统的架构也确实直接使用了这个思路。但这个思路在解决一致性问题的同时,又带来了新的问题:写入的性能。如果你的应用场景有非常多的写请求,那么使用这个思路之后,后续的写请求都将会阻塞在前一个请求的写操作上,导致系统整体性能急剧下降。
总得来说,我们无法找到一种能够满足分布式系统所有系统属性的分布式一致性解决方案。因此,如何既保证数据的一致性,同时又不影响系统运行的性能,是每一个分布式系统都需要重点考虑和权衡的。于是,一致性级别由此诞生:
- 强一致性(CAP 指的是强一致性)
写操作完成后,后续的读操作都能看到最新数据。这种一致性级别是最符合用户直觉的,它要求系统写入什么,读出来的也会是什么,用户体验好,但实现起来往往对系统的性能影响大。 - 弱一致性
能容忍部分或全部都看不到最新数据。这种一致性级别约束了系统在写入成功后,不承诺立即可以读到写入的值,也不久承诺多久之后数据能够达到一致,但会尽可能地保证到某个时间级别(比如秒级别)后,数据能够达到一致状态。 - 最终一致性
经过一段时间后,都能看到最新数据。是弱一致性的一个特例,系统会保证在一定时间内,能够达到一个数据一致的状态。这里之所以将最终一致性单独提出来,是因为它是弱一致性中非常推崇的一种一致性模型,也是业界在大型分布式系统的数据一致性上比较推崇的模型。
2.2 可用性(Availability)
可用性是指系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。
- 有限时间内:对于用户的一个操作请求,系统必须能够在指定的时间内返回对应的处理结果,如果超过了这个时间范围,那么系统就被认为是不可用的。另外,"有限的时间内"是指系统设计之初就设计好的运行指标,通常不同系统之间有很大的不同,无论如何,对于用户请求,系统必须存在一个合理的响应时间,否则用户便会对系统感到失望。
- 返回结果:是可用性的另一个非常重要的指标,它要求系统在完成对用户请求的处理后,返回一个正常的响应结果。正常的响应结果通常能够明确地反映出对请求的处理结果,即成功或失败,而不是一个让用户感到困惑的返回结果。
参照前面“一致性”中的两种情形,可见一致性和可用性无法兼顾:
- 若要保证一致性:则必须进行节点间数据同步,同步期间数据锁定,导致期间的读取失败或超时,破坏了可用性。
- 若要保证可用性:则不允许节点间同步期间锁定,这又破坏了一致性。
2.3 分区容错(Partition tolerance)
分布式系统在遇到任何网络分区故障的时候,仍然需要能够保证对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障。
网络分区是指在分布式系统中,不同的节点分布在不同的子网络(机房或异地网络) 中,由于一些特殊的原因导致这些子网络出现网络不连通的状况,但各个子网络的内部网络是正常的,从而导致整个系统的网络环境被切分成了若干个孤立的区域。 需要注意的是,组成一个分布式系统的每个节点的加入与退出都可以看作是一个特殊的网络分区。
分布式系统中,必须满足 CAP 中的 P,此时只能在 C/A 之间作出取舍。
CAP 经常被误解为“三选二”,但实际上必须满足 P,然后在 C/A 之间做出选择。
三、应用
CAP 理论,被看成分布式系统(尤其是分布式存储)的理论基础。
保CA弃P | 保CP弃A | 保AP弃C | |
---|---|---|---|
满足特性 | 一致性、可用性 | 一致性、分区容错性 | 可用性、分区容错性 |
使用场景 | 单机 | 要求数据强一致性的场景 比如银行、金融等 | 要求即时响应用户,但对数据一致性要求较低的场景 比如电商中的商品查询等 |
组件或系统 | 单点的传统关系型数据DBMS 比如MySQL、Oracle等 | Redis、HBase、ZooKeeper等 | Eureka、CoachDB、Cassandra、DynamoDB等 |
-
保 CA 弃 P
现在的网络基础设施无法做到始终保持稳定,网络分区(网络不连通)难以避免。牺牲分区容错性,就相当于放弃使用分布式系统。
既然分布式系统不能采用这种策略,那单点系统毫无疑问就需要满足 CA 特性了,比如关系型数据库 DBMS(比如 MySQL、Oracle)部署在单台机器上,因为不存在网络通信问题,所以保证 CA 就可以了。
对于一个分布式系统来说,CAP 三者中:P 是基本要求,只能通过基础设施提升,无法通过降低 C/A 来提升;然后在 C/A 两者之间权衡。 -
保CP 弃 A:
如果一个分布式场景需要很强的数据一致性,或者该场景可以容忍系统长时间无响应的情况下,保 CP 弃 A 这个策略就比较合适了。
一个保证 CP 而舍弃 A 的分布式系统,一旦发生网络分区会导致数据无法同步的情况,就要牺牲系统的可用性,降低用户体验,直到节点数据达到一致性后在响应用户。所以,这种策略通常用在设计金钱交易的分布式场景下,因为他任何时候都不允许痴线数据不一致的情况,否则就会给用户造成损失。
保证 CP 的系统有很多,典型的有 Redis、HBase、ZooKeeper 等。 -
保 AP 弃 C:
如果一个分布式场景需要很高的可用性,或者说在网络状况不太好的情况下,该场景允许数据暂时不一致,那这种情况下就可以牺牲一定的一致性了。
网络分区出现后,各个节点之间数据无法马上同步,为了保证高可用,分布式系统需要即可响应用户的请求。但, 此时可能某些节点还没有拿到最新数据,只能将本地旧的数据返回给用户,从而导致数据不一致的情况。
适合保证 AP 放弃 C 的场景有很多(CoachDB / Cassandra / DynamoDB),比如很多查询网站、电商系统中的商品查询等,用户体验非常重要,所以大多会保证系统的可用性,而牺牲一定的数据一致性。