从分布式CAP理论到分布式一致性协议

前言

在分布式开发中,我认为具备CAP理论与了解Raft、Zab等分布式一致性协议是十分有必要的,例如分布式锁的选择,你是选择Redis的主备集群(AP模型)还是选择ZK、etcd(CP模型)呢?关于分布式锁的文章 点击这里查看 ,如果不具备这些理论知识,我觉得是无法灵活选择且用好分布式锁的,不同业务场景有不同AP、CP模型的需求,例如为什么Nacos的配置中心使用CP模型,但注册中心却使用AP模型呢?这都是不同的场景有着不同的考量。说了那么多,那么为什么CAP总是在CP与AP之间讨论?接着往下看。

CAP理论

CAP理论是在分布式集群环境下讨论的,为什么分布式集群环境下会存在CAP问题呢?举个例子,假设我们后端存储服务使用Redis中间件,如果只部署一台Redis服务器,那么这台Redis如果挂了,整个存储服务都挂了,那么我们就想要提高它的可用性,怎么办呢?最简单的方法就是加机器

  • 可用性:我部署两台Redis机器在一个集群中(主备集群),如果此时有一台Redis挂了我客户端照样可以访问另一台Redis,保证了一定程度的可用性(保证允许一台Redis宕机)

同时这两台主备Redis需要时刻保持数据同步,这样在一台宕机之后另一台也能保持原有的查询服务:

  • 一致性:如果我客户端在A节点的Redis设置了一个M值,然后A节点宕机,此时换B节点的Redis上,如果B节点正好同步了这个M值,就保证了一致性,但是有可能B节点的Redis还没来得及同步这个M值,我客户端在B节点读不到M值,这就存在了一致性问题

那什么是分区容错性呢(P)?

  • 分区容错性:这两台主备Redis发生了分区(A、B节点互相连接不上),那就做不了数据同步了,但此时集群依然向外开放服务,此时集群具有分区容错性,如果发生分区,集群就不能对外服务,则集群不具有分区容错性(P特性)

分区容错性,代表当分布式节点发生分区(A、B节点互相连接不上)此时的分布式系统是否还提供服务(是否容错),如果没有了P,代表发生分区之后整个分布式集群不能使用,这显然是不行的。下面看看保证P与不保证P的集群是什么样的:

  • 如果没了P,理论上集群不容许任何一个节点发生分区,当没有分区发生时确实可以保证AC(谁能保证集群系统的节点百分百不会出问题呢?能保证也不需要讨论AC问题了),当发生分区时整个集群不可用,没有现实意义(如图1)
  • 如果保证P,说明集群就只能从A和C选择其一(如图2)

综上所述,所以做一个分布式系统,P一定需要保证,那么我们的焦点就在AP与CP的模型去选择

在这里插入图片描述

在这里插入图片描述

如果保证P,说明集群就只能从A和C选择其一,为什么呢?我们重点看图2中的第四步,在B节点read M值的操作。

  • CP:若此时不让客户端读取B节点的M值,那么此时不可用,只能让客户端读取A节点,在A节点中M值确实是刚刚客户端Set M = 1值,M值一致,此时是CP模型,可用性被舍弃。
  • AP:若此时可以让客户端读取B节点的M值,那么此时节点可用,但是读取到的值M=0,如果下次又来一个客户端读取A节点的M值,却读到了M=1,M值不一致,此时是AP模型,一致性被舍弃

分布式一致性协议

当发生网络分区或是集群某个节点挂掉了,我们是否只能从一致性与可用性做抉择呢?不,作为成年人,我们两者都要(狗头),但两者我们都各自只拿一半,也就是基本可用与最终一致性(类似BASE理论),这里我将业界公认的分布式一致性协议Raft、Zab抽象出来讲,看看他们是如果保证CA两个属性的。

其实可以Raft、Zab协议可以做到基本可用+强一致性(线性一致性),基本可以算是分布式一致性算法,非要归类的话我认为其属于CP模型

Raft和Zab在分布式一致性角度上来说,主要思想是差不多的,所以可以一起抽象来讲,一些协议细节建议查看相关论文,这里只讨论如何保证数据一致与基本可用特性

协议的几个规则

在集群中节点有Leader、Follower两种角色,只有Leader节点才负责处理所有的事务操作,Follower节点只需要同步Leader处理的事务操作的数据,有时候也可以提供读服务。此算法是通过一切以领导者为准的方式,实现一系列值的共识和各节点日志的一致。

数据复制

假设此时客户端请求数据Set M = 0
在这里插入图片描述

  • 半数复制原则:只有Leader节点接受事务请求,Leader节点会通知其他节点保存数据,当有半数节点返回OK,Leader节点也保存数据,返回给客户端保存成功信息(保证数据在超过半数的节点上)

领导者选举

  • 获得集群内大多数节点的选票的节点才能当一个Leader

  • 每一个节点都可以发起选举让别人投自己一票,每一个节点在同一个任期号内只能投一票

  • 投给谁?

    • 参照Raft算法,按照先来先服务的标准,谁先来要票,谁就能获得本节点的选票,但是有一个关键是来要票的节点的数据至少不能比本节点旧

      • 数据怎么算新:任期号最大的最新,任期号一样的话那么数据越多的越新,每一届领导者都有一个epoch任期号的值概念,其为递增的值,重新选举时任期号会进行自增

      先来先服务,若数据都一样新,且选票被瓜分,选不出领导人怎么办?

      • 在Raft算法中使用一个随机的选举超时时间,使得每个节点去要票的时间点是随机的,一群节点同时交错去要票转化为了一个个节点去要票,Raft在很多地方都做的简单易懂,还不乏实用性。一个好的算法不仅能work,还能让人们简单易懂为什么能work,这很关键。
      • 在Zab协议中每一个节点都有一个myid,如果节点数据都一样新,选不出领导人就会比较myid,myid大的即可当选

如何保证一致性

在这里插入图片描述
假设此时客户端在写入一个值M=1:

根据数据复制规则,Leader只需要保证数据在多数节点上保存下来就可以返回成功

各个节点在各个时期挂了,是否会导致数据的不一致?

  • 若此时Leader在收到半数节点OK之前就挂了,那么客户端也会返回一个设置M值错误,显然此时的集群数据是一致的(从未保存过值)

  • Leader在收到半数节点OK,证明数据在大部分节点上之后,返回给客户端设置值成功之后Leader节点就挂了,正如图4那样,第四步返回客户端OK信息后宕机,没来得及复制给S4和S5节点,此时各节点数据如下:

在这里插入图片描述
群龙无首,此时必须选举出一个新的Leader
在这里插入图片描述
可以看出,数据比较旧的S4,S5只能要到两票,得不到半数的票(半数=(节点/2) + 1,这里5个节点就是3票),但是数据比较新的S2、S3却可以得到半数以上的票(任何的节点都会给他们投票,因为他们的数据至少都不比其他节点旧),根据上面的领导者选举规则,只有S2、S3有机会能当选Leader,当选后,Leader会通过heartbeat或其他同步数据方式同步Set M = 1 这条日志,此时集群数据一致

  • 那既然数据最新的才可以当选Leader,那如果最新的S2、S3也挂了呢?
    在这里插入图片描述
    由图可以看出,如果最新的S2、S3都挂了,那旧的S4、S5也是只能拿到两票(对方和自己总共两票),此时选不出一个Leader,整个集群不可用,数据依然可以看作是一致的

    由此可以看出,此分布式协议容许半数以下的节点宕机,如果半数节点宕机,集群不可用,所以称为基本可用

总结

从上面的讨论上来看,半数很关键(半数提交、半数选举),相信看到这里你会懂得这半数的魔力,接下来总结一下此分布式一致性协议由于"半数"会有哪些问题:

  • 事务操作的性能瓶颈:由于需要保证半数节点保存了数据,则增删改数据的性能取决于半数节点的性能(5个节点的写入性能则取决于3个节点(包括自己)的写入速度)和与半数节点通信的网络开销。这意味着,集群节点越多(如果想要高可用,那就上多节点),写入性能有可能会越差(试想,上100个节点,每次写入都要发给50个节点确保写入成功,有50次网络开销,不过数据复制过程是并行的,木桶效应总耗时取决于半数中最慢的那个节点,不过节点越多,木桶短板出现的概率也就越大,所以这里说写入性能是可能变差的)

  • 基本可用:集群由于需要半数提交才能成功写入数据,所以如果5个节点宕机2个集群是可用的,若宕机3个,由于得不到半数的提交,集群就会不可用,从这点上来看达到了基本可用的特性

  • 最终一致性 or 强一致性(线性一致性):

    如果我们仅仅只是写入一个值,从写入角度来说那就是强一致性(因为都在Leader处理,Raft与Zab都支持顺序一致性,即为你刚刚Set M = 1,又Set M = 2,顺序如果反了那结果会变成 M = 1,由于都在同一个Leader节点做,节点维护一个队列即可保持顺序),所以下面更多的是对于读数据的一致性讨论

    • 最终一致性:这取决于具体的实现,若是最终一致性,那么Follower就开放读能力,这么做有个优点,整个集群的读能力会随着节点个数得到提升,但有个缺点,由于半数提交的规则,有可能上一秒你写入成功了一个值,下一秒在集群另外某个节点中读不到这个值(可能还没同步到),但最终会在一个时间点此值被同步,也就是最终一定会达到一致性
    • 强一致性(线性一致性):如果读能力只在Leader节点开放,也就是读我也只能在Leader上读,那么此时的一致性是极强的,随着上一秒的设置值,下一秒一定可以查到刚刚设置的值,缺点就是读性能得不到扩展,局限于Leader单点性能的问题(读写都在一台节点上操作)

以上讨论了CP模型,对一致性要求比较高的系统比较适用,可以看出来,这种分布式协议有性能的局限性,因为他牺牲了一定的客户端响应延迟来确保一致性,而且仅仅保证了半数的基本可用性。在延时要求高、高并发场景下这种模型或许并不适用,此时可以考虑AP模型提高响应延迟。

如果是AP模型,相比于一致性协议会简单一些,因为他只需要保证可用性,加集群节点即可,而数据丢失、不一致的情况只会在宕机的那一时刻发生,丢失、不一致的也只是那一时刻的数据,例如Redis的主从集群架构,主Redis节点只需要异步、定时去同步数据,在写入时只需要一个节点确认写入即可返回,延时比一致性协议低,由于Redis在使用上大部分场景都用在缓存,快是他的设计目标,偶尔丢几条或者不一致几条缓存数据并不影响场景(关于Redis主从的AP模型的讨论更详细的在这里)。

回答开篇

为什么Nacos配置中心要使用CP模型?

  • 答:CP模型牺牲了一定的延时去换取数据的强一致,但应用的外部化配置并不要求配置成功的延迟要多低,更多的是要保证配置信息的一致性(我配置什么信息A=1,不要给我弄丢了或者等下查 A 不等于 1,这些都是数据不一致,这肯定是不行的),所以在这种配置场景下是十分适合做CP模型的

为什么Nacos注册中心要使用AP模型?

  • 答:试想一下,微服务的通信,或者说Dubbo的RPC通信,都需要使用到注册中心去拿到服务的IP地址列表,这关乎业务场景服务调用的延迟问题,延迟这块就有一定的要求了,那么再来看看,数据的不一致会导致什么问题?可能你刚上线了一个A服务实例,此时注册中心集群有某个节点挂了,没注册上,此实例就不会被服务访问到(注册中心没有这个实例的IP),但也无伤大雅,暂时访问不到而已,发现问题将服务重启重新注册到注册中心上去即可,再说了,这种情况是刚好注册实例的时候注册中心Nacos集群的Master挂了,才会导致这种数据不一致的情况,且能接受,这种延迟要求高、数据一致性要求低的场景确实十分适合做AP模型
发布了86 篇原创文章 · 获赞 102 · 访问量 12万+

猜你喜欢

转载自blog.csdn.net/qq_41737716/article/details/105426200