分布式基础理论之CAP 和BASE
前言
本文聊聊 CAP 定理和 BASE 理论。
CAP 定理
C:一致性(Consistency)
- 数据的强一致性。希望分布式系统只读到最新写入的数据
A:可用性(Availability)
- 分布式系统能提供服务就行,数据的不一致可以忍受
P:分区容错性(Partition tolerance)
- 分布式系统下,多个节点之间无法通信的情况下,节点被隔离,产生了网络分区,整个系统仍然是可以工作的。
CAP定理: 在网络节点之间无法通信的情况下, 和数据复制相关的功能, 要么选择可用性(A) , 要么选择一致性(C), 不能同时选择两者。
分布式系统下,如果产生了网络分区(P),此时P已经客观存在。那么我们需要在可用性A和强一致性C中做出取舍。分区A和分区B,不能通信,一方的数据无法同步给另一方,我们是选择不忍受数据的强一致性,不提供服务(保C不留A)。还是忍受分区A和分区B 的数据不一致(报A不留C),给客户端提供可用服务呢。
那么如果没有网络分区的情况下,节点之间通信正常。此时P是不存在的。那么我们是可以满足AC的。需要注意的是,不存在网络分区P,我们在分布式系统中满足AC也是对架构有很高要求的。
换言之就是分布式系统下C、A、P不可能同时满足。最多满足两者。
在互联网项目中根据需求进行取舍。通常选择AP 保证最终一致性。但如果银行可能需要强一致性做保障。
BASE 理论
BASE理论是CAP理论不能同时满足的折中理论。看它是四个字母,其实讲的是三个属性, 而每个属性其实包含着两个单词。
Basically Available: 基本可用
我们试想一个两个人对同一个账户进行取钱的操作,这个两个操作被分配到两个机器上。此时这两个机器产生了网络分区,那么如果我们提供可用性的话,两个人都从账户上取出了钱,但如果他们取出的钱超出了账户余额,那么对于银行来说,取钱是一件很麻烦的事情。
上面说到银行通常选择强一致性,那么就放弃可用性了吗。我们可以提供一个基本可用的折中方案,让集群中拥有较多机器的部分继续提供服务,另一部分不接受任何请求,等待网络分区结束后再恢复服务。他们没有利用整个集群的能力,只是利用了其中的一部分。也就是说让一个人能够取出钱来?
为什么说是基本可用(Basically Available)而不是说部分可用(Partially Available)呢?
因为在部分可用的时候,系统不一定是可用的。
Soft State:软状态
软状态在网络分区发生的时候,允许集群中的不同部分都能被访问到。并且,这个时候他们对集群中不同节点的不同操作可能会有不同的状态。如果业务上认为这种不一致是能够容忍的,那么软状态的存在就没有问题,比如说文章的点击数,不是很重要的业务,对用户阻碍也不大。
Eventually Consistency: 最终一致性
如果在网络分区结束后,集群能够自动解决两个部分之间的冲突,让集群达成一致性的状态,那么我们可以说这个集群实现了最终一致性。
这个时候集群没有了网络分区P,重回了拥有CA属性的状态。
补充问题
单机没有P 那么就有CA吗
单节点的系统没有网络分区,那么就自带CA属性吗?
C一致性是客观存在的,如果不考虑并发造成的数据不一致。但是单节点是不拥有可用性A 的,想想为何后来的架构发展为何会走向分布式 微服务。单节点通常在访问量增大时,处理能力有限。可用性就很差了,宕机后,系统直接是不可用的。不能规避单点故障。
不存在网络分区P的情况下 如何更好的满足CA呢
前面讲到在没有网络分区P的情况下,我们想要满足CA也是需要一定难度的。
主动-被动复制模式
在不同的位置持有副本,但是只允许对于其中一个位置的状态做修改。
这种模式可以理解为一主多从,主节点负责写,从节点负责读。由主节点将写操作同步给从节点。
- 集群的成员能够发现和枚举所有副本位置
- 集群保证始终只有一个主节点在允许
- 主节点接收写处理,将处理进行广播,并且当所有从节点应答成功后,主节点应答客户端成功
- 多个备份节点,防止数据丢失
此时如果主节点如果挂了,系统能够能够进行故障处理。
- 如何保证只有一个主节点?
- 主节点挂了,如何进行切换(故障自动转移)?
- 怎样才算是负责成功?
- 从节点 状态滞后怎么办?
- 发生分区时如何选C和A?
当主动副本接收到更新请求的时候,它会接受请求,并将其广播到被动副本,并在得到肯定回复的时候,应答客户端。那么,我们要考虑的是,要等到多少个被动副本应答之后,才算复制成功了?要求更多的副本应答,能显著减少数据丢失的可能性,并保障系统的一致性;要求更少的副本应答,能显著减少副本响应缓慢带来的延迟,提高可用性。但是这个数目是确定的,假设其为 N。也就是说,必须要有 N 个副本应答之后,系统才能接受更新成功。否则主动副本会一直等待集群确认,直到超时。
基本可用也就是要表达这个意思:你的可用分区必须至少要有 N 个副本,才能达成对更新的确认。少于 N 个的话,要么想办法减少 N 的值,要么想办法添加新的副本进去。否则,系统连基本可用都无法保障。
主动-被动复制模式的优点在于,当系统运行良好的时候,这种模式的性能会非常好,因为主动副本不需要执行协调任务;所有的读请求都能在不需要额外通信的情况下得到响应;写入请求只需要多数副本确认即可。
而当系统有失败发生的时候,它将有两种不同情形的性能退化。如果被动副本节点失败重启,那么为了跟上最新的状态,它将占用较多的流量来请求大部分更新;如果主动副本节点失败的话,就会有一段时间内没有任何主动副本在运行。此时集群需要花费时间来确认节点已经失效,并且需要在集群中选出新的主动副本,来协调整体的更新。而在这段时间内,系统的表现是不可用的。
多主复制模式
在不同的位置上保持服务的多个副本,每处都可以接受修改,并将所有修改在各个副本之间传播。
可以通过多种方法来实现多主复制模式,他们之间的不同则在于发生网络分区的时候,如何处理更新请求。我们这里主要说一下基于共识的复制。
基于共识的复制是最具一致性的方式,它通过对每个更新都确立共识的方式实现,代价则是网络分区的时候,不能处理任何请求(保有 CP)。
多主复制模式意味着每一个副本都能接收到更新请求,并能在集群内部对该请求提起共识请求。任何流入的更新都以编号的形式写入到一个虚拟的账本(复制日志)中,并且每个节点都要对哪个更新在哪一行取得共识,这样每个副本都能根据这个账本达到当前状态。这样就在集群内部实现了一致性。而这里的共识操作,我们可以使用 Raft 算法来达成。
多主复制模式其实还有如下两种方案:
具有冲突检测与处理方案的复制: 在网络分区的时候,接受可能会发生冲突的更新请求。而这些冲突可以在分区结束之后解决。只是可能会要丢弃部分已经被确认过的更新。
限定数据模型,使用无冲突的可复制数据结构。这样的并发更新天生就是无冲突的,只需要在集群中进行复制即可。
另外还有主动-主动复制模式,它的策略是在不同的地方持有服务的多份副本,并在所有副本上执行所有的修改操作。这样,每个副本都会执行相同的更新操作,它们之间的状态是保持一致的。只是我们需要一个协调者来负责将更新请求发送给每一个副本。
我们可以发现保证系统高可用一个重要因素就是冗余 避免单点故障。并且能完成故障自动转移。
References
- 公众号:写Scala的老王