Open AlexiaChen opened 4 years ago
CAP定理是由UC Berkeley的Brewer教授在2000年的ACM PODC上提出的一个猜想,然后由MIT的某两个教授继续完善并证明了这个猜想,这样就让这个猜想成为了分布式领域的一个定理。如果你要设计或者是搭建一个分布式系统,那么CAP理论你需要懂一些的。
CAP猜想被提出来的时候,并没有详细定义Consistency,Availability,Partition Tolerance三个概念的明确含义,之后才有人慢慢完善概念,所以你去网络上搜索,解释在细节上还是有3一点点差别的。
下面会摘录国外一位大神对CAP理论的渐进式理解,他也是慢慢来的。
简单粗暴版本:
科学严谨版本:
这两个版本有点差异,主要有两点:
严谨版本定义了什么才是CAP理论探讨的对象,强调了互相连接和共享数据的分布式系统,为什么要强调这两点? 因为并不是所有的分布式系统都会互联和共享数据。最简单的就是memcached集群,互相之间就没有连接和共享数据,因此这样类似的分布式集群不符合CAP理论的讨论对象。MySQL集群就是互联和共享数据的(进行数据复制),这才是CAP理论的讨论对象。
强调了读写操作,CAP关注的是对数据的读写操作,而不是分布式系统的所有功能。Zookeeper的选举机制就不是CAP理论的讨论对象。选举机制是分布式系统里面的共识部分。你见过由讨论共识算法的人把CAP搬出来吗?
下面单独分开讲解CAP的3个设计约束。
科学严谨版本:
两个版本差异有以下2点:
粗暴版本从节点的角度描述,严谨版本从客户端的角度,显然科学版更符合我们评估系统的方式,客户端是从调用者用户的角度考虑的。
粗暴版本从常理上违背物理规律,强调同一时刻拥有相同的数据,太不现实了,网络还有时间延迟呢,在某个时间窗口内,数据肯定是不一致的。在事务的执行过程中,系统其实处于一个不一致的状态,不同的节点数据并不完全一致,因为在执行过程中,客户端是无法读取到未提交(commit)的数据的,只有等待事务commited后,客户端才能读取到事务写入的数据,而事务失败则会进行回滚,客户端也不会读取到事务中间写入的数据。
两个版本差异:
粗暴版本太扯了,每个请求不严谨,现实是只有非故障节点才能满足可用性的要求,如果节点故障了,请求不一定能得到一个响应。
这个响应不能是网络超时,超时就说明你请求可能都没跟那个节点搭上话,还有不能出错,出错请求结果就没意义了。合理的不正确结果不代表是错误。这个要搞清楚。
差异:
简单版只说了运行,可是现实是,继续运行的系统不一定能正常提供业务服务,可能它只是仅仅在运行罢了,实际已经在错误的路上越走越远了。所以科学版强调了系统能够继续提供业务服务。
简单版直接说明了消息丢失(丢包,因为网络本身是不可靠的)出现了网络分区,但是消息丢失只是网络故障的千千万万的原因之一。科学版直接说明网络分区现象,不管网络发生啥故障,拥塞也好,丢包也好,物理网线或者网络设备断了也好,统统都算里面,只要分布式系统内的网络发生分区,我就能提供服务。(注意,这里讨论的网络分区是有范围的,范围不能延伸到分布式系统所在网络的外部网络)
感觉上CAP理论定义是三个约束中只能取两个是不是?然而,这是一种误导,不是任意3选2,因为放到物理现实中来看,对于分布式系统而言,你会发现不得不选择P(分区容忍性),因为网络本身无法做到100%可靠,网络分区是个必然现象。所以讨论CAP理论的时候,满足P是个大前提,接下来你要做的就是在C和A中选择一个了。分布式系统理论上是不可能选择CA架构的,只能要么是CP,要不是AP。
考虑一个分布式系统中两个节点A和B,为了保证强一致性,当发生分区现象后,节点A的数据已经更新了(状态x -> 状态y),但是由于节点A和B之间的网络发生故障了,节点A的状态y无法同步复制到节点B,节点B的状态还是x。这时候客户端访问节点B,节点B需要返回Error(因为系统要求强一致,所以必须按系统错误处理),提示客户端“系统现在发生了错误”,这种处理方式违背了CAP中的A的要求,分区发生时,系统居然返回Error,A的要求是要返回合理的结果,结果可以不正确,但是不能是系统错误或超时!!。因此CAP三者只能满足CP。
考虑一个分布式系统中两个节点A和B,为了保证可用性,当发生分区现象后,节点A的数据已经更新了(状态x -> 状态y),但是由于节点A和B之间的网络发生故障了,节点A的状态y无法同步复制到节点B,节点B的状态还是x。这时候客户端访问节点B,节点B将当前拥有的状态数据x返回给客户端了(因为不要求强一致了,可以放宽要求),而实际上当前最新的状态数据已经是y了,这就不满足一致性中的强一致的要求(无论是简单版本还是严谨版本的解释)。因此CAP三者中只能满足AP。
CAP理论的优点在于清晰简洁,易于理解,但是缺点就是过于理论化,缺乏工程上的实践指导意义,如果你需要在实践中应用CAP理论,你没注意到有些关键细节的话,你可能会发现方案难以落地。
每个分布式系统不可能单单只处理一种类型的数据,有的数据策略需要选择CP,有的数据需要选择AP,所以做设计的时候不要从整个系统的角度去选择CP还是AP,不然会发现顾此失彼,无论怎么做都有问题。
至于选择CP还是AP,看你对某种类型的数据的一致性要求了,有些数据真不需要保证强一致性。比如微信头像,即使我最新的头像换成刘亦菲了,其他人还是可以看到我之前的旧头像的。没必要见到最新头像,这种数据还是选择AP吧,至于C我不保证强一致,但我保证最终一致性嘛(最终一致性就不是C了)。
所以总结一句,看数据类型,看场景。
这个是隐藏的比较深的假设,即使C强调的是强一致,但是在物理世界中,网络传输是有时延的,事务提交完成后,数据并不能瞬间复制到所有节点,至于时延多少,这个取决于网络布局了,是否在同一个机房,是否在同一个城市,是否在不同的城市,这个时延多少,如果是架构师,确实要做到心中有个数,Jeff Dean写过相关的文章我总结了发出来了,在这里https://github.com/AlexiaChen/AlexiaChen.github.io/issues/76。
另外,不要小看这毫秒级别的时延,这个微小的时间窗口内的不一致,对于一些金融支付相关的的业务场景下是致命的。那就意味着这样的场景不能分布式架构了吗?就完蛋了吗? 显然不是的,既然业务上要求强一致,但是现实情况又无法达到那样100%的强一致,大不了就不考虑P了呗,只选择CA,不把网络考虑进来了,我单节点写入,其他节点只做数据备份,不做多节点写入。
咦?好奇的人说,那这样算个毛分布式架构啊? 其实动点脑子还是可以的,如果要多节点写入,那么这几个节点需要各自负责一段指定范围的数据,比如节点A负责处理用户ID为0到100的数据并备份101-200的数据,节点B负责用户ID为101-200的数据并备份0-100的用户数据。但这样的设计有个缺陷就是某个节点故障了,这个节点上的用户就无法进行读写操作了,业务不能进行。但是站在整体系统来看,这种设计降低了影响范围。这就是为什么在挖掘机挖断光缆以后,支付宝只有一部分用户出现业务异常,而不是所有的用户。你看,连阿里也无法完美解决这样的问题。
标题什么意思呢?P是指,当网络分区现象发生时,系统能继续提供业务服务。但是你仔细想想,分区现象不是一直都在发生的好吗,分区虽说是个必然现象,但是往往发生时间并不长。也就是说,大部分时间下,大部分正常情况下(废话,如果网络分区一年时间有11个月的时间都发生问题,那么网络直接不可用了,咱啥也别接着讨论了),网络分区没有发生的,就意味着这个时候,系统可以不用考虑P,可以只考虑CA。所以设计分布式架构就有以下逻辑:
// 分布式系统不停的run while(true) { if(网络分区发生 == true) { // 设计者根据业务场景决策 if(数据是否需要保证一致性 == true) { CP(); } else { AP(); } } else { CA(); } }
上面的逻辑简而言之就是,分布式系统既要考虑分区发生时选择CP还是AP,也要考虑分区没发生时如何保证CA。
放弃这个词在原始的CAP理论里面是有误导意义的,确实有些时候我感觉也是莫名其妙。在分区过程中,无法同时保证C和A,并不意味咱啥也不管,因为系统在整个运行周期中,大部分时间里分区是没有发生的,发生分区现象的时间并不长,例如99.99%可用性(俗称4个9)的系统,一年运行下来,不可用的时间只有50分钟,如果是5个9的高可用系统,一年运行下来,不可用的时间只有5分钟。分区期间放弃C或A,并不意味着永远放弃C和A,现实情况下,可以在分区期间进行一些操作,从而让分区现象消失或者解决后,系统能够重新达到CA的状态。
当然,这种操作一般是写本地日志了,当分区发生后写日志,当分区故障恢复后,把本地日志里面的记录再通过网络同步过去,这样又回到CA状态了。
当谈到数据一致性的时候,CAP,ACID,BASE难免都会被拿出来比较,原因在于这三者都和数据一致性多少有点关系,如果不仔细研究,可能会陷入一头雾水的状态。
ACID是数据库管理系统(DBMS)为了保证事务的正确性而提出来的一个理论,所以谈到ACID,我们一般是指数据库事务(transaction)。
这个概念跟内存上的原子操作概念差不多,对于一个事务,要不啥也没干,要不就全干完。发生错误就回滚。回滚是实现原子性的一个手段。
事务开始(begin)之前和事务结束(commit)之后, 数据库的完整性没被破坏。
数据库允许多个并发事务同时对数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉而导致的数据不一致。事务隔离由不同级别: 读未提交(read uncommitted), 读提交(read committed), 可重复读(repeatable read) 和串行化(Serializable)。每个级别对应不同的性能,当然,串行化性能是最低的,相当于没有事务并发,都得排队。
事务结束后(committed),对数据的修改就是永久的,即便系统故障也不会丢失。
所以,你看到了,ACID中的A和CAP中的A根本不是一个概念,C虽然都是说一致性,但是含义也不一样,ACID中的C是指数据库的数据完整性(可能通过Write-ahead log实现),CAP中的C是指分布式节点中的数据或状态的一致性。再所以,ACID跟CAP完全没有可比性,不是同一层面的概念。
其实你也看到了,CAP太理想,太理论了,对于设计分布式系统的工程实践指导意义不大,C是100%强一致,A是100%可用性,都是完美100%。实际上哪有这样的系统,不存在的,要对现实做妥协,所以BASE理论作为CAP的外延补充,应运而生了。它是CA权衡的结果,更是工程实践的总结。
BASE中的BA是Basically Available(基本可用)的缩写,S是Soft state(软状态),E是Eventally Consistency(最终一致性)。其核心思想就是如果你的系统无法做到强一致性,那么系统结合自身的业务特点可以采用合适的方式达到最终一致性。
分布式系统在出现故障时,允许损失部分可用性,即保证核心可用。至于哪些业务可以损失可用性,哪些不可以,这个要看架构师怎样权衡了。这里可以根据一个简单的原则就是,越跟金钱相关的业务,越不能损失可用性,越不相关的的业务一般就没啥大不了的。
允许系统存在中间状态,而该中间状态不会影响系统整体可用性。这里的中间状态就是CAP理论中的数据不一致的这段时间窗口的状态。
系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。
放宽了CAP中的C强一致的追求。给予了一定时间内的缓冲期,但是这个一定时间是根据数据类型不同而不同的。有些时间可以长些,有些时间不能太长。
100%完美的CP,CA场景都是不存在的,因为有时延。AP方案中牺牲C,那只是分区期间,而不是永远放弃一致性,等到分区恢复后,系统应该通过某种手段达到最终一致性。
当然,由于业界的发展,现在很多系统已经开始选择CP了,因为A虽然达不到100%可用性,但是可以让A达到一定程度的高可用(HA),所以选择CP的系统,不代表没有可用性啊,这点要明白。
分布式理论----CAP
CAP理论
CAP定理是由UC Berkeley的Brewer教授在2000年的ACM PODC上提出的一个猜想,然后由MIT的某两个教授继续完善并证明了这个猜想,这样就让这个猜想成为了分布式领域的一个定理。如果你要设计或者是搭建一个分布式系统,那么CAP理论你需要懂一些的。
CAP猜想被提出来的时候,并没有详细定义Consistency,Availability,Partition Tolerance三个概念的明确含义,之后才有人慢慢完善概念,所以你去网络上搜索,解释在细节上还是有3一点点差别的。
下面会摘录国外一位大神对CAP理论的渐进式理解,他也是慢慢来的。
简单粗暴版本:
科学严谨版本:
这两个版本有点差异,主要有两点:
严谨版本定义了什么才是CAP理论探讨的对象,强调了互相连接和共享数据的分布式系统,为什么要强调这两点? 因为并不是所有的分布式系统都会互联和共享数据。最简单的就是memcached集群,互相之间就没有连接和共享数据,因此这样类似的分布式集群不符合CAP理论的讨论对象。MySQL集群就是互联和共享数据的(进行数据复制),这才是CAP理论的讨论对象。
强调了读写操作,CAP关注的是对数据的读写操作,而不是分布式系统的所有功能。Zookeeper的选举机制就不是CAP理论的讨论对象。选举机制是分布式系统里面的共识部分。你见过由讨论共识算法的人把CAP搬出来吗?
下面单独分开讲解CAP的3个设计约束。
一致性(Consistency)
简单粗暴版本:
科学严谨版本:
两个版本差异有以下2点:
粗暴版本从节点的角度描述,严谨版本从客户端的角度,显然科学版更符合我们评估系统的方式,客户端是从调用者用户的角度考虑的。
粗暴版本从常理上违背物理规律,强调同一时刻拥有相同的数据,太不现实了,网络还有时间延迟呢,在某个时间窗口内,数据肯定是不一致的。在事务的执行过程中,系统其实处于一个不一致的状态,不同的节点数据并不完全一致,因为在执行过程中,客户端是无法读取到未提交(commit)的数据的,只有等待事务commited后,客户端才能读取到事务写入的数据,而事务失败则会进行回滚,客户端也不会读取到事务中间写入的数据。
可用性
简单粗暴版本:
科学严谨版本:
两个版本差异:
粗暴版本太扯了,每个请求不严谨,现实是只有非故障节点才能满足可用性的要求,如果节点故障了,请求不一定能得到一个响应。
这个响应不能是网络超时,超时就说明你请求可能都没跟那个节点搭上话,还有不能出错,出错请求结果就没意义了。合理的不正确结果不代表是错误。这个要搞清楚。
分区容忍性
简单粗暴版本:
科学严谨版本:
差异:
简单版只说了运行,可是现实是,继续运行的系统不一定能正常提供业务服务,可能它只是仅仅在运行罢了,实际已经在错误的路上越走越远了。所以科学版强调了系统能够继续提供业务服务。
简单版直接说明了消息丢失(丢包,因为网络本身是不可靠的)出现了网络分区,但是消息丢失只是网络故障的千千万万的原因之一。科学版直接说明网络分区现象,不管网络发生啥故障,拥塞也好,丢包也好,物理网线或者网络设备断了也好,统统都算里面,只要分布式系统内的网络发生分区,我就能提供服务。(注意,这里讨论的网络分区是有范围的,范围不能延伸到分布式系统所在网络的外部网络)
CAP理论的应用
感觉上CAP理论定义是三个约束中只能取两个是不是?然而,这是一种误导,不是任意3选2,因为放到物理现实中来看,对于分布式系统而言,你会发现不得不选择P(分区容忍性),因为网络本身无法做到100%可靠,网络分区是个必然现象。所以讨论CAP理论的时候,满足P是个大前提,接下来你要做的就是在C和A中选择一个了。分布式系统理论上是不可能选择CA架构的,只能要么是CP,要不是AP。
CP系统
考虑一个分布式系统中两个节点A和B,为了保证强一致性,当发生分区现象后,节点A的数据已经更新了(状态x -> 状态y),但是由于节点A和B之间的网络发生故障了,节点A的状态y无法同步复制到节点B,节点B的状态还是x。这时候客户端访问节点B,节点B需要返回Error(因为系统要求强一致,所以必须按系统错误处理),提示客户端“系统现在发生了错误”,这种处理方式违背了CAP中的A的要求,分区发生时,系统居然返回Error,A的要求是要返回合理的结果,结果可以不正确,但是不能是系统错误或超时!!。因此CAP三者只能满足CP。
AP系统
考虑一个分布式系统中两个节点A和B,为了保证可用性,当发生分区现象后,节点A的数据已经更新了(状态x -> 状态y),但是由于节点A和B之间的网络发生故障了,节点A的状态y无法同步复制到节点B,节点B的状态还是x。这时候客户端访问节点B,节点B将当前拥有的状态数据x返回给客户端了(因为不要求强一致了,可以放宽要求),而实际上当前最新的状态数据已经是y了,这就不满足一致性中的强一致的要求(无论是简单版本还是严谨版本的解释)。因此CAP三者中只能满足AP。
CAP理论的相关细节
CAP理论的优点在于清晰简洁,易于理解,但是缺点就是过于理论化,缺乏工程上的实践指导意义,如果你需要在实践中应用CAP理论,你没注意到有些关键细节的话,你可能会发现方案难以落地。
每个分布式系统不可能单单只处理一种类型的数据,有的数据策略需要选择CP,有的数据需要选择AP,所以做设计的时候不要从整个系统的角度去选择CP还是AP,不然会发现顾此失彼,无论怎么做都有问题。
至于选择CP还是AP,看你对某种类型的数据的一致性要求了,有些数据真不需要保证强一致性。比如微信头像,即使我最新的头像换成刘亦菲了,其他人还是可以看到我之前的旧头像的。没必要见到最新头像,这种数据还是选择AP吧,至于C我不保证强一致,但我保证最终一致性嘛(最终一致性就不是C了)。
所以总结一句,看数据类型,看场景。
这个是隐藏的比较深的假设,即使C强调的是强一致,但是在物理世界中,网络传输是有时延的,事务提交完成后,数据并不能瞬间复制到所有节点,至于时延多少,这个取决于网络布局了,是否在同一个机房,是否在同一个城市,是否在不同的城市,这个时延多少,如果是架构师,确实要做到心中有个数,Jeff Dean写过相关的文章我总结了发出来了,在这里https://github.com/AlexiaChen/AlexiaChen.github.io/issues/76。
另外,不要小看这毫秒级别的时延,这个微小的时间窗口内的不一致,对于一些金融支付相关的的业务场景下是致命的。那就意味着这样的场景不能分布式架构了吗?就完蛋了吗? 显然不是的,既然业务上要求强一致,但是现实情况又无法达到那样100%的强一致,大不了就不考虑P了呗,只选择CA,不把网络考虑进来了,我单节点写入,其他节点只做数据备份,不做多节点写入。
咦?好奇的人说,那这样算个毛分布式架构啊? 其实动点脑子还是可以的,如果要多节点写入,那么这几个节点需要各自负责一段指定范围的数据,比如节点A负责处理用户ID为0到100的数据并备份101-200的数据,节点B负责用户ID为101-200的数据并备份0-100的用户数据。但这样的设计有个缺陷就是某个节点故障了,这个节点上的用户就无法进行读写操作了,业务不能进行。但是站在整体系统来看,这种设计降低了影响范围。这就是为什么在挖掘机挖断光缆以后,支付宝只有一部分用户出现业务异常,而不是所有的用户。你看,连阿里也无法完美解决这样的问题。
标题什么意思呢?P是指,当网络分区现象发生时,系统能继续提供业务服务。但是你仔细想想,分区现象不是一直都在发生的好吗,分区虽说是个必然现象,但是往往发生时间并不长。也就是说,大部分时间下,大部分正常情况下(废话,如果网络分区一年时间有11个月的时间都发生问题,那么网络直接不可用了,咱啥也别接着讨论了),网络分区没有发生的,就意味着这个时候,系统可以不用考虑P,可以只考虑CA。所以设计分布式架构就有以下逻辑:
上面的逻辑简而言之就是,分布式系统既要考虑分区发生时选择CP还是AP,也要考虑分区没发生时如何保证CA。
放弃这个词在原始的CAP理论里面是有误导意义的,确实有些时候我感觉也是莫名其妙。在分区过程中,无法同时保证C和A,并不意味咱啥也不管,因为系统在整个运行周期中,大部分时间里分区是没有发生的,发生分区现象的时间并不长,例如99.99%可用性(俗称4个9)的系统,一年运行下来,不可用的时间只有50分钟,如果是5个9的高可用系统,一年运行下来,不可用的时间只有5分钟。分区期间放弃C或A,并不意味着永远放弃C和A,现实情况下,可以在分区期间进行一些操作,从而让分区现象消失或者解决后,系统能够重新达到CA的状态。
当然,这种操作一般是写本地日志了,当分区发生后写日志,当分区故障恢复后,把本地日志里面的记录再通过网络同步过去,这样又回到CA状态了。
ACID与BASE
当谈到数据一致性的时候,CAP,ACID,BASE难免都会被拿出来比较,原因在于这三者都和数据一致性多少有点关系,如果不仔细研究,可能会陷入一头雾水的状态。
ACID
ACID是数据库管理系统(DBMS)为了保证事务的正确性而提出来的一个理论,所以谈到ACID,我们一般是指数据库事务(transaction)。
这个概念跟内存上的原子操作概念差不多,对于一个事务,要不啥也没干,要不就全干完。发生错误就回滚。回滚是实现原子性的一个手段。
事务开始(begin)之前和事务结束(commit)之后, 数据库的完整性没被破坏。
数据库允许多个并发事务同时对数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉而导致的数据不一致。事务隔离由不同级别: 读未提交(read uncommitted), 读提交(read committed), 可重复读(repeatable read) 和串行化(Serializable)。每个级别对应不同的性能,当然,串行化性能是最低的,相当于没有事务并发,都得排队。
事务结束后(committed),对数据的修改就是永久的,即便系统故障也不会丢失。
所以,你看到了,ACID中的A和CAP中的A根本不是一个概念,C虽然都是说一致性,但是含义也不一样,ACID中的C是指数据库的数据完整性(可能通过Write-ahead log实现),CAP中的C是指分布式节点中的数据或状态的一致性。再所以,ACID跟CAP完全没有可比性,不是同一层面的概念。
BASE
其实你也看到了,CAP太理想,太理论了,对于设计分布式系统的工程实践指导意义不大,C是100%强一致,A是100%可用性,都是完美100%。实际上哪有这样的系统,不存在的,要对现实做妥协,所以BASE理论作为CAP的外延补充,应运而生了。它是CA权衡的结果,更是工程实践的总结。
BASE中的BA是Basically Available(基本可用)的缩写,S是Soft state(软状态),E是Eventally Consistency(最终一致性)。其核心思想就是如果你的系统无法做到强一致性,那么系统结合自身的业务特点可以采用合适的方式达到最终一致性。
分布式系统在出现故障时,允许损失部分可用性,即保证核心可用。至于哪些业务可以损失可用性,哪些不可以,这个要看架构师怎样权衡了。这里可以根据一个简单的原则就是,越跟金钱相关的业务,越不能损失可用性,越不相关的的业务一般就没啥大不了的。
允许系统存在中间状态,而该中间状态不会影响系统整体可用性。这里的中间状态就是CAP理论中的数据不一致的这段时间窗口的状态。
系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。
放宽了CAP中的C强一致的追求。给予了一定时间内的缓冲期,但是这个一定时间是根据数据类型不同而不同的。有些时间可以长些,有些时间不能太长。
100%完美的CP,CA场景都是不存在的,因为有时延。AP方案中牺牲C,那只是分区期间,而不是永远放弃一致性,等到分区恢复后,系统应该通过某种手段达到最终一致性。
当然,由于业界的发展,现在很多系统已经开始选择CP了,因为A虽然达不到100%可用性,但是可以让A达到一定程度的高可用(HA),所以选择CP的系统,不代表没有可用性啊,这点要明白。