分布式事务相关
1. 事务
1.1 事务(本地事务)
事务是一个操作序列,它包含了一组数据库操作命令,并把这些操作命令作为一个整体,同时这组命令要么全部执行,要么全都不执行。它是一个不可分割的执行单位。
事务遵循以下4个特性
原子性:一个事务中的所有操作,要么全部提交成功,要么全部失败回滚。一致性:事务执行前后数据的完整性必须保持一致。即,数据库总是从一个一致性状态转换到另一个一致性状态。隔离性:在并发环境中,一个事务的执行不会被其他事务干扰。持久性:事务一旦提交,它对数据库的修改是永久性的。
1.2 分布式事务
在大多数场景下,我们应用只需要操作单一数据库,这种情况下的事务称之为本地事务,本地事务ACID特性是由数据库直接提供。
随着业务不断地扩增,不可避免的是需要由单一服务向服务拆分的方向发展,往往完成一个业务需要横跨多个服务,操作多个数据库。需要操作的资源位于多个资源服务器上,而应用需要保证位于多个资源服务器的数据,要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不通资源服务器的数据一致性的问题。
2. 什么场景需要分布式事务
- 跨库事务
- 分库分表
- 服务化(SOA)
2.1 CAP理论
CAP理论是分布式架构中提出的一种设计思想理论。
其中CAP分别表示:
C:一致性:表示一个节点的数据做了修改,那么之后其他所有节点读到这个数据,得到的一定都是最新的数据。
A:可用性:表示分布式系统中的某些节点挂掉了,不影响整个服务的使用。
P:分区容错性:分布式系统出现网络分区的时候,仍然可以对外提供服务。
CAP无法同时满足三者,最多只能三选二。
因为分布式系统中网络是不可靠的,所以分区容错性是必选项,也就是P是必选的。
所以在P的基础上C和A二选一,组成CP或者AP。
由于CAP理论是强一致性的,对系统要求比较高,所以可以根据自身业务特性,选择退而求其次的方案:BASE。
2.2 BASE理论
BASE理论本质上是对CAP的延迟和补充,是对CAP的AP方案的一个补充,即在选择AP的方案情况下,如何更好地最终达到C(一致性)。
其中BASE分别表示:
BA:Base Available:基本可用,这意味着即使在分布式系统中遇到故障的一部分,系统仍然应该能够提供服务,虽然可能是降级的服务,但是核心功能依旧能够运行。例如,一个网站在高负载下可能显示简化的页面,或者一个服务可能暂时减少其功能集,但是仍然保持可用。
S:Software State:分布式系统中的数据可能处于中间状态,即数据在不同节点可能暂时不一致。这是因为系统允许数据在更新过程中存在延迟,这种延迟是由于网络延迟、数据复制时间或者其它因素造成的。软状态接受数据可能会短暂不一致的现象,但是这种状态在系统整体的运行中这是可以接受的。
E:Eventually Consistent:最终一致性。即使数据在短时间内可能不一致,分布式系统中的所有数据副本最终会到达一致性的状态,这通常意味着,一旦所有正在进行的更新完成,所有节点将反映相同的数据版本。最终一致性依赖于系统的自我修复机制,如数据复制和同步过程。
BASE理论通常用于设计大规模的分布式系统,其中强一致性可能难以维持,特别是在面对高并发和地理分布的情况。通过采用BASE理论,系统设计者可以构建更加弹性、高可用的系统,即使在面对网络分区和其他故障时,也能保持系统的运行和服务的连续性。
2.3 一致性的分类
- 强一致性
- 弱一致性
- 最终一致性
3. 分布式事务常用解决方案
3.1 两阶段提交(2PC)
3.1.1 2PC的构部分:
- 本地资源管理器(参与者):往往由本地数据库实现。
- 事务管理器(协调者):作为全局的调度者,负责各个本地资源的提交和回滚。
3.1.2 2PC的实现原理:
1) 阶段一:准备阶段
询问各个本地资源管理器是否准备好,投票阶段。
- 协调者向所有参与者发送commit请求,询问是否可以提交事务,并等待答复。
- 各参与者开始准备执行事务,将uodo log和redo log记入事务日志中,并不提交事务。
- 如果参与者执行成功,则向协调者返回yes,否则返回no。
2) 阶段二:提交阶段
协调者收到各个参与者的准备信息后,根据反馈情况,通知各个参与者Commit或者Rollback。
(1) 事务提交
当第一阶段所有参与者都反馈同意时,协调者发起正式提交事务请求,当所有的参与者都回复成功,则表明完成事务,具体流程如下:
- 协调者向所有参与者发送正式提交事务请求(即:commit请求)。
- 参与者收到协调者的commit请求后,参与者正式执行事务提交操作,并释放整个事务期间占用的资源。
- 参与者完成事务提交后,向协调者发送ACK消息。
- 协调者收到所有参与者反馈的ACK消息后,完成事务。
所以,整个事务提交流程图如下
(2) 事务回滚
如果任意一个参与者在第一阶段返回中止信息,或者由于超时协调者无法获取到所有参与者的信息,那么这个事务将会被回滚,具体流程如下:
- 协调者向所有参与者发送回滚请求(即:rollback请求)。
- 参与者收到协调者发送的回滚请求后,参与者使用第一阶段中的undo log信息执行回滚操作,并释放在整个事务期间占用的资源。
- 参与者在执行完回滚操作之后,向协调者发送ACK信息。
- 协调者受到所有参与者反馈的信息后,取消事务。
所以,整个事务回滚流程图如下:
3.1.2 2PC的缺点:
二阶段的确可以提供原子性操作,但是仍有如下缺点:
-
性能问题:所有参与者在提交阶段,都处于同步阻塞状态,占用系统资源,容易导致性能瓶颈。
-
可靠性问题:如果协调者出现单点故障,或者出现不可用状态,参与者将一直处于锁定状态。
-
数据一致性问题:在阶段2中,如果出现协调者和参与者都挂了,有可能导致数据不一致。
3.2 三阶段提交(3PC)
3.2.1 和2PC的区别
与两阶段提交不同的是,三阶段提交有两个改动点:
- 引入超时机制,在协调者与参与者中都引入了超时机制。
- 在第一阶段与第二阶段中,插入了一个
准备阶段。保证了在最后提交阶段之前,各参与节点的状态是一致的。故3PC有CanCommit,PreCommit,DoCommit。
所以3PC处理流程如下:
3.2.2 3PC详解
1) CanCommit阶段
3PC的CanCommit阶段其实和2PC的准备阶段很像。协调者向参与者发送commit请求,参与者如果可以提交就返回yes,否则返回no。
- 事务询问: 协调者向参与者发送CanCommit请求,询问是否可以执行事务提交操作,然后开始等待答复。
- 响应反馈: 参与者接到CanCommit请求后,如果可以顺利执行事务,则反馈yes响应,并进入
预备状态,否则反馈no。
2) PreCommit阶段
协调者根据参与者的反馈情况决定是否可以继续执行事务的PreCommit阶段。
有两种反馈情况:
一、所有的参与者都反馈yes响应,那么就会执行事务的PreCommit阶段。
- 发送预提交请求: 协调者向参与者发送
PreCommit请求,并进入Prepared阶段。 - 预提交事务: 参与者收到PreCommit请求后,会执行事务操作,并将undo和redo信息记录到事务日志中 (但不提交事务) 。
- 响应反馈: 如果参与者都成功了执行事务操作,则返回ack响应,同时开始等待最终命令。
二、加入有任何一个参与者反馈no响应,或者等待超时之后,协调者也没收到参与者的反馈,那么就执行事务中断。
- 发送中断请求: 协调者向所有参与者发送abort请求。
- 中断事务: 参与者收到协调者发送的abort请求后(或者超时后,仍未收到协调者的请求),开始执行事务的中断。
3) DoCommit阶段
该阶段进行真正的事务提交,可以分为如下两种情况:
一、执行事务提交:协调者收到所有参与者的ack信息,开始执行提交事务。
- 发送DoCommit请求:协调者收到参与者发送的ack响应后,那么就会从
预提交状态进入到提交状态,并向所有的参与者发送DoCommit请求。 - 事务提交:参与者接收到协调者发送的DoCommit请求,开始执行事务提交操作,并在完成事务操作后释放所有的事务资源。
- 响应反馈:事务提交完成后,参与者向协调者发送ack信息。
- 事务完成:协调者受到所有参与者的ack信息后,完成事务。
二、中断事务:协调者没能收到所有参与者的ack信息,开始执行中断事务。
- 发送中断请求:协调者性所有的参与者发送abort请求。
- 事务回滚:参与者收到abort请求后,开始利用阶段2中记录的undo log执行事务的回滚操作,并在完成回滚后,释放所有的事务资源。
- 响应反馈:参与者完成事务回滚后,向协调者发送ack信息。
- 中断事务:协调者受到所有参与者的ack信息后,执行事务中断。
在进入DoCommit阶段是,如果协调者或者参与者出现问题,导致参与者无法接收到协调者发出的提交事务/中断事务请求,此时,参与者都会在等待超时之后,继续执行事务提交。这是基于概率来决定,当进入第三阶段时,说明第一阶段,所有的参与者都同意进行修改操作,同时在第二阶段,所有的参与者都统一同意PreCommit操作。所以,如果在第三阶段,如果出现网络问题,虽然参与者没有收到commit/abort请求,但是它有理由相信:成功提交的几率很大。
3.2.3 3PC的优缺点
与2PC相比,3PC降低了阻塞范围,并且在等待超时后,协调者或者参与者中断事务,避免了协调者单点问题,阶段3中协调者出现问题时,参与者会继续提交事务,可能造成数据不一致。
2PC和3PC都无法保证数据绝对的一致性,一般为预防这种问题,可以提交一个告警。
3.3 TCC
3.3.1. 什么是TCC
TCC: TCC(Try Confirm Cancel)是应用层的两阶段提交,所以对业务有侵入。
核心思想: 针对每个操作,都要实现对应的确认和补偿操作,也就是业务逻辑的每个分支,都要实现Try、Confirm、Cancel三个操作。
3.3.2 TCC的执行流程
TCC的执行过程可以分成两个阶段:
- 第一阶段: Try。该阶段,通过try操作做检测并预留资源。(比如:下单,在try阶段,并不是真正的扣减库存,只是把下单的库存进行锁定。)
- 第二阶段: Confirm/Cancel。根据第一阶段的结果决定是执行confirm还是cancel。
-
- Confirm:对Try阶段锁定的资源实际扣除。
- Cancel:对Try阶段锁定的资源进行释放。
3.3.3 TCC是如何保证最终一致性
- TCC是以Try为中心的,Confirm操作和Cancel操作都是围绕着Try展开的。所以在Try阶段保障性是最好的,即使出现失败,也可以通过Cancel操作将其执行结果撤销。
- 在Try阶段执行成功,并进入到Confirm阶段时,默认Confirm阶段是不会出错的,也就是说只要Try成功,Confirm一定成功(TCC设计之初的定义)。
- Confirm和Cancel如果失败,则由TCC框架进行重试补偿(定时)。
- 但仍存在极低情况下在CC阶段彻底失败,则需要人工介入。
3.3.4 TCC的注意事项
(1) 允许空回滚:
空回滚的原因是Try阶段超时或者丢包,导致TCC二阶段进行回滚,触发Cancel操作,此时事务参与者可能可能未收到Try操作,但是收到了Cancel操作。
所以,Cancel操作在实现的时候需要允许空回滚,即在Cancel执行时,如果没有查到对应业务的Try操作时,也是需要返回成功,让事务管理器认为已回滚。
(2) 防悬挂控制:
悬挂是指二阶段的Cancel比一阶段的Try操作先执行,出现该问题的原因是Try阶段由于网络拥堵而超时,导致事务管理器生成回滚,触发Cancel操作,但之后拥堵网络的Try又被资源管理器收到了,但是Cancel操作比Try操作先到。如果按照前面允许空回滚的逻辑,回滚是会成功,事务管理器认为回滚成功,所以,此时应该拒绝空回滚之后的Try操作,否则会产生数据不一致。
因此,我们在Cancel空回滚返回成功之前,应记录该条事务xid或者业务主键,标识该记录已经回滚过,Try操作在执行前先检查这条事务xid或者业务主键是否标记为回滚成功,如果是,则不执行Try操作。
(3) 幂等控制
由于网络原因或者重试操作都有可能导致Try - Confirm - Cancel三个操作重复执行,所以使用TCC时需要注意这三个操作的幂等控制。通常针对具体业务选择对应的业务幂等键来做防重控制。
3.3.4 TCC方案的优缺点
-
TCC事务机制相比上面的XA事务机制,有如下优点:
- 性能提升:具体业务实现,控制资源锁的粒度变小,不会锁定整个资源。
- 数据最终一致性:基于Confirm和Cancel操作的幂等性,确保事务最终完成或者取消,保证了数据一致性。
- 可靠性:解决了XA协议的协调者单点故障问题。由主业务发起并控制整个业务活动,业务活动管理器可以变成多点,引入集群。
-
缺点:TCC的Try - Confirm - Cancel操作功能需要按具体业务实现,业务耦合度高,提高了开发成功。
3.4 本地消息表
3.4.1 什么是本地消息表
本地事务表的核心思路是将**分布式事务拆分成本地事务进行处理,该方案中主要有两个角色:事务主动方和事务被动方**。
**事务主动方需要额外新建事务消息表,并在本地事务中完成业务处理和记录事务消息,并轮训事务消息表的数据发哦那个事务消息,事务被动方**基于消息中间件消费事务消息表中的事务。
这样可以避免一下两种情况导致的数据不一致性:
- 业务处理成功,事务消息发送失败。
- 业务处理失败,事务消息发送成功。
3.4.2 本地事务表的执行流程
- 事务主动方在同一个本地事务中处理业务和写消息表操作。
- 事务主动方通过消息中间件发送消息,通知事务被动发处理事务消息。
- 事务被动方接收到消息后,处理业务逻辑。
- 事务被动方通过消息中间件发送消息,通知事务主动方事务已处理。
- 事务主动方接收到消息后,更新消息表的状态为已处理。
一些必要的容错处理如下:
- 当①处理出错,由于事务还在事务主动方的本地事务中,直接回滚即可。
- 当②、④、⑤处理出错,由于事务主动方本地保存了消息,只需要轮询消息重新通过消息中间件发送,通知事务被动方重新读取消息处理业务即可。
- 当③业务上处理失败,事务被动方可以发送消息给事务主动方回滚事务。
3.4.3 本地消息表优缺点
1) 优点:
- 由于写消息表和业务数据在同一个本地事务中处理,确保了消息数据的可靠性,消息数据可靠性不依赖于消息中间件,弱化了对MQ中间件特性的依赖。
- 方案轻量,容易实现。
2) 缺点:
- 与具体业务场景绑定,耦合性强,不可公用。
- 消息数据和业务数据同库,占用业务系统资源。
- 由于需要将消息持久化到数据库中,消息服务性能会受到一定的影响。
3.5 事务消息
3.5.1 MQ事务消息执行流程
通过消息的异步事务,可以保证本地事务和消息发送同时执行成功或失败,既能实现系统间的解耦,又能保证数据的最终一致性。
本地消息表方案中,事务主动方通过在同一个本地事务中写业务数据和消息数据来保证数据的一致性。而事务消息相对于普通的MQ提供了2PC的提交接口,流程如下:
1) 正常执行情况
在事务主动方服务正常,没有发生故障的情况下,发消息流程如下:
- 步骤①:发送Half消息
- 步骤②:Half消息发送成功
- 步骤③:开始执行本地事务
- 步骤④:根据本地事务执行结果向MQ Server发送Commit/Rollback。
- 最终步骤:MQ Server基于Commit/Rollback进行消息投递或者删除。
2) 异常情况
在断网或者应用重启情况下,图中的步骤④Commit或者Rollback未到达MQ Server,此时处理逻辑如下:
- 步骤⑤:MQ Server未收到二次确认消息,发起消息回查。
- 步骤⑥:收到消息回查后,需要检查对应消息的本地事务执行状态。
- 步骤⑦:根据检查的本地事务执行状态,再次向MQ发送Commit/Rollback。
- 最终步骤:MQ Server基于Commit/Rollback进行消息投递或者删除
3.5.2 MQ事务消息的优缺点
1) 优点( 相较于本地消息表 )
- 消息数据独立存储,降低业务系统与消息系统的耦合性。
- 吞吐量有所提升。
2) 缺点
- 一次消息发送需要两次网络请求(Half消息+Commit/Rollback)。
- 业务处理服务需要实现消息状态回查接口。
3.6 最大努力通知
最大努力通知也称为定期校对,是对MQ事务方案的进一步优化。它在事务主动方增加了消息校对接口,如果事务被动方没有接收到主动方发送的消息,此时可以调用事务主动方提供的消息校对接口主动获取。
在可靠事务消息中,事务主动方需要将消息发送出去,并且让接收方成功接收消息,这种可靠性是由事务主动方保证的。
但是最大努力通知,事务主动方仅仅是做到尽最大努力(重试,轮询...)将信息发送给事务被动方,所以存在事务被动方接收不到信息的情况,所以需要事务被动发通过消息校对接口主动查询获取消息并消费,这种通知的可靠性是由事务被动方保证的。
适用场景:
适用于业务通知类型,如支付宝/微信交易的结果,就是通过最大努力通知方式通知商户,既有回调通知,也有交易查询接口。
3.7 各方案常见的使用场景
-
2PC/3PC: 依赖数据库,能够很好的提供强一致性和强事务性,但是性能比较差,延迟较高,比较适合传统的单体应用,在同一个方法中存在跨库操作,不适合高并发并性能要求的场景。
-
TCC: 适用于执行时间确定且较短,对数据一致性较高,比如:交易,支付等业务。
-
本地消息表/事务消息:适用于对强一致性要求不高的业务,业务上可以容忍数据在一定周期内不一致,超过周期仍不一致需人工介入。