一、场景引入
在随着人类的发展过程,由于人类是一群人,而非单个人,并相互联系,为了基本的生存或更美好的精神追求,出现了分工协作(单个人无法完成)的概念。人性是具有追求极致、美化的特性,因此简单地协作还无法满足他们的需求,所以出现了例如群落中的首领去领导群落,出现国家的概念更完善的体系去更好地分工。【体现着 性能与复杂度处理、可用性的 平衡】
计算机的设计也来源于社会的需求,人类的抽象。再解决复杂问题时,除了单个计算机或CPU的性能提升外,还可以通过 分布式、集群等方式来共同协作。众人拾柴火焰高。
那么带来的问题是什么呢?
- 数据不一致(好比 Leader传递指令,下面的执行者**看到【读】**的数据指令不一样,那么就无法有效推进程序运行,甚至 出错】
- 协作出错(例如机器宕机,通信超时问题
- 其他:规则如何制定才能保证高效的协作【该处则共识算法去考虑的】? 在计算机中各节点或者程序都是平等的,就像人一样出生平等,但随着时间推移,选择了不同的职业,做了不同的角色。— 等等
那么一致性可能会出现在什么场景呢?
这一致性,指的是流动的 数据
需要一致性,通过 该数据 使 相应的 节点(机器、Cache,程序应用)发生联系。因此,实际上 与该数据 无关 的机器 则 不用考虑【可以理解对应为 并发中的 临界资源】。
而对于 数据 的操作 只有两种:
- 读(使用数据,指挥计算机做某件事情
- 写(更改数据,改变指令、数据状态
只要不写,不发生CURD 数据,那么读到的数据永远是一致的。因此只要保证写一致性,那么读也就一致了。
一致性的程度
只要存在通信的过程,则无法完全保证同一时刻,所有的节点数据一致(当然是指由一个机器进行修改,然后将更新的数据通过通信传递至其他机器节点)【强一致性的体现 不仅在于 同步的时间上,也在要求同步的节点数量方面,例如2PC 需要全部参与者决定,则是强一致性】
→ 那为什么不能直接同时写入所有节点呢?,如果这样做,那么考虑一下性能开销。client的等待时间,可用性大大打折扣。
因此BASE,提出了 最终一致性。变种:
- 因果一致性
- 读已之所写
- 会话一致性
- 单调读\写一致性
如何才能控制一致性呢?
- 节点间的相互通信,能够相传递变更信息;广播
- 串行化,让数据的变化 具有顺序性; 锁,MMVVC
自低到上,大致来总结看看计算机中的(数据)一致性场景:
- CPU:CPU Cache一致性问题
- OS:进程、线程、协程协作方面的一致性,
- 编程语言层面:JUC的锁机制、JMM
- 系统级应用:MySQL,事务的一致性—并发控制
- 应用协作:→ 缓存 与 数据库 的一致性问题【Redis和DB的双写不一致问题,常问】
- 架构机器层面:机器节点之间的协作【其实本质则是进程间,只是通信方式由 本地 改为 远程】,分布式一致性协议 【 数据 通过 事务体现
二、一致性协议
两段提交(2PC)
两阶段提交协议,简称2PC(2 Prepare Commit),是比较常用的解决分布式事务问题的方式,要么所有参与进程提交事务,要么都取消事务。【通过协调者去发布,如何参与者共同决定事务】,是当前绝大数数据库采用的一致性算法去保证分布式事务。
基本角色
- 协调者,复杂协调调度 参与者,最终决定事务的提交
- 决策者(集群参与者),执行具体的事务,并将undo,redo信息写入日志
基本过程
第一阶段(提交事务请求
- 协调者发起事务
- 参与者,直接执行事务,并写入日志(并未真正执行,
- 反馈ack至协调者(yes,no)
第二阶段(执行事务提交
两种情况:执行事务提交(成功执行) Or 中断事务(执行失败);
对于执行事务提交,当所有的参与者反馈的yes(即所有参与者都成功将事务执行成功),则协调者可以正当去提交申请了。
- 向参与者发起 提交commit请求
- 参与者 执行 Commit,真正意义上地 提交事务执行
- 参与者反馈ACK
- 完成事务(思考:若此时有机器执行事务失败呢?→ 中断事务,全部重来)
对于中断事务,反馈的ack有no,或者有节点超时。
- 向所有参与者发起Rollback 回滚
- 事务回滚
- 参与者反馈回滚ack
- 事务执行结束
优缺点
优点:
- 原理简单、实现方便
缺点:
- 同步阻塞:当参与者中有节点未在超时设定范围内返回,其他节点都需要等待。木桶效应,不能尽可能地压榨机器、CPU。
- 单点问题:协调者只有一个,一旦宕机,则GG
- 脑裂
- 数据不一致:网络分区,导致一部分节点(未能正常通信)和其他部分数据不一致。
- 过于保守:无容错机制
应用实现
- Seata 分布式事务实现
- Oracle、MySQL事务机制
三段提交(3PC)
原文链接:https://blog.csdn.net/xuhss_com/article/details/123147658
三阶段提交是二阶段提交的改进版,将2PC的”提交事务请求“过程一分为二,共形成了由CanCommit,PreCommit和doCommit三个阶段组成的事务处理协议。
第一阶段(CanCommit阶段):类似于2PC的准备(第一)阶段。协调者向参与者发送commit请求,参与者如果可以提交就返回Yes响应,否则返回No响应。
第二阶段(PreCommit阶段):协调者根据参与者的反应情况来决定是否可以执行事务的PreCommit操作。
如果是YES,向参与者发送预提交请求,通知参与者执行事务操作。
如果是NO,向参与者发送abort请求
第三阶段(doCommit阶段):根据上一阶段的结果进行真正的事务提交或中断。
如果第三阶段中,协调者挂掉或者协调者和参与者出现网络问题:参与者都会在等待超时之后,继续进行事务提交
2PC和3PC的对比:
3PC对于协调者和参与者都设置了超时机制(在2PC中,只有协调者拥有超时机制,即如果在一定时间内没有收到参与者的消息则默认失败),主要是避免了参与者在长时间无法与协调者节点通讯(协调者挂掉了)的情况下,无法释放资源的问题,因为参与者自身拥有超时机制会在超时后,自动进行本地commit从而进行释放资源。而这种机制也侧面降低了整个事务的阻塞时间和范围。
通过CanCommit、PreCommit、DoCommit三个阶段的设计,相较于2PC而言,多设置了一个缓冲阶段保证了在最后提交阶段之前各参与节点的状态是一致的 。
PreCommit是一个缓冲,保证了在最后提交阶段之前各参与节点的状态是一致的。
NWR协议
原文参考:https://www.cnblogs.com/tortoise512/articles/16368540.html
分布式系统中各类型数据的一致性要求不尽相同,而Quorum NWR算法则为我们提供了一种在强一致性与最终一致性之间可以进行动态变化的思路。
- N: 数据的副本数,而非机器节点数
- W:参数W为写一致性级别(Write Consistency Level),其含义为成功完成W个副本更新、写入,才会视为本次写操作成功。
- R:参数R为读一致性级别(Read Consistency Level)。类似地,其含义为成功从R个副本读取相应数据,才会视为本次读操作成功。
通过N、W、R参数在不同组合条件下,就可实现强一致性、最终一致性的动态切换:
- 当 「W + R > N」 时,根据「鸽巢原理」可知,在进行读操作时R个副本返回的结果中一定包含最新的数据【W和R必然有重合的部分】。然后再利用**时间戳、版本号【对比数据库的MVVC,Kafka通过时间戳读数据】**等手段即可确定出最新的数据。
→ 该条件的参数组合下,可以实现数据的强一致性【此刻的强一致性并非要求的时间上的同步高效,而是高可用??数据的准确性??】
- 当 「W + R <= N」 时,无法实现强一致性,其只能保障最终一致性。即系统可能会获取旧数据。W+R可能无重合,拿不到最新数据。
事实上当W=N、R=1时,即所谓的WARO(Write All Read One)。就是CAP理论中CP模型的场景。综上所述从实际应用角度出发,Quorum NWR算法有效解决了AP模型下不同业务场景对自定义一致性级别的需求
Gossip协议
基本概念
Gossip协议也叫Epidemic协议(流行病协议)。原本用于分布式数据库中节点同步数据使用,后被广泛用于数据库复制、信息扩散、集群成员身份确认、故障探测等。
gossip 协议的有:Redis Cluster、Consul、Apache Cassandra
gossip 协议利用一种随机的方式将信息传播到整个网络中,并在一定时间内使得系统内的所有节点数据一致。Gossip 其实是一种去中心化思路的分布式协议,解决状态在集群中的传播和状态一致性的保证两个问题。
基本原理
原文参考:https://www.jianshu.com/p/ba1415a636a5
Gossip协议基本思想就是:一个节点想要分享一些信息给网络中的其他的节点,于是随机选择一些节点进行信息传递。这些收到信息的节点接下来把这些信息传递给其他一些随机选择的节点。整体过程描述如下。
- Gossip 是周期性的散播消息
- 被感染节点随机选择 k 个邻接节点(fan-out)散播消息,假设把 fan-out 设置为2,每次最多往2个节点散播
- 每次散播消息都选择尚未发送过的节点进行散播
- 收到消息的节点不再往发送节点散播,比如 A -> B,那么 B 进行散播的时候,不再发给 A
在gossip中消息传播方式有以下两种:
- Anti-entropy(反熵):传播节点上的所有的数据【全量】
- Rumor mongering(传谣):是传播节点上的新到达的、或者变更的数据【增量】
优缺点
优点
- 扩展性:允许节点的任意增加和减少,新增节点的状态最终会与其他节点一致
- 容错:任意节点的宕机和重启都不会影响Gossip消息的传播,具有天然的分布式系统容错特性
- 去中心化:无需中心节点,所有节点都是对等的,任意节点无需知道整个网络状况,只要网络连通,任意节点可把消息散播到全网
- 最终一致性:Gossip协议实现信息指数级【LogN,Log K (N)】的快速传播,因此在有新信息需要传播时,消息可以快速地发送到全局节点,在有限的时间内能够做到所有节点都拥有最新的数据。【如何一个节点都可去写更新数据,然后再传播,但只能保证最终一致性,不能保证更改数据后,立马读取到最新数据】
缺点
- 消息延迟:节点随机向少数几个节点发送消息,消息最终是通过多个轮次的散播而到达全网;不可避免的造成消息延迟。
- 消息冗余:节点定期随机选择周围节点发送消息,而收到消息的节点也会重复该步骤;不可避免的引起同一节点消息多次接收,增加消息处理压力
Paxos协议
基本概述
https://baike.baidu.com/item/Paxos 算法/10688635?fr=aladdin
paxos算法是一种基于消息传递且具有高度容错特性的一致性算法,被认为是类似算法中最有效的,开源的ZooKeeper,以及MySQL 5.7推出的用来取代传统的主从复制的MySQL Group Replication都采用Paxos算法解决分布式一致性问题
Paxos的版本有Basic Paxos,Multi Paxos,Fast-Paxos,具体落地有Raft和zk的ZAB协议。
Paxos 算法解决的问题是一个分布式系统如何就某个值(决议)达成一致。一个典型的场景是,在一个分布式数据库系统中,如果各节点的初始状态一致,每个节点执行相同的操作序列,那么他们最后能得到一个一致的状态。
为保证每个节点执行相同的命令序列,需要在每一条指令上执行一个“一致性算法”以保证每个节点看到的指令【数据】一致。一个通用的一致性算法可以应用在许多场景中,是分布式计算中的重要问题。
节点通信存在两种模型:共享内存(Shared memory)和消息传递(Messages passing)。
Paxos 算法就是一种基于消息传递模型的一致性算法。
不仅仅是分布式系统中,凡是多个过程需要达成某种一致的场合都可以使用Paxos 算法。一致性算法可以通过共享内存(需要锁)【大数据组件中 通过Zookeeper管理元数据去协调,注册中心等去管理服务】或者消息传递实现,Paxos 算法采用的是后者。Paxos 算法适用的几种情况:一台机器中多个进程/线程达成数据一致;分布式文件系统或者分布式数据库中多客户端并发读写数据;分布式存储中多个副本响应读写请求的一致性。
- 进程、线程数据一致性
- 并发读写数据
- 副本数存储多副本响应读写请求一致性
基本原理
角色
Paxos将系统中的角色分为提议者 (Proposer),决策者 (Acceptor),和最终决策学习者 (Learner):
- Proposer: 提出提案 (Proposal)。Proposal信息包括提案编号 (Proposal ID) 和提议的值 (Value)。【Leader】 →抽象的属性: 提案编号N【全局唯一,保证顺序性—>串行化】, 提案V。
- Acceptor: 参与决策,回应Proposers的提案。收到Proposal后可以接受提案,若Proposal获得多数Acceptors的接受,则称该Proposal被批准。【Qurom 半数仲裁机制】
→抽象的属性: 响应提案编号ResN, 接受的提案编号AcceptN, 接收的提案AcceptV
- Learner: 不参与决策,从Proposers/Acceptors学习最新达成一致的提案(Value)。【观察者,享受结果,提升读并发读?】
简单可拿人大代表例子去理解,人大代表则是Acceptor,Proposer则是人大常委员,Learner则是人民。
基本过程
第一阶段: Prepare准备阶段
Proposer: Proposer生成全局唯一且递增的提案编号N,,向所有Acceptor发送Prepare请求,这里无需携带提案内容,只携带提案编号即可, 即发送 Proposer(N, null)。
Acceptor: Acceptor收到Prepare请求后,有两种情况:
- 如果Acceptor首次接收Prepare请求, 设置ResN=N, 同时响应ok
- 如果Acceptor不是首次接收Prepare请求,则:
- 若请求过来的提案编号N小于等于上次持久化的提案编号ResN,则不响应或者响应error。【需要处理最新的提案,保证数据的最新】
- 若请求过来的提案编号N大于上次持久化的提案编号ResN【若N>ResN+1?中间有数据未进行处理?可拿是本身出问题。】, 则更新ResN=N,同时给出响应。响应的结果有两种,如果这个Acceptor此前没有接受过提案, 只返回ok。否则如果这个Acceptor此前接收过提案,则返回ok和上次接受的提案编号AcceptN, 接收的提案AcceptV。
第二阶段: Accept接受阶段
Proposer: Proposer收到响应后,有两种情况:
- 如果收到了超过半数响应ok, 检查响应中是否有提案,如果有的话,取提案V=响应中最大AcceptN对应的AcceptV,如果没有的话,V则有当前Proposer自己设定。最后发出accept请求,这个请求中携带提案V。
- 如果没有收到超过半数响应ok, 则重新生成提案编号N, 重新回到第一阶段,发起Prepare请求。
Acceptor: Acceptor收到accept请求后,分为两种情况:
- 如果发送的提案请求N大于此前保存的RespN,接受提案,设置AcceptN = N, AcceptV=V, 并且回复ok。
- 如果发送的提案请求N小于等于此前保存的RespN,不接受,不回复或者回复error。
Proposer: Proposer收到ok超过半数,则V被选定,否则重新发起Prepare请求。
第三阶段: Learn学习阶段
Learner: Proposer收到多数Acceptor的Accept后,决议形成,将形成的决议发送给Learner。
Raft协议
https://mp.weixin.qq.com/s/OLcb0nWLp-Oa1DA6ppFbKQ
Raft在Paxos的基础上进行简化。
基本概念
Raft协议一共包含如下3类角色:
- Leader(领袖):领袖由群众投票选举得出,每次选举,只能选出一名领袖;
- Candidate(候选人):当没有领袖时【避免单独故障】,某些群众可以成为候选人,然后去竞争领袖的位置;
- Follower(群众):听从决策,只管读数据,提升并发读。
- Leader Election(领导人选举):简称选举,就是从候选人中选出领袖;
- Term(任期):它其实是个单独递增的连续数字,每一次任期就会重新发起一次领导人选举;
- Election Timeout(选举超时):就是一个超时时间,当群众超时未收到领袖的心跳时,会重新进行选举。【防止机器宕机,保证可用性】
主要分析这几个过程:
- 领导选举
- 领导挂了
- 出现多个候选者
Lease协议
原文:https://blog.csdn.net/xuhss_com/article/details/123147658
Lease机制,翻译过来是租约机制,是维护分布式系统数据一致性的一种常用工具。
Lease机制有如下几个特点:
- Lease是颁发者对一段时间内数据一致性的承诺。
- 颁发者发出Lease后,不管是否被接受,只要Lease不过期,颁发者都会被按照协议遵守承诺。
- Lease的持有者只能在Lease的有效期内使用承诺,一旦Lease超时,持有者需要放弃执行,重新申请Lease
解决的问题
Node1是主节点,剩下4个副本,如果Node1发生网络抖动(Node1本身是正常的,没有宕机),导致从节点无法接收到心跳。他们就会再选出一个主节点。
这种场景解决思路有4种:
- 设计能容忍双主的协议
- Raft协议:通过TermId大的通过给低的
- Lease机制
- 去中心化-Gossip协议
引入了一个中心节点,负责下发有时效性的Lease给主节点(这个有效期内不管你出啥问题,我就认你)
如果网络出现问题了,重新选了主节点。Lease未过期,新的主节点去申请会被拒绝网络恢复了,原来的主节点可以继续续期。如果是原来的主节点是真的宕机了,那么它的有效期过后,新的主节点就能申请成功了。
ZAB
https://blog.51cto.com/u_6478076/5214929
角色
Leader节点:有任期的领导节点,负责作为与客户端的连接点接受读写操作,然后将其广播到其他节点去。
Follower节点:主要是跟随领导节点的广播消息进行同步,并关注领导节点的健康状态,好随时取而代之。
基本过程
既然有Leader节点,就必然有Leader的选举过程,ZAB的选举,会先看各个节点所记录的消息的时间戳(数据ID),时间戳(数据ID)越大,节点上的数据越新,就会优先被投票,如果数据ID比较不出来,就再看事先定义的节点的优先级(节点ID)。当大家根据上述优先级投票,超过半数去支持一个节点时,该节点就成为Leader节点了。
1.leader把消息赋予一个全局唯一的自增64位id,也就是zxid
2.leader为每个follower准备了一个FIFO队列,zxid的消息分发给所有的follower
3.follower接收到proposal,先写磁盘,写入成功后给leader回复一个ack
4.leader收到半数以上的ack后,就向这些follower发送commit命令,同时本地执行这个消息
5.当follower收到消息的commit命令以后,会提交该消息
通过心跳算法可以共同检查Leader节点的健康度,如果出现问题(比如机器下线、网络分区、延迟过高等),就会考虑重新选举。
三、总结思考
分布式的解决的关键问题为:通信 + 顺序性。其中顺序性则是去保证数据的正常写,正常被修改,不会出错,保证的是一种正确性,而基本手段则是通过时间戳、事务全局ID等方式。通信(关系是最麻烦的问题,也是最容易出问题的地方)则是如何将分布式的集群进行关联、协调起来,此时则需要考虑去中心化和非去中心化,消息\数据的传播方式,通信故障问题等。