关于一致性问题的简单总结

一、场景引入

在随着人类的发展过程,由于人类是一群人,而非单个人,并相互联系,为了基本的生存或更美好的精神追求,出现了分工协作(单个人无法完成)的概念。人性是具有追求极致、美化的特性,因此简单地协作还无法满足他们的需求,所以出现了例如群落中的首领去领导群落,出现国家的概念更完善的体系去更好地分工。【体现着 性能与复杂度处理、可用性的 平衡】

计算机的设计也来源于社会的需求,人类的抽象。再解决复杂问题时,除了单个计算机或CPU的性能提升外,还可以通过 分布式、集群等方式来共同协作。众人拾柴火焰高。

那么带来的问题是什么呢?

  • 数据不一致(好比 Leader传递指令,下面的执行者**看到【读】**的数据指令不一样,那么就无法有效推进程序运行,甚至 出错】
  • 协作出错(例如机器宕机,通信超时问题
  • 其他:规则如何制定才能保证高效的协作【该处则共识算法去考虑的】? 在计算机中各节点或者程序都是平等的,就像人一样出生平等,但随着时间推移,选择了不同的职业,做了不同的角色。— 等等

那么一致性可能会出现在什么场景呢?

这一致性,指的是流动的 数据 需要一致性,通过 该数据 使 相应的 节点(机器、Cache,程序应用)发生联系。因此,实际上 与该数据 无关 的机器 则 不用考虑【可以理解对应为 并发中的 临界资源】。

而对于 数据 的操作 只有两种:

  • 读(使用数据,指挥计算机做某件事情
  • 写(更改数据,改变指令、数据状态

只要不写,不发生CURD 数据,那么读到的数据永远是一致的。因此只要保证写一致性,那么读也就一致了。

一致性的程度

只要存在通信的过程,则无法完全保证同一时刻,所有的节点数据一致(当然是指由一个机器进行修改,然后将更新的数据通过通信传递至其他机器节点)【强一致性的体现 不仅在于 同步的时间上,也在要求同步的节点数量方面,例如2PC 需要全部参与者决定,则是强一致性】

→ 那为什么不能直接同时写入所有节点呢?,如果这样做,那么考虑一下性能开销。client的等待时间,可用性大大打折扣。

因此BASE,提出了 最终一致性。变种:

  • 因果一致性
  • 读已之所写
  • 会话一致性
  • 单调读\写一致性

如何才能控制一致性呢?

  • 节点间的相互通信,能够相传递变更信息;广播
  • 串行化,让数据的变化 具有顺序性; ,MMVVC

自低到上,大致来总结看看计算机中的(数据)一致性场景:

  1. CPU:CPU Cache一致性问题
  2. OS:进程、线程、协程协作方面的一致性,
  3. 编程语言层面:JUC的锁机制、JMM
  4. 系统级应用:MySQL,事务的一致性—并发控制
  5. 应用协作:→ 缓存 与 数据库 的一致性问题【Redis和DB的双写不一致问题,常问】
  6. 架构机器层面:机器节点之间的协作【其实本质则是进程间,只是通信方式由 本地 改为 远程】,分布式一致性协议 【 数据 通过 事务体现

二、一致性协议

在这里插入图片描述

两段提交(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参数在不同组合条件下,就可实现强一致性、最终一致性的动态切换:

  1. 当 「W + R > N」 时,根据「鸽巢原理」可知,在进行读操作时R个副本返回的结果中一定包含最新的数据【W和R必然有重合的部分】。然后再利用**时间戳、版本号【对比数据库的MVVC,Kafka通过时间戳读数据】**等手段即可确定出最新的数据。

→ 该条件的参数组合下,可以实现数据的强一致性【此刻的强一致性并非要求的时间上的同步高效,而是高可用??数据的准确性??】

  1. 当 「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协议基本思想就是:一个节点想要分享一些信息给网络中的其他的节点,于是随机选择一些节点进行信息传递。这些收到信息的节点接下来把这些信息传递给其他一些随机选择的节点。整体过程描述如下。

  1. Gossip 是周期性的散播消息
  2. 被感染节点随机选择 k 个邻接节点(fan-out)散播消息,假设把 fan-out 设置为2,每次最多往2个节点散播
  3. 每次散播消息都选择尚未发送过的节点进行散播
  4. 收到消息的节点不再往发送节点散播,比如 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请求后,有两种情况:

  1. 如果Acceptor首次接收Prepare请求, 设置ResN=N, 同时响应ok
  2. 如果Acceptor不是首次接收Prepare请求,则:
  • 若请求过来的提案编号N小于等于上次持久化的提案编号ResN,则不响应或者响应error。【需要处理最新的提案,保证数据的最新】
  • 若请求过来的提案编号N大于上次持久化的提案编号ResN【若N>ResN+1?中间有数据未进行处理?可拿是本身出问题。】, 则更新ResN=N,同时给出响应。响应的结果有两种,如果这个Acceptor此前没有接受过提案, 只返回ok。否则如果这个Acceptor此前接收过提案,则返回ok和上次接受的提案编号AcceptN, 接收的提案AcceptV。

第二阶段: Accept接受阶段

Proposer: Proposer收到响应后,有两种情况:

  1. 如果收到了超过半数响应ok, 检查响应中是否有提案,如果有的话,取提案V=响应中最大AcceptN对应的AcceptV,如果没有的话,V则有当前Proposer自己设定。最后发出accept请求,这个请求中携带提案V。
  2. 如果没有收到超过半数响应ok, 则重新生成提案编号N, 重新回到第一阶段,发起Prepare请求。

Acceptor: Acceptor收到accept请求后,分为两种情况:

  1. 如果发送的提案请求N大于此前保存的RespN,接受提案,设置AcceptN = N, AcceptV=V, 并且回复ok。
  2. 如果发送的提案请求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等方式。通信(关系是最麻烦的问题,也是最容易出问题的地方)则是如何将分布式的集群进行关联、协调起来,此时则需要考虑去中心化和非去中心化,消息\数据的传播方式,通信故障问题等。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JAVA相关基础知识 1、面向对象的特征有哪些方面 1.抽象: 抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节。抽象包括两个方面,一是过程抽象,二是数据抽象。 2.继承: 继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法。对象的一个新类可以从现有的类中派生,这个过程称为类继承。新类继承了原始类的特性,新类称为原始类的派生类(子类),而原始类称为新类的基类(父类)。派生类可以从它的基类那里继承方法和实例变量,并且类可以修改或增加新的方法使之更适合特殊的需要。 3.封装: 封装是把过程和数据包围起来,对数据的访问只能通过已定义的界面。面向对象计算始于这个基本概念,即现实世界可以被描绘成一系列完全自治、封装的对象,这些对象通过一个受保护的接口访问其他对象。 4. 多态性: 多态性是指允许不同类的对象对同一消息作出响应。多态性包括参数化多态性和包含多态性。多态性语言具有灵活、抽象、行为共享、代码共享的优势,很好的解决了应用程序函数同名问题。 2、String是最基本的数据类型吗? 基本数据类型包括byte、int、char、long、float、double、boolean和short。 java.lang.String类是final类型的,因此不可以继承这个类、不能修改这个类。为了提高效率节省空间,我们应该用StringBuffer类 3、int 和 Integer 有什么区别 Java 提供两种不同的类型:引用类型和原始类型(或内置类型)。Int是java的原始数据类型,Integer是java为int提供的封装类。Java为每个原始类型提供了封装类。 原始类型封装类 booleanBoolean charCharacter byteByte shortShort intInteger longLong floatFloat doubleDouble 引用类型和原始类型的行为完全不同,并且它们具有不同的语义。引用类型和原始类型具有不同的特征和用法,它们包括:大小和速度问题,这种类型以哪种类型的数据结构存储,当引用类型和原始类型用作某个类的实例数据时所指定的缺省值。对象引用实例变量的缺省值为 null,而原始类型实例变量的缺省值与它们的类型有关。 4、String 和StringBuffer的区别 JAVA平台提供了两个类:String和StringBuffer,它们可以储存和操作字符串,即包含多个字符的字符数据。这个String类提供了数值不可改变的字符串。而这个StringBuffer类提供的字符串进行修改。当你知道字符数据要改变的时候你就可以使用StringBuffer。典型地,你可以使用StringBuffers来动态构造字符数据。 5、运行时异常与一般异常有何异同? 异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误。java编译器要求方法必须声明抛出可能发生的非运行时异常,但是并不要求必须声明抛出未被捕获的运行时异常。 6、说出Servlet的生命周期,并说出Servlet和CGI的区别。 Servlet被服务器实例化后,容器运行其init方法,请求到达时运行其service方法,service方法自动派遣运行与请求对应的doXXX方法(doGet,doPost)等,当服务器决定将实例销毁的时候调用其destroy方法。 与cgi的区别在于servlet处于服务器进程中,它通过多线程方式运行其service方法,一个实例可以服务于多个请求,并且其实例一般不会销毁,而CGI对每个请求都产生新的进程,服务完成后就销毁,所以效率上低于servlet。 7、说出ArrayList,Vector, LinkedList的存储性能和特性 ArrayList和Vector都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢,Vector由于使用了synchronized方法(线程安全),通常性能上较ArrayList差,而LinkedList使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。 8、EJB是基于哪些技术实现的?并说出SessionBean和EntityBean的区别,StatefulBean和StatelessBean的区别。 EJB包括Session Bean、Entity Bean、Message Driven Bea

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值