分布式系统是一个硬件或软件组件分布在不同的网路计算机上,彼此之间仅仅通过消息传递进行通信和协调的系统。
由于网络本身的不可靠性,出现消息丢失、消息延迟
由于网络发生异常情况,导致分布式系统中部分节点之间的网络延迟不断增大,最终导致组成分布式系统中有部分节点能够正常通信,网络之间出现了网络不连通,但各个子网络的内部网络是正常的,从而导致整个系统的网络环境被切分成了若干个孤立的区域,分布式系统就会出现局部小集群,在极端情况下,这些小集群会独立完成原本需要整个分布式系统才能完成的功能,包括数据的事务处理,这就是对分布式一致性提出非常大的挑战。
分布式系统每一次请求与响应存在特有的“三态”概念,即成功、失败和超时。在分布式系统中,由于网络是不可靠的,虽然绝大部分情况下,网络通信能够接收到成功或失败的响应,但当网络出现异常的情况下,就会出现超时现象,通常有以下两种情况:
1.由于网络原因,该请求并没有被成功的发送到接收方,而是在发送过程就发生了丢失现象。
2.改请求成功的被接收方接收后,并进行了处理,但在响应反馈给发送方过程中,发生了消息丢失现象。
节点故障是分布式系统下另一个比较常见的问题,指的是组成分布式系统的服务器节点出现的**宕机或“僵死”**现象,根据经验来说,每个节点都有可能出现故障,并且经常发生
在分布式系统中要解决的一个重要问题就是数据的复制。在我们的日常开发中,相信有很多人遇到过这样的问题:假设客户端C1将系统中的一个值K由V1更新为V2,但客户端C2无法立即读取到最新值,需要在一段时间之后才能读取到,这个例子就是常见的数据库之间复制延迟问题。
分布式系统对于数据的复制需求一般都来自于一下两个原因:
数据复制在可用性和性能方面带来的好处不言而喻,然后数据复制带来的一致性挑战也是不得不面对。
数据一致性是指对一个副本数据进行更新的时候,必须确保也能够更新其他的副本,否则不同副本之间的数据将不一致。
那么如果解决这个问题?一种思路就是“既然是由于延时动作引起的问题,那我们可以将写入的动作阻塞,知道数据复制完成后,才完成写入动作”,一些系统架构也是这样设计的,但是这种思路之后会引入新的问题:写入的性能。如果你的应用场景有非常多的写请求,那么使用这个思路以后,后续的写请求都将会阻塞在前一个请求的写操作上,导致系统整体性能急剧下降。
总得来说,我们无法找到一种能够满足分布式系统所有系统属性的分布式一致性解决方案。那么如何保证数据的一致性,又能够不影响系统运行的性能,是每一个分布式系统都需要重点考虑和权衡的,于是一致性级别由此诞生:
这种一致性级别是最符合用户直觉的,它要求系统写入什么,读出来的也会是什么,用户体验好,但实现起来往往对系统的性能影响大。
优点:用户体验好
缺点:性能开销大
这种一致性级别约束了系统在写入成功后,不承诺立即可以读到写入的值,也不承诺多久之后数据能够达到一致,但会尽可能保证到某个时间级别后,数据能够达到一致性状态
最终一致性是弱一致性的一个特例,系统会保证在一定时间内,能够达到一个数据一致的状态。这里之所以最终一致性单独提出来,是因为它是弱一致性中非常推崇的一种一致性模型,也是业界在大型分布式系统的数据一致性上比较推崇的模型。
在单机数据库中,我们很容易实现一套满足ACID特性的事务处理系统,但在分布式数据库中,数据分散在各台不同的机器上,如何对这些数据进行分布式事务处理具有非常大的挑战。
分布式事务:是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于分布式系统的不同节点上,通常一个分布式事务中会涉及对多个数据源或业务系统的操作。
可以设想一个最典型的分布式事务场景:一个跨银行的转账操作涉及调用两个异地的银行服务,其中一个是本地银行提供的取款服务,另一个则是目标银行提供的存款服务,这两个服务本身是无状态并且相互独立的,共同构成了一个完整的分布式事务。如果从本地银行取款成功,但是因为某种原因存款服务失败了,那么就必须回滚到取款之前的状态,否则用户可能会发现自己的钱不翼而飞了。
从这个例子可以看到,一个分布式事务可以看做是多个分布式的操作序列组成的,例如上面例子的取款服务和存款服务,通常可以把这一系列分布式的操作序列称为子事务。
因此,分布式事务也可以被定义为一种嵌套型的事务,同时也就具有了ACID事务特性。但由于在分布式事务中,各个子事务的执行是分布式的,因此要实现一种能够保证ACID特性的分布式事务处理系统就显得格外复杂,尤其是对于一个高访问量,高并发的互联网分布式系统来说
如果我们期望实现一套严格满足ACID特性的分布式事务,很可能出现的情况就是在系统的可用性和严格一致性之间出现冲突…因为当我们要求分布式系统具有严格一致性时,很可能就需要牺牲掉系统的可用性。但毋庸置疑的一点是,可用性又是一个消费者不允许我们讨价还价的系统属性,比如像淘宝这样的在线购物网站,就要求7x24小时不间断地对外提供服务,而对于一致性,则更加是所有消费者对于一个软件的刚需。
因此,在可用性和一致性之间永远无法存在一个两全其美的方案,于是如何构建一个兼顾可用性和一致性的分布式系统成为了无数开发人员探讨的难题,于是就出现了CAP和BASE这样的分布式系统经典理论
CAP理论告诉我们,一个分布式系统不可能同时满足一致性(Consistency)、可用性(Avaliability)、分区容错性(Partition)这三分基本需求,最多只能同时满足其中2两个
选项 | 描述 |
---|---|
一致性 | 分布式环境中,数据在多个副本之间能够保持一致的特性(严格一致性),在一致性的需求下,当一个系统在数据一致性的状态下执行更新操作后,应该保证系统的数据依然处在一直的转态 |
可用性 | 系统提供的服务必须一直处于可用的状态,每次请求都能获取到非错的响应–但不保证获取的数据为最新数据 |
分区容错性 | 分布式系统在遇到任何网络分区故障的时候,依然能够对外提供满足一致性和可用性的服务,除非整个网络环境都发生了故障 |
首先,能不能同事满足这三个条件?
假设有一个系统如下:
整个系统由两个节点配合组成,之间通过网络通信,当节点A进行更新数据库操作的时候,需要同时更新节点B的数据库(这是一个原子操作)
上面这个系统怎么满足CAP呢?C(一致性):当节点A更新的时候,节点B也要更新, A(可用性):必须保证两个节点都是可用的, P(网络分区:由于网络故障导致各个机器不能通讯,又可能其他机器又在通信,这样就造成了网络划分成多个子网络)当节点A、B出现了网络分区,必须保证对外可用。
可见,根本完成不了同时满足CAP,因为只要出现了网络分区,C就无法满足,因为节点A根本连接不上节点B。如果强行满足C一致性,就必须停止服务运行,从而放弃可用性A
所以,最多满足两个条件:
组合 | 分析结果 |
---|---|
CA(一致性+可用性) | 放弃分区容错性,说白了,就是一个整体的应用,如果希望能够避免系统出现分区容错性问题,一种较为简单的做法是将所有的数据(或者仅仅是那些与事务相关的数据)都放在一个分布式节点上。这样做虽然无法100%保证系统不会出错,但至少不会碰到由于网络分区带来的负面影响。但同时需要注意的是,放弃P的同时也就意味着放弃了系统的可拓展性 |
CP(一致性+分区容错性) | 一旦系统遇到网络分区或其它故障或为了保证一致性时,放弃可用性,那么受到影响的服务需要等待一定的时间需要等网络修复好以后才能继续提供服务,因此在等待期间系统无法对外提供正常的服务,即不可用 |
AP(可用性+分区容错性) | 出现网络分区,为了保证可用性,必须让节点继续对外提供服务,这样必然失去一致性。这里所说的放弃一致性,并不是完全不需要数据一致性,指的是放弃系统的强一致性,保留最终一致性。这样的系统无法保证数据保持实时的一致性,但是能够承诺的是,数据最终会达到一个一致的状态这就引入了一个时间窗口的概念,具体多久能够达到数据一致性的状态取决于系统的设计,主要包括数据副本在不同节点之间的复制时间长短。 |
想要解决3选2的问题,首先大家需要思考分区是100%出现的?如果不出现分区,那么就能够同时满足CAP。如果出现了分区,可以根据策略进行调整。比如C不必使用那么强的一致性,可以先将数据存在一起,稍后再更新,实现所谓的"最终一致性"
基于这个思路,引出了第二个理论Base理论
BASE:全称:Basically Available(基本可用),Soft state(软状态),和Eventually consistent(最终一致性)三个短语的缩写,来自eBay架构师提出。
Base理论是对CAP中一致性和可用性权衡的结果,其来源于对大型互联网分布式实践的总结,是基于CAP定理逐步演化而来的。
其核心思想:既然无法做到强一致性(String consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency )
什么是基本可用呢?
基本可用是指分布式系统在出现不可预知故障的时候,允许损失部门可用性-但不等于系统不可用。以下就是两个“基本可用”的例子
为了使系统尽量能够达到CAP,于是有了BASE协议,而BASE协议是在可用性和一致性之间做的取舍和妥协。
也就是说,我们在对分布式系统进行架构设计的过程中,往往需要我们在系统的可用性和数据一致性之间反复的权衡。于是,就涌现了许多经典的算法和协议,最著名的几种就是二阶段提交协议、三阶段提交协Paxos算法等。
在分布式系统中,会有多个机器节点,每一个机器节点虽然能够明确地知道自己在进行事务操作过程中的结果是成功或失败,但无法直接获取到其他分布式节点的操作结果,因此当一个事务操作需要跨越多个分布式节点的时候,为了保证事务处理的ACID特性,就需要引入一个“协调者”的组件来统一调度所有分布式节点的执行逻辑,这些被调度的节点则称为“参与者”,协调者负责调度参与者的行为,并最终决定这些参与者是否要把事务真正进行提交。基于这个思想,就衍生了二阶段提交和三阶段提交两种协议。
协议说明:
二阶段提交就是将事务的提交过程分成了两个阶段来进行处理,流程如下:
协调者向所有的参与者发送事务内容,询问是否可以执行事务提交操作,并开始等待其他参与者的响应。
各参与者节点执行事务操作,并将Undo和Redo信息记入事务日志中(Undo能保证事务的一致性,Redo用来保证事务的原子性和持久性,两者也是系统恢复的基础前提)
如果参与者成功执行了事务操作,那么就反馈给协调者Yes响应,表示事务可以执行;如果参与者没有成功执行事务,就返回No给协调者,表示事务不可以执行。
由于上面的内容在形式上近似是协调者组织各参与者对一次事务操作的投票表态过程,因此二阶段提交协议的阶段一也被称为“投票阶段”,即各参与者投票表明是否要继续执行接下去的事务提交操作。
在阶段二中,就会根据阶段一的投票结果来决定最终是否可以进行事务提交操作,正常情况下,包含两种操作可能:提交事务、中断事务。
假如协调者从所有的参与者获得的反馈都是yes响应,那么就会执行事务提交。
协调者向所有参与者发出commit请求。
参与者收到commit请求后,会正式执行事务提交操作,并在完成提交之后释放整个事务执行期间占用的事务资源。
参与者在完成事务提交之后,向协调者发送ACK信息。
协调者接收到所有参与者反馈的ACK信息后,完成事务。
ACK:ACK字符是一些通信协议下用来做确认消息的字符,也有通信协议使用其他字符。
假如任何一个参与者向协调者反馈了No响应,或者在等待超时之后,协调者尚无法接受到所有参与者的反馈响应,那么就会中断事务。
协调者向所有参与者发出Rollback请求。
参与者接受到Rollback请求后,会利用其在阶段一记录的Undo记录来执行事务回滚操作,并在完成回滚之后释放在整个事务执行期间占用的资源。
参与者在完成事务回滚之后,向协调者发送ACK信息。
协调者接收到所有参与者反馈的ACK信息后,完成事务中断。
从上面逻辑可以看出,二阶段提交就做了两件事情:投票、执行。
原理简单,实现方便
同步阻塞,单点问题数据不一致,过于保守
扩展讨论
网络中存在各种不确定性,其中协议流程是基于时间顺序的(时间),而协议又涉及到不同的参与者(空间),时空交错,任一点都有可能出现意外,因此我们可以通过时间和空间对协议流程进行展开讨论:
阶段一:协调者出问题、参与者正常
假设只有部分节点收到协调者的询问并给予协调者反馈了,协调者此时除了问题,无法继续询问,这些部分已经参与询问的参与者可以放弃事务,相当于全局事务回滚。
阶段一:协调者正常、部分参与者出问题
假设有一部分参与者不可用,表现为无法响应或者响应No,协调者知道这件事情,因此可以通知其他参与者放弃事务,同时其他事务也可以自行放弃事务,相当于全局事务回滚。
阶段一:协调者和参与者都出问题
假设部分正常的参与者如果接受过询问,此时协调者出问题了,其他部分参与者也出问题了,则正常已经参与过询问的参与者可以放弃事务,相当于全局事务回滚。
阶段二:协调者出问题、参与者正常;
假设进入第二阶段,部分参与者已经提交了,此时协调者出问题了,此时部分提交的参与者,造成了全局状态的不一致的问题。
阶段二:协调者正常、参与者出问题
假设进入第二阶段,协调者通知完部分参与者提交了之后发现另一部分参与者不可用,此时一部分参与者已经提交了,造成了全局状态的不一致问题。
阶段二:协调者和参与者都出问题
此时部分提交的事务仍然造成了全局状态的不一致。
上面可以看出在2PC进入第二阶段之后,无论是协调者出问题还是参与者出问题,都会造成全局资源状态的不一致问题,那么为什么会造成这种问题呢?比如协调者出问题了、部分参与者已经提交的情况,其余未提交的参与者并不知道这部分已经提交的参与者是什么状况,因为对于这部分未提交的参与者来说,协调者已经跪了,它们无法知道全局事务的状态,而且剩下的参与者的状况有以下三种可能:
因为有太多不确定性,因此对于这部分未提交的参与者来说什么都不能做,因为做什么都是错的,结果就是出现了全局状态的一致性问题,那么从这个角度上来讲,三阶段提交协议减少这种不确定性,后面会讲到。
(202001430继续更新)
刚刚讲解了二阶段提交协议的设计和实现原理,并明确指出了其在实际运行过程中可能存在的诸如同步阻塞,单点问题,数据不一致,过于保守的容错机制等缺陷
而为了弥补二阶段提交的缺点,引入了三阶段提交协议。
将2PC的“提交事务请求”过程一分为二,共形成了由CanCommit、PreCommit和doCommit三个阶段组成的事务处理协议。
两种情况:
成功:执行事务预提交
失败:中断事务
发送预提交请求
协调者向所有参与者发出PreCommit请求,进入准备阶段
事务预提交
参与者收到请求后,执行事务操作,并将Undo、Redo信息记录到事务日志中(改日志信息可以用来回滚事务)
反馈执行结果
协调者收到反馈,确定是提交(发送doCommit请求)还是终止(发送abort请求)操作。
提交事务(收到doCommit请求将预提交状态->提交状态)、中断事务(收到abort请求),完成事务以后发送ACK给协调者。
优点:
在2pc基础上有协调者新增了一个CanCommit阶段,会预先判断机器是否可以执行事务操作,而不是直接发送执行事务操作请求,这样做的优点是降低了参与者的阻塞范围(由于2pc是直接发送prepare,等待参与者事务处理完成并反馈结果),其次能够在单点故障后达成一致(由于第一阶段是判断服务器是否能够CanCommit,协调者会根据反馈结果判断有故障节点情况下,发送事务prepareCommit请求操作)
缺点:
如果参与者收到了PreCommit消息后,出现了网络分区,此时协调者和参与者无法通信,参与者等待超时后,会进行事务的提交(这种超时自动提交机制是3pc特性,就是为了解决同步阻塞情况),这必然出现分布式数据不一致问题
首先对于协调者和参与者都设置了超时机制(在2pc中,只有协调者拥有超时机制,即如果在一定时间内没有收到参与者的消息则默认失败)。其次在2pc的准备阶段和提交阶段之间,插入预提交阶段,这个阶段是一个缓冲,保证了在最后提交之前各参与节点的状态是一致的。
paxos算法需要解决的是如何在发送机器宕机、网络异常等异常的分布式系统中,快速且正确地在集群内部对某个数据的值(不单是某个数,可表示一条日志、命令等)达成一致,并且保证不论发生任何异常都不会破坏整个系统的一致性
在基于教案的理解基础上纯手打有点浪费时间,明天继续补充!!