专栏文章 混沌工程理论知识记录 -- CAP

孙高飞 · 2022年04月25日 · 最后由 Nick 回复于 2022年05月14日 · 5609 次阅读
本帖已被设为精华帖!

前言

前一阵子一直在做 K8S 监控平台的东西,最近开始开发混沌工程平台并且着手组织相关的测试项目。 而正好我写书也写到了混沌工程这一章了, 所以我决定梳理一下一些知识点,免得自己忘记了。

为什么要写 CAP

其实不仅仅是要写 cap, 我写的书中还会详细介绍在 K8S 中常用的高可用设计方法。我一直以来主张的都是要先测试一个东西那就要先了解它。就像我们测试一个产品功能之前肯定要对这个产品的功能十分的熟悉才行。同理的如果我们接到了高可用测试的需求,那就一定要先熟悉系统的高可用设计,这样才能针对性的对齐测试目标和设计测试场景。

什么是 CAP

我十分不擅长对事物进行总结定义,就像我在写标题的时候纠结了好一会到底应该叫混沌工程还是叫高可用测试。当初互联网这些造新词的大佬们真的是给我出了难题。索性我就不把网络上针对 CAP 的定义 copy 过来了,我就按自己的理解随便说说吧。我理解 CAP 是一致性,可用性和分区容错性三个词的英文缩写,讲的是针对一个分布式系统在高可用设计上最重要的三种能力。并且这三种能力无法同时满足,在故障出现时必定要牺牲一项。

一致性(Consistency)

这里说的一致性是数据的一致性,一般分布式系统的数据都会备份在多台机器上。一致性是要保证用户在读取数据的时候,返回的应该是这个数据最新的结果。当然在分布式事务中未提交的数据不算是最新的数据。 那什么时候情况下会造成不一致的现象呢, 比如在 mysql 的 master/slave 架构下(异步复制方案),所有的写请求必须发送给 master 节点,而读请求可以发送给 master 和 slave 的任意一个节点。数据写入 master 节点后会有机制把数据同步到 slave 节点上。但是这个同步是有延迟的,所以就会有数据一致性的风险。 在平时这个延迟会非常小,可能是毫秒级别的。但是如果 master 到 slave 的网络出现了故障,那么这个不一致会越来越大。

可用性(Availability)

在部分节点发生故障时,非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)。翻译成大白话就是即便有部分节点 down 机,系统依然可以对外提供服务。这个比较容易理解,服务实例部署,当有部分实例故障时,流量可以切换到健康的服务上继续为用户提供服务。

分区容错性(Partition Tolerance)

这个应该是最难理解的,我个人理解网络分区指由于某些故障,造成系统中某些节点互相无法访问,从而在系统中分裂出了若干独立的组。比如还是拿 mysql 的 master/slave 架构来说(异步复制方案),由于某些故障导致 master 节点到 slave 节点的网络中断了,但是仅仅是 master 节点到 slave 节点的网络出现了问题,用户还是可以正常访问两个节点的。这就出现了网络分区,也就是说这些节点都是正常可以服务的,但是这些节点之间的通信出现了问题。 分区容错性的意思就是在系统中出现了网络分区时,要求系统仍然能够继续履行职责。

三选二

CAP 的理论是这三种能力无法同时满足,我们必须至少牺牲一项。但实际上我们只能牺牲 C 或者 A。在出现网络分区的情况下如果你想要保证 C 的话,那就只能禁止系统的写请求了,因为你的数据无法同步到其他节点上,一定会造成数据不一致的情况。 但如果你禁止写请求就违背了 A。 实际上我们只有 CP 和 AP 两个选择,CA 是做不到的。

AP

在上面 mysql 这个案例中(异步复制方案)就属于 AP 的方案,一旦出现了网络分区问题,会造成数据的不一致,因为 master 节点的数据无法同步到 slave 节点上了,这导致两边的数据不是不一致的。 但是用户依然可以从 master 和 slave 上读取数据。所以是满足 AP 的。 这种方案一般在对数据的一致性不是很看重的场景中使用

CP

CP 的方案其实也比较好理解,出现故障后为了保证 C,那就需要牺牲 A。 比如还是上面 mysql 的案例,当出现了网络分区后,用户发往 slave 节点的请求全部返回 error 就可以了。这样用户不会读取到未同步的数据。 或者还有一种设计是根据用户/订单的 ID 对数据进行分区, 比如 ID 是 0~1000 的数据存到 Node1 中, 1000~2000 的存到 Node2 中。 这样的设计简单粗暴,因为同一份数据没有在其他节点上留下备份,用户想要读取这份数据也必须在固定的节点读取,所以它是强一致性的。代价是彻底牺牲了可用性,当一个节点故障的时候,那么这部分的数据就是不可用了。 这也是为什么我们有时会看到一些新闻,说某某平台机房故障,导致部分用户出现异常。 这里不是全部的用户异常,而是部分用户。就是因为它采取了这种强一致性的设计。

我觉得比较关键的东西

  • 首先 CAP 中的 C 关注的是数据,而不是系统功能。并且它的粒度是可以很细的,不是说整个系统必须选择 CP 或者 AP 的。而部分数据可以选择 CP 部分数据可以选择 AP 的。比如一些关键的数据,涉及金钱的数据是要选择 CP 的策略的。比如在一个电商系统中,商品的库存,用户的余额等数据需要做到 CP。 但是对于一些不是很重要的数据,比如用户的基本信息,像是性别,爱好,地址,电话等。
  • 在没有网络分区的情况下,其实还是可以追求 C 和 A 的。在测试的过程中需要额外注意。 注入网络分区故障后要根据业务要求进行验证,比如业务需要保证 C 那就要验证数据确实做到了一致性。如果业务选择了 A 也是同理。而在没有网络分区故障的时候,要验证系统的 C 和 A 都是达标的。
  • 很多时候没有完美的 CP,因为网络是有延迟的,不管网络有多快,数据同步都不是 100% 实时的。除非采用全同步的数据复制方案,不过这个是极大的消耗性能的,所以绝大多数场景不会采用这种方案。

BASE

之前在极客时间上学习课程的时候看到了 BASE 理论,这是对于 CAP 的补充,很多时候没有完美的系统所以提出了 BASE 理论来进行指导。BASE 是指基本可用(Basically Available)、软状态( Soft State)、最终一致性( Eventual Consistency),核心思想是即使无法做到强一致性(CAP 的一致性就是强一致性),但应用可以采用适合的方式达到最终一致性。

基本可用(Basically Available)

我理解就是系统中的功能但是分三六九等的,有核心的业务也有边边角角的业务。所以在系统异常时可以只保证核心业务可用即可。 追求整个系统的可用太过于浪费成本。

软状态( Soft State)

我理解就是准许系统存在中间状态, 在中间状态下数据可能是不一致的,但不能影响可用性。 这一点我理解就是在 CAP 中说道的没有完美的 CP。 数据同步一定会有一定的延迟。

最终一致性( Eventual Consistency)

系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。尤其是对于选择了 AP 方案的系统,放弃了一致性不是说数据就永远不一致了, 系统需要有一定的机制保障数据最终会呈现一致的状态。可以使用自动化的方式也可以使用人工的方式,一定要让数据最终呈现一致的状态。比如 mysql 的主从和主备架构(异步复制方案),在发生网络分区时或者 master 节点 down 机后数据肯定是不一致的。 运维人员可以通过 binlog 从 master 中恢复数据并重新保持 master 和 slave 的数据一致。 这也是一种可接受的方案。

总结

我理解 BASE 是对 CAP 的一个补充,尤其是对 AP 方案的补充。 因为 CAP 过于理想,想要实现完美的 CP 和 AP 方案太困难了。 所以很多系统最终都会变成 BASE 的状态。

我自己的思考

测试人员了解 CAP 和 BASE 的目的是在测试的过程中起到指导作用。 帮助我们了解系统当前的设计,分析系统高可用的目标。业务是希望选择 CP 还是 CA?是要保证强一致性? 还是最终一致性?只有了解了这些才能设计测试用例。 比如这里我列举 2 个我最近遇到的典型的场景。

  • 测试一个分布式系统时,这个系统使用了类似 raft 一致性算法进行 leader 的选举。这种系统的特点就是一定会存在一个 leader 节点,当 leader 挂掉后其他节点会发起选举来重新选一个节点作为 leader。这里判断 leader 节点是否健康的机制一般都是 leader 节点会定时向 follower 节点发送心跳来判断的。 根据这个原理,这类系统会比较怕网络分区故障。leader 节点其实并没有挂, 只是它与部分 follower 节点的网络中断了。 这导致在 leader 节点没有挂的情况下进行了重新选举。所以面对这种系统的时候,网路分区故障是一定要测试的。
  • 如果系统选择了类似 mysql 的主备或主从架构(异步复制方案)。这基本上就决定了系统是选择了 BASE 作为方案的。它肯定保证不了强一致性,如果我们在主备之间注入网络分区故障,那么数据一定就是不一致的,尤其是在注入网络分区故障一定的时间后,再注入一个故障把 master 节点弄挂掉。这时把备节点切换成 master 节点。那么备节点的数据就是旧的数据,对比与 master 节点一定是有数据丢失的。 其实这就要求系统要保证最终一致性。 这算是个容灾测试了, 一般来说系统要出一个手册来应对这种情况。 测试人员就要像我刚才说的先注入一个网络分区让数据不一致的情况更明显,然后再把 master 节点干掉。故意制造这个场景,然后验证的是我们的人员是否能按照容灾手册里的步骤在规定的时间内把数据恢复。 也就是要保证 BASE 中定义的最终一致性。

结尾

暂时就写这么多吧,感觉高可用这个方向还是很复杂的,我迄今为止感觉还是有蛮多东西没有搞懂的。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 5 条回复 时间 点赞

只有了解了这些才能设计测试用例。 十分认同

终于抽空看完了,点个赞。

这块确实是个盲点,现在大部分都是集群部署,甚至跨机房部署。一些中间件的网络一旦出问题,如果没设计好对应的处理方案,很容易引发数据不一致。但这块很多时候并不会作为功能需求由产品明确提出,所以很容易被忽略。

陈恒捷 将本帖设为了精华贴 05月07日 11:31

每个人对高可用的理解好像还不太一样

十分认同

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册