对于设计分布式系统的架构师来说,CAP 是必须掌握的理论。
在一个分布式系统(指互相连接并共享数据的节点的集合)中,当涉及读写操作时,只能保证一致性(Consistence)、可用性(Availability)、分区容错性(Partition Tolerance)三者中的两个,另外一个必须被牺牲。
分布式并不一定会互联和共享数据。
- CAP探讨的对象是
interconnected
和share data
的分布式系统。- CAP关注的是对数据的读写操作,而不是分布式系统所有功能。
特性 | 原始解释 | 升级版解释 |
---|---|---|
一致性(Consisitency) | 所有节点在同一时刻都能看到相同的数据 | 对某个指定的客户端来说,读操作保证能够返回最新的写操作结果 |
可用性(Availability) | 每个请求都能得到成功或者失败的响应 | 非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应) |
分区容错性(Partition Tolerance) | 出现消息丢失或者分区错误时系统能够继续运行 | 当出现网络分区后,系统能够继续“履行职责” |
一致性:,对于系统执行事务来说,在事务执行过程中,系统其实处于一个不一致的状态,不同的节点的数据并不完全一致,也就是出现
same time + different data
的情况,而不是每时每刻都是same time + same data
。网络分区:在分布式集群中,节点之间由于网络不通,导致集群中节点形成不同的子集,子集中节点间的网络相通,而子集和子集间网络不通。
虽然 CAP 理论定义是三个要素中只能取两个,但放到分布式环境下必须选择 P(分区容忍)要素,因为网络本身无法做到 100% 可靠,有可能出故障,所以分区是必然的现象。
假设实现
CA
架构,当发生分区现象时,为了保证 C,系统需要禁止写入,当有写入请求时,系统返回 error(例如,当前系统不允许写入),这又和 A 冲突了,因为 A 要求返回no error
和no timeout
。因此,分布式系统理论上不可能选择CA
架构,只能选择CP
或者AP
架构。
如下图所示,为了保证一致性,当发生分区现象后,N1 节点上的数据已经更新到 y
,但由于 N1 和 N2 之间的复制通道中断,数据 y
无法同步到 N2,N2 节点上的数据还是 x
。
这时客户端访问 N2 时,N2 需要返回 Error
,提示客户端“系统现在发生了错误”,这种处理方式违背了可用性(Availability)的要求,因此 CAP 三者只能满足 CP。
如下图所示,为了保证可用性,当发生分区现象后,N1 节点上的数据已经更新到 y
,但由于 N1 和 N2 之间的复制通道中断,数据 y
无法同步到 N2,N2 节点上的数据还是 x
。
这时客户端访问 N2 时,N2 将当前自己拥有的数据 x
返回给客户端,而实际上当前最新的数据已经是 y
,这就不满足一致性(Consistency)的要求,因此 CAP 三者只能满足 AP。
注意:这里 N2 节点返回
x
,虽然不是一个“正确”的结果,但是一个“合理”的结果,因为x
是旧的数据,并不是一个错乱的值,只是不是最新的数据而已。
C 与 A 之间的取舍可以在同一系统内以非常细小的粒度反复发生,而每一次的决策可能因为具体的操作,乃至因为牵涉到特定的数据或用户而有所不同。
CAP 理论的定义和解释中,都是 system
、node
这类系统级的概念,这造成了误导,认为在进行架构设计时,整个系统要么选择 CP,要么选择 AP。
在实际设计过程中,每个系统不可能只处理一种数据,而是包含多种类型的数据:
架构设计时,从整个系统的角度去选择 CP 还是 AP,就会发现顾此失彼。所以在 CAP 理论落地实践时,需要将系统内的数据按照不同的应用场景和要求进行分类,每类数据选择不同的策略(CP 还是 AP),而不是直接限定整个系统所有数据都是同一策略。
CAP 理论中的 C
在实践中是不可能完美实现的,在数据复制的过程中,总是需要花费一定的时间(几毫秒到几十毫秒不等),节点 A 和节点 B 的数据并不一致。
技术上是无法做到在分布式场景下完美的一致性的,但是在条件严格的业务场景下,必须要求一致性(如银行账户存取钱、商品抢购库存更新等),因此在实际中会选择CA
,也就是单点写入,其他节点做备份,无法做到分布式情况下的多点写入。
例如,根据用户ID将用户的读写操作限定在某一个节点,这样单点故障只会影响部分用户,从整体上看依然是分布式架构。
CAP 理论中分布式系统只能选择
CP
或者AP
的前提是系统发生了“分区”现象。
正常运行情况下,系统中不存在网络分区,节点间的网络连接一起正常,此时应该同时保证C
和A
。因此,在架构设计的时候既要考虑分区发生时选择 CP
还是 AP
,也要考虑分区没有发生时如何保证 CA
。
CAP 理论中三者只能取两个,需要“牺牲”(sacrificed)另外一个,这里的“牺牲”是有一定误导作用的,因为“牺牲”让很多人理解成什么都不做。
实际上,CAP 理论的“牺牲”只是说在网络分区过程中无法保证 C
或者 A
,但并不意味着什么都不做。因为在系统整个运行周期中,大部分时间都是正常的,发生分区现象的时间并不长。分区期间放弃 C
或者 A
,并不意味着永远放弃 C
和 A
,可以在分区期间进行一些操作,从而让分区故障解决后,系统能够重新达到 CA
的状态。
最典型的做法就是在分区期间记录日志,当分区故障解决后,系统根据日志进行数据恢复,使得重新达到
CA
状态。
ACID 是数据库管理系统为了保证事务的正确性而提出来的一个理论,ACID 包含四个约束。
Durability(持久性):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
ACID 中的 A
(Atomicity)和 CAP 中的 A
(Availability)意义完全不同
ACID 中的 C
和 CAP 中的 C
名称虽然都是一致性,但含义也完全不一样
C
是指数据库的数据完整性C
是指分布式节点中的数据一致性ACID 的应用场景是数据库事务
CAP 关注的是分布式系统数据读写
BASE :
核心思想是即使无法做到强一致性(CAP 的一致性就是强一致性),但应用可以采用适合的方式达到最终一致性。
BASE 理论本质上是对 CAP 中 AP
方案的一个补充:
CP
场景是不存在的,即使是几毫秒的数据复制延迟,在这几毫秒时间间隔内,系统是不符合 CP
要求的。因此 CAP 中的 CP
方案,实际上也是实现了最终一致性,只是“一定时间”是指几毫秒。AP
方案中牺牲一致性只是指分区期间,而不是永远放弃一致性。这其实就是 BASE 理论延伸的地方,分区期间牺牲一致性,但分区故障恢复后,系统应该达到最终一致性。综上:
AP
方案的延伸FMEA(Failure mode and effects analysis,故障模式与影响分析)又称为失效模式与后果分析、失效模式与效应分析、故障模式与后果分析等,FMEA 是一种在各行各业都有广泛应用的可用性分析方法,通过对系统范围内潜在的故障模式加以分析,并按照严重程度进行分类,以确定失效对于系统的最终影响。
在架构设计领域,FMEA 的具体分析方法是:
FMEA 分析的方法是分析表,常见的 FMEA 分析表格包含下面部分。
注意这里的“功能点”指的是从用户角度来看的,而不是从系统各个模块功能点划分来看的。
系统会出现什么样的故障,包括故障点和故障形式。
注意,这里的故障模式并不需要给出真正的故障原因,只需要假设出现某种故障现象即可,例如 MySQL 响应时间达到 3 秒。造成这个现象可能的原因很多:磁盘坏道、慢查询、服务器到 MySQL 的连接网络故障、MySQL bug 等,不需要在故障模式中一一列出来,而是在“故障原因”一节中列出来。
因为在实际应用过程中,不管哪种原因,只要现象是一样的,对业务的影响就是一样的。
此外,故障模式的描述要尽量精确,多使用量化描述,避免使用泛化的描述。例如,推荐使用“MySQL 响应时间达到 3 秒”,而不是“MySQL 响应慢”。
当发生故障模式中描述的故障时,功能点具体会受到什么影响。常见的影响有:
故障影响需要准确描述。例如,推荐使用“20% 的用户无法登录”,而不是“大部分用户无法登录”。要注意这里的数字不需要完全精确,比如 21.25% 这样的数据其实是没有必要的,只需要预估影响是 20% 还是 40%。
站在业务角度故障的影响程度,一般分为“致命 / 高 / 中 / 低 / 无”五个档次。
严重程度按照这个公式进行评估:严重程度 = 功能点重要程度 × 故障影响范围 × 功能点受损程度
。
以用户管理系统为例:登录功能比修改用户资料要重要得多,80% 的用户比 20% 的用户范围更大,完全无法登录比登录缓慢要更严重,得出如下故障模式的严重程度。
- 致命:超过 70% 用户无法登录。
- 高:超过 30% 的用户无法登录。
- 中:所有用户登录时间超过 5 秒。
- 低:10% 的用户登录时间超过 5 秒。
- 中:所有用户都无法修改资料。
- 低:20% 的用户无法修改头像。
对于某个故障的影响到底属于哪个档次,有时会出现一些争议。
“故障模式”中只描述了故障的现象,不管什么故障原因,故障现象相同,对功能点的影响就相同。
单独将故障原因列出来是原因:
指某个具体故障原因发生的概率。例如,磁盘坏道的概率、MySQL bug 的概率、没有索引的概率。
一般分为“高 / 中 / 低”三档即可,具体评估时有以下几点要重点关注。
高中低是相对的,只是为了确定优先级以决定后续的资源投入,没有必要绝对量化,因为绝对量化是需要成本的,而且很多时候都没法量化。
综合严重程度和故障概率判断某个故障的最终等级,风险程度 = 严重程度 × 故障概率
。可能出现某个故障影响非常严重,但其概率很低,最终来看风险程度就低。
“某个机房业务瘫痪”对业务影响是致命的:
同样的故障影响,不同的故障原因有不同的概率,最终得到的风险级别就是不同的。
针对具体的故障原因,系统现在是否提供了某些措施来应对,包括:检测告警、容错、自恢复等。
为了降低故障发生概率而做的一些事情,可以是技术手段,也可以是管理手段。
为了能够解决问题而做的一些事情,一般都是技术手段。例如:
如果某个故障既可以采取规避措施,又可以采取解决措施,那么优先选择解决措施。
如果问题是系统自己无法解决的,例如磁盘坏道、开源系统 bug,这类故障只能采取规避措施;系统能够自己解决的故障,大部分是和系统本身功能相关的。
综合前面的分析:
这些规划既可以是技术手段,也可以是管理手段;可以是规避措施,也可以是解决措施。需要考虑资源的投入情况,优先将风险程度高的系统隐患解决。
例如: