分布式事务详解与解决方案

理论


CAP

CAP定理(CAP theorem):它指出对于一个分布式计算系统来说,不可能同时满足以下三点:
1、一致性(Consistency):一致性是指写操作后的读操作可以读到最新的数据状态,当数据分布在多个节点上,从任意结点读取到的数据都是最新的状态。
2、可用性(Availability):可用性是指任何事务操作都可以得到响应的结果并且不会出现响应超时或者响应错误的情况发生。
3、分区容忍性(Partition tolerance):
通常分布式系统的各个结点部署在不同的子网上,不可避免的会出现由于网络问题而导致结点之间通信失败,此时仍可以对外提供服务,这叫做分区容忍性。

● CA:如果不要求P(不允许分区),则C(强一致性)和A(可用性)是可以保证的。分区是不可避免的,因此CA的系统更多的是允许分区后各子系统依然保持CA。
● CP:如果不要求A(可用),相当于每个请求都需要在Server之间强一致,而P(分区)会导致同步时间无限延长,如此CP也是可以保证的。很多传统的数据库分布式事务都属于这种模式。
● AP:要高可用并允许分区,则需放弃一致性。一旦分区发生,节点之间可能会失去联系,为了高可用,每个节点只能用本地数据提供服务,而这样会导致全局数据的不一致性。现在众多的NoSQL都属于此类。


两种模型

ACID

      传统关系型数据库具备的四个特性即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。这表明了其具有强一致性和高可用性,但是没有可分区的特点。
原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。(要么都成功要么都失败)
一致性:在事务开始之前和事务结束以后,数据库的完整性限制没有被破坏。
隔离性: 当两个或者多个事务并发访问(此处访问指查询和修改的操作)数据库的同一数据时所表现出的相互关系。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
持久性:在事务完成以后,该事务对数据库所作的更改便持久地保存在数据库之中,并且是完全的。

BASE

      解释:BASE 理论即Basically Available(基本可用) 、Soft-state(软状态)和 Eventually Consistent(最终一致性) 三个短语的缩写。它的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身的业务特点,采用适当的方式来达到最终一致性。
基本可用(Basically Available):
指一个分布式系统的一部分发生问题变得不可用时,其他部分仍然可以正常使用,也就是允许分区失败的情况出现。例如:一个数据库系统部署了很多节点,有可能一两个节点出现了失败,但是整个系统依然是可用的
软状态(Soft State):允许系统中的数据存在中间状态,在不影响系统的整体可用性的情况下,允许存在数据的延迟。

允许一段时间内系统中存在不一致的数据,因为系统可能是分布式的,数据同步需要时间,所有系统的数据一致需要一定时间。
最终一致性(Eventually consistent):系统中的所有数据最终都会达到一致状态,但不保证在任何时间点都能立即访问到最新的数据。

一致性解决方案


强一致性


一致性算法Paxos

阶段1

~阶段1a:准备(Prepare)
提议者(Proposer)创建一个消息,这我们称为“准备(Prepare)”消息,由一个数字n标识。注意n不是要提议的值,也不是可能达成一致意见的值,它只是一个数字,唯一标识由该提议者创建的这个初始消息(会被发送给接收者的消息)。这个数字n必须大于之前由该提议者创建的任意Prepare消息中所使用的数字。然后,提议者将这个包含数字n的Prepare消息发送给一个仲裁组的接收者。注意,这个Prepare消息只包含数字n(即,它不必包含通常由v表示的提议值)。这个提议者判定谁是在这个仲裁组中。如果提议者不能跟至少一个仲裁组的接收者通信,那么它不应该初始化Paxos。

~阶段1b:承诺(Promise)
接收者(Acceptor)等待来自任意提议者的准备消息。如果一个接收者收到了一条准备消息,这个接收者必须查看刚刚接收到的这个准备消息的标识号n。有两种情况:
如果n大于所有之前该接收者收到的来自任意提议者的提议号,那么该接收者必须返回一个消息(称为一个“承诺‘Promise’”)给该提议者,以忽略所有将来有比n小的提议号的提议。如果该接收者在过去某个时间点已接受(accepted)了一个提议,它必须在给这个提议者的回复中包含这个之前的提议号,表示为m,以及对应接受的值,表示为w。
否则(即,n小于或等于该接收者之前接收到的来自任意提议者的提议号),该接收者可以忽略掉这个接收到的提议。这种情况中,接收者没有必要做出回复来保持Paxos的运作。但是,出于优化的目的,发送一个拒绝(Nack-否定)回复会告诉这个提议者他可以停止用提议号n来建立共识的尝试了。

阶段2

~阶段2a:接收(Accept)
如果提议者(Proposer)接收到了来自一个仲裁组中大部分接收者的承诺(Promise)消息,那么它就需要给它的提议设置一个值v(提议值)。如果任意接收者已经在之前接受过提议了,那么它们会把它们的值(包括提议号和提议值)发送给这个提议者。该提议者现在必须要设置它的提议值v,将设置为这些接收者报告的最高提议号相关联的值,我们称之为z。如果目前所有接收者都没有接收过提议,那么该提议者可以选择它原始想要提议的值,为x。
提议者(Proposer)发送一个接收(Accept)消息(n, v)到一个仲裁组的接收者,消息中包含提议值v,以及提议号n(跟之前发送给接收者的Prepare消息的提议号是一样的)。因此这个接收消息要么是(n, v=z)(即接收者之前已经接受过提议),要么是(n, v=x)(即所有接收者之前没有接收过提议)。
这个Accept消息应该被解释为一个“请求”,就像“请接收这个提议”。
译者注:从1b和2a的描述看,可以得出这样的结论——如果有接收者之前已经接受过提议,那么现在的提议者就不能提交自己的提议值了,提交的仍是之前已经被接受的那个值。

~阶段2b:已接收(Accepted)
如果一个接收者接收了来自提议者的Accept消息(n, v),当且仅当在该接收者尚未承诺(Promise,在Paxos协议的阶段1b中)只考虑标识符大于n的提议时,它才必须接受。
译者注:
注意这里描述的“只考虑标识符大于n”,是大于,通常在阶段1b中的Promise消息承诺的是会忽略提议号小于n的提议,而没有承诺“只考虑标识符大于n”。
如果这个接收者尚未承诺(阶段1b)只考虑标识符大于n的提议,那么它应该注册值v(刚刚接收的Accept消息中的值v)为已接收的值(协议的已接收值),并且发送一个已接收(Accepted)消息给该提议者以及每个学习者(学习者通常可能是提议者自己)。
否则,接收者会忽略掉这个Accept消息或者请求。
注意一个接收者会接收多个提议。这是可能发生的,当另外的提议者,它不知道当前这个正在被判定的新值,用一个更高的标识号n来开始了一个新的回合(round)。在这种情况中,该接收者会承诺并且在之后接收了这个新的提议值,尽管它已经在早前接收了另外的值。在存在某些故障的情况下,这些提议甚至可能有不同的值。但是,Paxos协议会保证接收者将最终对单个值达成一致意见。

                


 一致性协议2PC

解释:它可以保证在分布式事务中,要么所有参与进程都提交事务,要么都取消事务,即实现 ACID 的原子性(A)。

流程说明:成功流程:

阶段一:
1.1.事务询问:协调者向所有的参与者发送事务内容,询问是否可以执行事务提交操作,并开始等待各参与者的响应。
1.2.执行事务 (写本地的Undo/Redo日志)
1.3.各参与者向协调者反馈事务询问的响应

阶段二:
2.1发送提交请求:协调者向所有参与者发出 commit 请求。
2.2事务提交:参与者收到 commit 请求后,会正式执行事务提交操作,并在完成提交之后释放整个事务执行期间占用的事务资源。
2.3反馈事务提交结果:参与者在完成事务提交之后,向协调者发送 Ack 信息。
2.4完成事务:协调者接收到所有参与者反馈的 Ack 信息后,完成事务。

流程说明:失败流程:

假如任何一个参与者向协调者反馈了No响应,或者在等待超时之后,协调者尚无法接收到所有参与者的反馈响应,那么就会中断事务

阶段一:
1.1事务询问协调者向所有的参与者发送事务内容,询问是否可以执行事务提交操作,并开始等待各参与者的响应。
1.2执行事务 (写本地的Undo/Redo日志)
1.3各参与者向协调者反馈事务询问的响应

阶段二:
2.1发送回滚请求:协调者向所有参与者发出 Rollback 请求。
2.2事务回滚:参与者接收到 Rollback 请求后,会利用其在阶段一中记录的 Undo 信息来执行事务回滚操作,并在完成回滚之后释放在整个事务执行期间占用的资源。
2.3反馈事务回滚结果:参与者在完成事务回滚之后,向协调者发送 Ack 信息。
2.4中断事务:协调者接收到所有参与者反馈的 Ack 信息后,完成事务中断。

          

 一致性协议3PC

3PC中的超时策略

3PC 要解决 2PC 的参与者阻塞超时问题,在2PC中参与者在协调者不给信号时会长时间阻塞,不释放资源,这样别人也没法处理其他事情,2PC 还是太依赖协调者。
3PC 认为网络超时是普遍发生的情况,如果参与者在一种大概率确定的状态下执行一些动作也是被允许的。

参与者等待 PreCommit 超时
~协调者和参与者之间可能存在较大的网络延时,或者协调者出现故障,或者出现网络分区等情况,参与者并不会一直等待,在超过设定时间之后,参与者就继续做之前的事情了。

参与者等待 DoCommit 超时
~在 CanCommit 和 PreCommit 之后,参与者认为大家都已经是完备的,如果参与者在设定时间内并没有收到协调者的 DoCommit 指令,那么就本地执行提交完成这次事务,因为参与者揣测协调者的意思大概率也是让我们提交,回滚可能和大家不一样,抉择之下参与者选择提交事务。

协调者等待反馈超时
~3PC协调者的等待超时处理和2PC基本上是一样的,无论在哪个阶段超时都认为不具备条件,进行 abort 或者 rollback 操作。


流程说明:

第一阶段:
① 事务询问,协调者向所有的参与者发送一个包含事务内容的请求,询问是否可以执行事务提交操作,并开始等待各参与者的响应。
② 参与者在接收到来自协调者的包含了事务内容的请求后,正常情况下,如果自身认为可以顺利执行事务,则反馈Yes响应,并进入预备状态,否则反馈No响应。

第二阶段:
协调者在得到所有参与者的响应之后,会根据结果有2种执行操作的情况:执行事务预提交,或者中断事务。假如所有参与反馈的都是Yes,那么就会执行事务预提交。若任一参与者反馈了No响应,或者在等待超时后,协调者尚无法接收到所有参与者反馈,则中断事务。

如果是执行事务请求,需要做三步:
① 发送预提交请求:协调者向所有参与者节点发出preCommit请求,并进入prepared阶段。
② 事务预提交:参与者接收到preCommit请求后,会执行事务操作,并将Undo和Redo信息记录到事务日志中。
③ 各参与者向协调者反馈事务执行的结果:若参与者成功执行了事务操作,那么反馈Ack

如果是回滚(Rollback)请求,需要做两步:
① 发送中断请求:协调者向所有参与者发出abort请求。
② 中断事务:无论是收到来自协调者的abort请求或者等待协调者请求过程中超时,参与者都会中断事务

第三阶段:
该阶段做真正的事务提交或者完成事务回滚,参与者会根据接收到协调者的请求,进而判断是需要事务的提交还是回滚。如果出现网络抖动或其他问题,最终导致参与者无法收到协调者发来的请求,针对这种情况,参与者会在等待超时之后,默认认为其他参与者接收时成功的,所以会进行事务的提交。

最终一致性

释义:基于 Base 理论

- 强一致性:在更新操作完成之后,任何后续的访问将返回更新的值。
- 弱一致性:系统不能保证后续访问返回更新的值。需要在一些条件满足之后,更新的值才能返回。从更新操作开始,到系统保证任何观察者总是看到更新的值的这期间被称为不一致窗口。
- 最终一致性:这是弱一致性的特殊形式;最终一致性分布式事务解决方案并不要求参与事务的各节点数据时刻保持一致,允许其存在中间状态,只要一段时间后,能够达到数据的最终一致状态即可

根据更新数据后各进程访问到数据的时间和方式不同,可以区分为:

因果一致性:如果进程A通知进程B它已经更新了一个数据项,那么进程B的后续访问将获取进程A写入的最新值。比如,我通知你,你就能获取后面的更新值,其他进程C、D,我没有通知到你,你就访问不到我刚刚写的值,只能最终能访问到,不是马上就访问到。
● 读己之所写:当进程A自己执行一个更新操作后,它自己总是可以访问更新过的值,不会看到旧值。
单调读一致性:如果进程已经看到过数据对象的某个值,那么任何后续访问都不会返回在那之前的旧值。
会话一致性:它把访问存储系统的这些进程,放到会话的上下文进程中,这个时候只要这些会话存在,系统就可以保证“读自己之所写一致性”。
单调写一致性:系统需保证来自同一进程的写操作按照顺序执行。同一个进程可能发生多个写操作,写操作有先有后,系统必须保证这些写操作,要按照顺序执行。

解决方案


典型方案
TCC 解决方案
释义:

TCC是Try、Confirm、Cancel三个词语的缩写,TCC要求每个分支事务实现三个操作:预处理Try、确认 Confirm、撤销Cancel。Try操作做业务检查及资源预留,Confirm做业务确认操作,Cancel实现一个与Try相反的 操作即回滚操作。TM首先发起所有的分支事务的try操作,任何一个分支事务的try操作执行失败,TM将会发起所有分支事务的Cancel操作,若try操作全部成功,TM将会发起所有分支事务的Confirm操作,其中Confirm/Cancel 操作若执行失败,TM会进行重试。


一个管理器:

TM事务管理器,可以实现为独立服务,或者让全局事务的发起者充当TM,目的是为了成为公用组件,以复用功能。
TM发起全局事务时生成全局事务记录,每条记录有全局事务ID,用来记录事务上下文,追踪和记录状态。由于confirm和cancel需要重试,因此需要幂等性。
● try做业务检查(主要检查一致性)以及资源预留(隔离性)。
● confirm做确认提交,通常认为,confirm不会出错,即只要try成功,confirm必定成功
● cancel阶段在业务执行错误,需要回滚状态下执行对分支事务的业务取消,同时取消预留的资源,通常情况下,也一定成功。


实现框架:Seata
异常处理

● 空回滚
即没有先调用try方法,却直接调用了cancel方法,cancel需要识别出这是一个空的回滚,直接返回成功。
原因:当一个分支事务所在的服务器宕机或网络异常,分支事务调用记录为失败,这时其实没有try,但是故障恢复后,发现失败,进行回滚从而调用了cancel。
解决:关键是得识别出try是否执行了,用一张分支事务记录表,记录全局事务ID以及分支事务ID,每次try完成就往里插入一条记录,以表示try执行了,这时cancel方法只需要调用这个接口看记录表中是否有该记录,有说明执行了,正常回滚,没有则没有try,直接空回滚。

● 幂等
为了保证TCC二阶段提交重试机制不会引发数据不一致,要求TCC的二阶段Try、Confirm和Cancel接口保证幂等,这样不会重复使用或者放资源。如果幂等控制没有做好,很有可能导致数据不一致等严重问题。
解决:在上述 “分支事务记录表”中增加执行状态,每次执行前都查询该状态。

● 悬挂
悬挂就是对于一个分布式事务,其二阶段Cancel接口比Try接口先执行。
原因:RPC调用分支事务try时,先注册这个分支事务,再执行RPC调用,此时如果网络拥堵,会导致RPC调用超时。
超时之后,TM就要通知回滚这个分布式事务,有可能回滚都结束了,RPC请求才刚到需要执行的地方,而try到了之后需要预留资源,只有刚才被回滚的那个事务才能用,那么这个预留的资源就没有人能够处理了。主要导致try操作预留的资源,因为事务已经被回滚,无法继续处理。
解决:如果某个事务二阶段的cancel执行完成,则一阶段try不允许执行。在“分支事务记录表”中查,如果有二阶段cancel记录,则不执行try。

                        
可靠消息最终一致性解决方案

释义:

可靠消息最终一致性方案,其实就是在分布式系统当中,把一个业务操作转换成一个消息,然后利用消息来实现事务的 最终一致性
两种实现方式:基于本地消息表、基于支持分布式事务的消息中间件,如RocketMQ等

基于本地消息表

基于本地消息服务的分布式事务分为三大部分:

● 可靠消息服务:存储消息,因为通常通过数据库存储,所以也叫本地消息表
● 生产者(上游服务):生产者是接口的调用方,生产消息
● 消费者(下游服务):消费者是接口的服务方,消费消息


                            
消息中间件


RocketMQ为例,消息的发送分成2个阶段: Prepare阶段 和 确认阶段 。

1 prepare阶段
生产者发送一个不完整的事务消息——HalfMsg到消息中间件,消息中间件会为这个HalfMsg生成一个全局唯一标识,生产者可以持有标识,以便下一阶段找到这个HalfMsg;
生产者执行本地事务。
注意: 消费者无法立刻消费HalfMsg,生产者可以对HalfMsg进行Commit或者Rollback来终结事务。只有当Commit了HalfMsg后,消费者才能消费到这条消息。

2 确认阶段
如果生产者执行本地事务成功,就向消息中间件发送一个Commit消息(包含之前HalfMsg的唯一标识),中间件修改HalfMsg的状态为【已提交】,然后通知消费者执行事务;
如果生产者执行本地事务失败,就向消息中间件发送一个Rollback消息(包含之前HalfMsg的唯一标识),中间件修改HalfMsg的状态为【已取消】。
消息中间件会定期去向生产者询问,是否可以Commit或者Rollback那些由于错误没有被终结的HalfMsg,以此来结束它们的生命周期,以达成事务最终的一致。之所以需要这个询问机制,是因为生产者可能提交完本地事务,还没来得及对HalfMsg进行Commit或者Rollback,就挂掉了,这样就会处于一种不一致状态。

3 ACK机制
消费者消费完消息后,可能因为自身异常,导致业务执行失败,此时就必须要能够重复消费消息。RocketMQ提供了ACK机制,即RocketMQ只有收到服务消费者的ack message后才认为消费成功。
所以,服务消费者可以在自身业务员逻辑执行成功后,向RocketMQ发送ack message,保证消费逻辑执行成功。

                            
最大努力通知型解决方案

释义:

最大努力通知型的目标是 事务发起方尽量将业务处理结果通知到参与方。适用于一些最终一致性时间敏感度低,且参与方的处理结果不影响发起方的处理结果的这类通知类的业务场景。
特点
~ 消息重复通知:消息发送失败后在配置的条件中重复发送消息给小费者
~ 定期校对:消费者定期校对消息接收状态,并能够主动消费消息进行补偿

                        
服务模式
可查询操作

        可查询操作服务模式需要服务的操作具有可标识性,主要体现在服务的操作具有全局唯一的标识,可以是业务的单据编码(如订单号),也可以是系统分配的操作流水号(如支付产生的交易流水号)。另外,在可查询的服务模式中,也要有完整的操作时间信息。

幂等操作

         要求操作具有幂等性。幂等性是数学上的概念,指的是使用相同的参数执行同一个方法时,无论执行多少次,都能输出相同的结果。在编程中,幂等性指的是对于同一个方法来说,只要参数相同,无论执行多少次都与第一次执行时产生的影响相同。

TCC 操作

          主要包括 3 个阶段,分别为 Try 阶段(尝试业务执行)、Confirm 阶段(确定业务执行)和 Cancel 阶段(取消业务执行)

可补偿操作

         如果某些数据处于不正常的状态,需要通过某种方式进行业务补偿,使数据能够达到最终一致性,这种因数据不正常而进行的补偿操作,就是可补偿操作服务模式


事务隔离级别

ISOLATION_DEFAULT
使用数据库默认的事务隔离级别.

ISOLATION_READ_UNCOMMITTED(读未提交)
事务尚未提交,其他事务即可以看到该事务的修改结果。

ISOLATION_READ_COMMITTED(读已提交)
保证一个事物提交后才能被另外一个事务读取。另外一个事务不能读取该事物未提交的数据。

ISOLATION_REPEATABLE_READ(可重复读)
一个事务操作中对于一个读取操作不管多少次,读取到的结果都是一样的。

ISOLATION_SERIALIZABLE(串行化)
事务如同按照顺序执行一样,但代价是性能降低,因为需要锁定所有读取的行。


        


Spring事务传播机制

Spring事务的传播机制有以下7种:

Propagation.REQUIRED:默认的事务传播机制,他表示如果当前存在事务,则加入事务;如果当前没有事务,则创建一个新的事务

Propagation.SUPPORTS:如果当前存在事务,则加入事务;如果当前没有事务,则以非事务的方式继续运行

Propagation.MANDATORY:(mandatory强制性)如果当前存在事务,则加入事务;如果当前没有事务,则抛出异常

Propagation.REQUIRES_NEW:表示创建一个新的事务,如果对当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且启动的事务互相独立,互不干扰

Propagation.NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起

Propagation.NEVER:以非事务方式运行,如果当前存在事务,则把当前事务挂起

Propagation.NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于PROPAGATION_REQUIRED

事务传播分为3类:

支持当前事务:REQUIRED + SUPPORTS + MANDATORY
不支持当前事务:REQUIRES_NEW  +  NOT_SUPPORTED  +  NEVER
嵌套事务 : NESTED

事务解释:

嵌套事务,可以实现部分事务回滚,也就是说回滚时,一直往上找调用它的方法和事务回滚,而不会回滚嵌套之前的事务
加入事务,相当我已经成了他的一部分,回滚时,整个一起回滚
整个事务如果全部执行成功,二者的结果是一样的
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值