![ee45a5dd491ddf2eec84ca12b450f217.png](https://i-blog.csdnimg.cn/blog_migrate/3da4899372708b16e427b5775e62623b.png)
前言
在分布式开发中,我认为具备CAP理论与了解Raft、Zab等分布式一致性协议是十分有必要的,例如分布式锁的选择,你是选择Redis的主备集群(AP模型)还是选择ZK、etcd(CP模型)呢?
关于分布式锁的文章
从分布式理论到如何做一个生产级别的分布式锁_|-| [- |_ |_ ()-CSDN博客_如何做一个分布式锁blog.csdn.net![796cba6c34992e3e8d662eac364a6d07.png](https://i-blog.csdnimg.cn/blog_migrate/e2d3d6b587df4af28888bdd49e89c499.jpeg)
如果不具备这些理论知识,我觉得是无法灵活选择且用好分布式锁的,不同业务场景有不同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的模型去选择
![1702f5d200fc3b872cc3a51c6ba0bf5f.png](https://i-blog.csdnimg.cn/blog_migrate/ddfad06ffc0a3e19dee0d975bfbddab6.jpeg)
![365cc542dd613443a59057b29bb80f56.png](https://i-blog.csdnimg.cn/blog_migrate/1c750dacc605ba3b38f0bda58e297f7f.jpeg)
如果保证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
![95cf07dd8c35f0478e9f6a73dae0e4f0.png](https://i-blog.csdnimg.cn/blog_migrate/ab7f088d74be556bb8844f92df1c9b22.jpeg)
- 半数复制原则:只有Leader节点接受事务请求,Leader节点会通知其他节点保存数据,当有半数节点返回OK,Leader节点也保存数据,返回给客户端保存成功信息(保证数据在超过半数的节点上)
领导者选举
- 获得集群内大多数节点的选票的节点才能当一个Leader
- 每一个节点都可以发起选举让别人投自己一票,每一个节点在同一个任期号内只能投一票
- 投给谁?
- 参照Raft算法,按照先来先服务的标准,谁先来要票,谁就能获得本节点的选票,但是有一个关键是来要票的节点的数据至少不能比本节点旧
- 数据怎么算新:任期号最大的最新,任期号一样的话那么数据越多的越新,每一届领导者都有一个epoch任期号的值概念,其为递增的值,重新选举时任期号会进行自增
- 参照Raft算法,按照先来先服务的标准,谁先来要票,谁就能获得本节点的选票,但是有一个关键是来要票的节点的数据至少不能比本节点旧
先来先服务,若数据都一样新,且选票被瓜分,选不出领导人怎么办?
-
-
- 在Raft算法中使用一个随机的选举超时时间,使得每个节点去要票的时间点是随机的,一群节点同时交错去要票转化为了一个个节点去要票,Raft在很多地方都做的简单易懂,还不乏实用性。一个好的算法不仅能work,还能让人们简单易懂为什么能work,这很关键。
- 在Zab协议中每一个节点都有一个myid,如果节点数据都一样新,选不出领导人就会比较myid,myid大的即可当选
-
如何保证一致性
![d807514b81a0f5391eaaa449ac99e170.png](https://i-blog.csdnimg.cn/blog_migrate/18e51b46e15f7f5e349a5df146a42d55.jpeg)
假设此时客户端在写入一个值M=1:
根据数据复制规则,Leader只需要保证数据在多数节点上保存下来就可以返回成功
各个节点在各个时期挂了,是否会导致数据的不一致?
- 若此时Leader在收到半数节点OK之前就挂了,那么客户端也会返回一个设置M值错误,显然此时的集群数据是一致的(从未保存过值)
- Leader在收到半数节点OK,证明数据在大部分节点上之后,返回给客户端设置值成功之后Leader节点就挂了,正如图4那样,第四步返回客户端OK信息后宕机,没来得及复制给S3和S4节点,此时各节点数据如下:
![d465d923adbda78dd21c4801b1d7afc8.png](https://i-blog.csdnimg.cn/blog_migrate/cc6713e6a72fdd7834d1630b0aa8e3c4.jpeg)
群龙无首,此时必须选举出一个新的Leader
![cd63cf9d3384b6e9f8f5cac75a4396aa.png](https://i-blog.csdnimg.cn/blog_migrate/4f304d9652166002826315dbdf211075.jpeg)
可以看出,数据比较旧的S4,S5只能要到两票,得不到半数的票(半数=(节点/2) + 1,这里5个节点就是3票),但是数据比较新的S2、S3却可以得到半数以上的票(任何的节点都会给他们投票,因为他们的数据至少都不比其他节点旧),根据上面的领导者选举规则,只有S2、S3有机会能当选Leader,当选后,Leader会通过heartbeat或其他同步数据方式同步Set M = 1 这条日志,此时集群数据一致
- 那既然数据最新的才可以当选Leader,那如果最新的S2、S3也挂了呢?
![ba20d58d6c3fc734f1aaa8e122e04212.png](https://i-blog.csdnimg.cn/blog_migrate/f2e8ad0debe06553ff3c69c3098e4976.jpeg)
由图可以看出,如果最新的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模型?
- 答:这个问题也可以转换成为什么注册中心要使用AP模型。因为可用性比一致性更加重要,可用性体现在两个方面,第一是容许集群宕机数量,AP模型中集群全部机器宕机才会造成不可用,CP模型只容许一半的机器宕机。第二是分区是否可写,例如A机房和B机房发生分区,无法进行网络调用,在CP模型下部署在A机房的服务就无法部署,因为A机房无法与B机房通讯,所以无法写入IP地址,在AP模型下,可以去中心化,就像Eureka那样就算发生分区每台机器还都能写入,集群间无Master,而是互相复制各自的服务IP信息,这样,在分区时依然是支持写入的。不可用会造成微服务间调用拿不到IP地址,进而业务崩盘,所以不可用是不可接受的,不一致会造成不同微服务拿到的IP地址不一样,会造成负载不均衡,这是可以接受的。
更多深度技术文章,可以瞅瞅本人的博客|-| [- |_ |_ ()_Static_lin_CSDN博客-spring,设计模式,分布式领域博主blog.csdn.net
![796cba6c34992e3e8d662eac364a6d07.png](https://i-blog.csdnimg.cn/blog_migrate/e2d3d6b587df4af28888bdd49e89c499.jpeg)