在深入理解 spring 框架之事务管理我们讲解了 spring 事务的管理,只涉及到单数据源的问题,无论业务系统发生了任何异常,数据库都能有效的保证数据的一致性。然而现在微服务盛行的时代,业务系统不单单只与一个数据源打交道,还会和其他业务系统打交道,其他业务系统可以理解为另外一套数据库。
我们将"业务系统"为协调者,数据库以及其他业务系统为"参与者"。当协调者在整个事务过程中,将参与者 A 提交了事务,然而参与者 B 提交事务失败。这时协调者需要将参与者 A 需要回滚,然而回滚失败,协调者该如何去处理该异常场景。例如,协调者向参与者 B 提交事务超时,参与者是否已经提交了事务,还是未提交事务;这个就是微服务常见的分布式事务问题。
在数据量暴增,访问量很大的情况下,往往一个单机服务器是无法有效的提供服务的;以及当服务器宕机后无法提供服务。这时就提出了这么分布式系统这个概念,在《分布式系统概念与设计》一书中,对分布式系统做了如下定义:分布式系统是一个硬件或软件组件分布在不同的网络计算机上,彼此之间仅仅通过消息传递进行通信和协调的系统。基于该分布式系统,衍生出了分布式事务这个专业术语。
CAP 理论有三个特性
基于无法满足 CAP 条件,提出了最终一致性解决方案。其摒弃短暂的不可用。现在说的 paxos 算法以及 raft 算法都是基于这样子理论进行设计,这纯属个人理解。
分布式事务的解决方案。
分布式事务中涉及到几个关键的概念,X/Open 组织(即现在的 Open Group )定义了分布式事务处理模型。 X/Open DTP 模型( 1994 )包括应用程序( AP )、事务管理器( TM )、资源管理器( RM )、通信资源管理器( CRM )四部分。一般,常见的事务管理器( TM )是交易中间件,常见的资源管理器( RM )是数据库,常见的通信资源管理器( CRM )是消息中间件。
两阶段提交协议分为两个阶段进行,第一阶段是预处理阶段,也就是发送 SQL 语句,业务系统校验数据库返回的响应进行校验。第二阶段是提交阶段,也就是发送 commit 指令给数据库。
无论最后结果如何,第二阶段都结束当前事务。
两阶段有如下几个缺点:
其在工程中很少使用,但其有一定的参考性;
基于两阶段的问题,提出了三阶段提交协议,主要的改动点:
三阶段提交提交协议,分为三个阶段,CanCommit 阶段、PreCommit 阶段、DoCommit 阶段。
简单理解即可,在日常的开发中,很少使用;我们重点了解 TCC 协议
TCC 是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(回滚)操作。它分为三个阶段:
补充:
问题:
需业务场景需要,是否需要回滚,还是依然继续提交。
在业务系统中需要记录该事务的执行状态以及子事务的执行状态;当发现超时或者宕机时,可以从中回复执行;
改善点:
在 Confirm 阶段、Cancel 阶段是否需要异步执行,可以有效的提供响应速度。
通过异步的形式,或者采用第三方组件将请求确保将消息发送给子业务系统。其需要评估该请求在子业务必须执行成功的;否则子业务系统执行失败,主业务系统还需要进行回滚,逻辑将会变得复杂;什么情况下,会采用该机制呢?主要是发短信等必须执行成功的场景下,才会采用该方法;
将请求保存到本地数据库,交由后台程序异步将请求推送给子业务系统。一般来讲,设计重试 3 次即可,当连续发送三次失败,即通知运维人员进行跟进处理,具体什么原因导致;如果涉及不限次数的发送,那么就会导致业务系统压力过大;
采用 MQ,redis 等中间件,事务性的将请求推送子业务系统;前提下,需要确保该中间件是否支持事务性操作,如不支持,需业务系统评估如何才能达到事务性推送。例如 RocketMQ 消息中间,是支持事务性操作,其原理是将消息保存到临时队列,当业务系统 commit 操作时,才将该消息从临时队列移出到目标队列。
该算法中有三种角色,follower(跟随着)、candidate(候选者)、Leader(领导者)
其主要有两个环节,第一个环节是选举 Leader;第二环节是副本拷贝。
当集群中,没有 Leader 时或者 Leader 宕机时,有资格的 follower 就会变为 candidate,接着会竞聘 Leader。谁先发起竞聘,谁就有限当 Leader。至于谁先发送,其给每个 candidate 设置一个随机时间,当时间一到还没收到其他 candidate 竞聘请求的,则其会向各个 candidate 发送竞聘请求;超过半数,自动变为 Leader。
当客户端发送请求过来时,有 Leader 接收处理,并广播给其他 follower;当收到半数的回复成功时,即可确认该请求处理成功。
多节点之间对提案达成共识,意味着大多数节点都接受该提案。
Proposer::提议者, 专门用来发出提案(一个值);当超过半数,就认为该提案被选定的。
Acceptor:接收者,专门用来接收提案,并判断是否通过该提案;
Learner:学习者,用来学习被选定的提案(个人理解,可有可无)。
其简单的流程图如下,这里不考虑异常,竞争下的场景;
在 prepare 阶段,Proposer 向 Acceptor 咨询自己是否有提议权(或者发表权)。一旦超过半数 Acceptor 回复 OK,则进入 accept 第二阶段。Proposer 发发送 accept 请求,超过 Acceptor 半数回复 OK。则结束这次提议。至于 Learner 通过谁去学习这个【proposal-value】,
如果 Acceptor 没有批准过提议权的,则无保留意见批准,并记录该 minProposal=【proposal-id】。
如果 Acceptor 之前已经批准过提议权 minProposal,会与现在收到的提议权携带的【proposal-id】进行比较。minProposal>=proposal-id,则拒绝该提议权;如果 minProposal<proposal-id,则批准该提议权,并记录该 minProposal=【proposal-id】。并将 Acceptor 自身的【proposal-value】以及【minProposal】发给 Proposer。这里留下一个提问,Acceptor 会返回不同的【proposal-value】麽?
prepare 响应中如果有【proposal-value】A,则设置 Proposer 的【proposal-value】为 A,否则自行做决定该值。
如果超过半数回复同意的,则进入第二阶段去发表意见。
否则重新发起提议,发送 prepare 消息获取提议权。
将【proposal-id】与【proposal-value】发送给 Acceptor。
如果 Acceptor 当前的【proposal-ID】A 与这次 accept 请求中的【提议 ID】B 相比较,如果不相等,则回复【NO】。否则设置 Acceptor 的【proposal-value】的值为 accept 请求中的【proposal-value】,并回复【OK】响应。
如果超过半数回复 OK 的,则不用管理其他回复【NO】响应,把该【proposal-value】发送给 Learner 进行学习。否则重新发送 prepare 消息,再进行一次提议;最终【proposal-value】是一致的。
答案:是不会存在多个不同【proposal-value】。因为在 prepare 阶段,如果 Acceptor 已经有【proposal-value】值,Proposal 会拿这个值在 accept 阶段进行发送;如果没有的话,Proposal 会自行生成一个【proposal-value】值到 accept 阶段进行发送;同时在 accpet 阶段,如果 Acceptor 会去校验【proposal-id】是否相同,相同则设置该【proposal-value】值。有点类似乐观锁的机制。
答案:不会。因为该协议是只要超过半数的 Acceptor 在运转,依然可以提供服务。而 Proposal 宕机,Proposal 重启进行下一轮提议或者交由其他 Proposer 提议,最终【proposal-value】是达成一致。
在多个 Proposal 进行提议时,容易对【proposal-id】进行竞争,导致性能低效,极端情况下,出现活锁现象。以及为了达成共识,需要先 prepare 阶段以及 accept 阶段,会有损耗,也是导致性能低效的原因之一。
这个就是基于 basic-paxos 中暴露的问题,提供出来的解决方案。
问题,
只有当 Acceptor 自身发现只接收到一个 Leader 的请求时,就会告知 Leader,让其跳过 prepare 阶段;
兰伯特博士提出比较简单的方式,就是哪个 server 的 ID 最大,谁就是最大。也可以采用 Raft 机制,谁先发起竞聘,谁就是 Leader。
兰伯特博士提出一个并发数参数来短时间避免网络分区问题,当网路分区现象发生时,超过多少笔请求接收完后,网络分区才真正会出现;例如,有 9 个节点,当网络分区发生时,6 个节点互通,而另外 3 个节点互通。当请求过来时,切好落在 3 节点区域,这时该请求是无法通过的,因为当前节点数是 9,需要过半数才能通过;当请求数超过指定的并发数时,该 3 个节点才真正的形成集群,只要超过 2 个节点的通过,即可接收该请求。
有关更多细节讲解的 paxos 文章,可以仔细查阅我引用的文章。
简单写了 paxos 的算法逻辑其目标是解决一致性问题,但还是不知道,一致性是指数据的一致性呢,还是状态的一致性?如果是数据的一致性,那么并发情况下,所有的请求都会到 server 都会变成串行执行,性能肯定是不高;如果说状态的一致性,就说的通,只要各个 server 的状态是一致的,那么表明各个 server 下的数据就是一致的;至于在分布式系统如何使用 paxos 算法,还需要结合请求进行设计流程。这只是我的理解,纯属参考。
我们所说的分布式系统,主要是解决计算、存储数据量的问题。无论是计算还是存储,其归根于如何拆分数据,让其数据均匀的分布在各个节点上;
同时分布式系统也需要高可用,所以需要多副本,从而避免服务宕机,其他副本服务依然提供服务;至于如何保证副本一致性,可以看上面的讲解。
分布式事务,我们主要是采用 TCC 协议,保证事务的一致性;当个别场景可以采用第三方组件,从而减少服务的压力;
领取专属 10元无门槛券
私享最新 技术干货