分布式事务

分布式事务相关知识整理。

名词:事务,ACID,CAP就不再赘述了。

分布式事务的概念

要了解分布式事务,我们要先清楚什么是事务。事务就是多个原子操作的组合,他们就像是一条绳上的蚂蚱,要么一起生,要么一起死,在事务中,如果其中一个操作执行失败,那么剩下的操作都不再执行,而之前执行过的操作也需要回滚。至于分布式事务,顾名思义就是包含对分布式系统中不同节点的操作的事务。

分布式事务产生的场景

跨JVM进程

当我们将单体项目拆分为分布式、微服务项目之后,各个服务之间通过远程REST或者RPC调用来协同完成业务操作。典型的场景就是:商城系统中的订单微服务和库存微服务,用户在下单时会访问订单微服务,订单微服务在生成订单记录时,会调用库存微服务来扣减库存。各个微服务是部署在不同的JVM进程中的,此时,就会产生因跨JVM进程而导致的分布式事务问题。

跨数据库实例

单体系统访问多个数据库实例,也就是跨数据源访问时会产生分布式事务。例如,我们的系统中的订单数据库和交易数据库是放在不同的数据库实例中,当用户发起退款时,会同时操作用户的订单数据库和交易数据库,在交易数据库中执行退款操作,在订单数据库中将订单的状态变更为已退款。由于数据分布在不同的数据库实例,需要通过不同的数据库连接会话来操作数据库中的数据,此时,就产生了分布式事务。

多服务单数据库

多个微服务访问同一个数据库。例如,订单微服务和库存微服务访问同一个数据库也会产生分布式事务,原因是:多个微服务访问同一个数据库,本质上也是通过不同的数据库会话来操作数据库,此时就会产生分布式事务。
注意:跨数据库实例场景和多服务单数据库场景,本质上都是因为会产生不同的数据库会话来操作数据库中的数据,进而产生分布式事务。这两种场景是大家比较容易忽略的。

保证最终一致性的模式

保证最终一致性的模式是用来解决分布式事务中的某个问题,就像设计模式用来解决系统中的某一个问题。比如事务结束了,但是某个状态不一致;高并发场景下,如何保证基本可用性和最终一致性。

查询模式

任何事务操作都需要提供一个查询接口,用来向外部输出操作执行的状态。事务操作的使用方可以通过查询接口得知事务操作执行的状态,然后根据不同的状态来做不同的处理操作。

补偿模式

通过查询模式,可以知道事务操作的状态,如果操作处于错误的状态,就需要对其未完成的子操作进行修复,来让系统达到一致。这种后期通过努力让系统最终达到一致的操作就叫做补偿。

异步确保模式

异步确保模式是使用补偿模式一个经典例子。在高并发的场景下,把使用方对响应时间要求不高的操作从主流程中摘除,通过异步的方式进行处理,将操作封装后持久化到数据库,然后定时捞取未完成的操作进行补偿模式。

定期校对模式

系统中各节点之间可能存在不一致,就需要定期执行校对操作,如果发现不一致,则运用补偿模式,进行补偿操作,来使系统最终达到一致。

处理方法和优缺点

一般分布式事务处理模式包括:2阶段提交、3阶段提交、TCC(Try-Confirm-Cancel)、可靠消息(消息队列、数据库表)、SAGAS长事务、补偿性事务。具体采用哪一种分布式事务处理模式,需要根据自己业务场景来选择合适的机制。
duboo、spring cloud都可以算作是SOA框架,分布式事务控制支持依赖其他组件,例如通过JTA(2阶段、3阶段)、ActiveMQ消息中间件、ByteTCC(TCC)等。
zookeeper、redis可以支持分布式锁,分布式锁是分布式事务的核心,但分布式锁不等同于分布式事务。
redis对分布式锁的支持主要通过setnx,在使用redis分布式锁时候,一定要注意处理加锁客户端异常导致锁资源没有正常释放的情况(例如调用端down掉),在调用setnx时候需要加上对锁超时时间的判断
zookeeper对分布式锁的支持可以直接使用zookeeper curator-recipes客户端。

解决方案一致性协议优缺点优缺点应用场景
两阶段提交协议(2PC)强一致性XA协议,由Tuxedo提出1、 同步阻塞问题;2、 单点故障;1、极端情况下数据的不一致性;2、引入事务管理者(协调者),单点故障;3、系统可伸缩性存在问题;4、全局事务结束才能释放资源,性能问题;在高并发场景下很少使用。
三阶段提交协议(3PC)强一致性XA协议,由Tuxedo提出1、超时机制解决同步阻塞问题;2、预备阶段尽可能提早发现问题;1、极端情况下数据的不一致性;2、引入事务管理者(协调者),单点故障;3、系统可伸缩性存在问题;4、全局事务结束才能释放资源,性能问题;在高并发场景下很少使用。
TCC 模式(Try、Confirm、Cancel)可以理解为SQL事务中的 Lock、Commit、Rollback最终一致性阿里提出1、对于事务恢复,基于 Quartz调度,按照一定频率进行重试。2、要按具体业务来实现,业务耦合度较高,提高了开发成本。部分控制的好处是并发量和性能很好,缺点是数据一致性减弱了,完全控制则是牺牲了性能,保障了一致性,具体用哪种方式,最终还是取决于业务场景。作为技术人员,一定不能忘了技术是为业务服务的,不要为了技术而技术,针对不同业务进行技术选型也是一种很重要的能力!tcc-transaction 框架,@Compensable 注解
补偿模式最终一致性1、重试机制:同步通知、消息队列、定时任务。2、每次更新的时候进行自我恢复和修正。3、定时校对:未完成的定时重试、定时核对。世界上解决一个计算机问题最简单的方法:“恰好”不需要解决它!如果系统要实现回滚流程的话,有可能系统复杂度将大大提升,且很容易出现 Bug,估计出现 Bug 的概率会比需要事务回滚的概率大很多。可以考虑当出现这个概率很小的问题,能否采用人工解决的方式。
可靠事件模式最终一致性1、正向发送消息:消息持久化到本地数据库,标志状态:待发送、结束、已发送、完成等。2、正向接受消息:ACK机制。3、逆向消息:弥补消息主动丢弃。4、补偿机制:定时任务扫描“未完成”的消息并重新投递。缺点:1、一次消息发送需要两次网络请求(half 消息+ commit/ rollback消息) 。2、业务处理服务需要实现消息状态回查接口。

共性点:
1、 如果最终多次重试失败可以根据相关日志并主动通知开发人员进行手工介入。
2、 业务处理逻辑需要保证幂等。

强一致性_2PC

第一阶段是表决阶段,所有参与者都将本事务能否成功的信息反馈发给协调者;
第二阶段是执行阶段,协调者根据所有参与者的反馈,通知所有参与者,步调一致地在所有分支上提交或者回滚;

如果第二阶段提交失败了呢?
有两种情况:
1.第二阶段执行的是回滚事务操作:第二阶段如果失败则不断重试,直到所有参与者都回滚,不然那些在第一阶段准备成功的参与者会一直阻塞。
2.第二阶段执行的是提交事务操作:也是不断重试,直到所有参与者都提交成功

缺点:
单点问题:事务管理器在整个流程中扮演的角色很关键,如果其宕机,比如在第一阶段已经完成,在第二阶段正准备提交的时候事务管理器宕机,资源管理器就会一直阻塞,导致数据库无法使用。
同步阻塞:在准备就绪之后,资源管理器中的资源一直处于阻塞,直到提交完成,释放资源。
数据不一致:两阶段提交协议虽然为分布式数据强一致性所设计,但仍然存在数据不一致性的可能。比如:在第二阶段中,假设协调者发出了事务 Commit 的通知,但是因为网络问题该通知仅被一部分参与者所收到并执行了 Commit 操作,其余的参与者则因为没有收到通知一直处于阻塞状态,这时候就产生了数据的不一致性。
总的来说,XA 协议比较简单,成本较低,但是其单点问题,以及不能支持高并发(由于同步阻塞)依然是其最大的弱点。

XA即两阶段提交事务方案。
需要数据库厂商支持; JAVA组件有atomikos等。
较适合单块应用中,跨多库的分布式事务,而且因为严重依赖于数据库层面来搞定复杂的事务,效率很低,不适合高并发场景。
如果要玩,那么基于Spring + JTA就可以搞定。
互联网公司基本都不用,因为某个系统内部如果出现跨多库的操作,是不合规的!现在的微服务,一个大的系统分成几十甚至上百个服务。一般规约每个服务只能操作自己对应的一个数据库。
如果你要操作别的服务对应的库,不允许直连别的服务的库。
要操作别人的服务的库,必须通过调用别的服务的接口

强一致性_3PC

三阶段提交有两个改动点。
引入超时机制。同时在协调者和参与者中都引入超时机制。
第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前各参与节点的状态是一致的。
也就是说,除了引入超时机制之外,3PC把2PC的准备阶段再次一分为二,这样三阶段提交就有CanCommit、PreCommit、DoCommit三个阶段。
3PC最关键要解决的就是协调者和参与者同时挂掉的问题,所以3PC把2PC的准备阶段再次一分为二,这样三阶段提交就有CanCommit、PreCommit、DoCommit三个阶段。在第一阶段,只是询问所有参与者是否可可以执行事务操作,并不在本阶段执行事务操作。当协调者收到所有的参与者都返回YES时,在第二阶段才执行事务操作,然后在第三阶段在执行commit或者rollback。

3PC为什么比2PC好
直接分析协调者和参与者都挂的情况。
第二阶段协调者和参与者挂了,挂了的这个参与者在挂之前已经执行了操作。但是由于他挂了,没有人知道他执行了什么操作。
这种情况下,当新的协调者被选出来之后,他同样是询问所有的参与者的情况来觉得是commit还是roolback。这看上去和二阶段提交一样啊?他是怎么解决一致性问题的呢?
看上去和二阶段提交的那种数据不一致的情况的现象是一样的,但仔细分析所有参与者的状态的话就会发现其实并不一样。我们假设挂掉的那台参与者执行的操作是commit。那么其他没挂的操作者的状态应该是什么?他们的状态要么是prepare-commit要么是commit。因为3PC的第三阶段一旦有机器执行了commit,那必然第一阶段大家都是同意commit。所以,这时,新选举出来的协调者一旦发现未挂掉的参与者中有人处于commit状态或者是prepare-commit的话,那就执行commit操作。否则就执行rollback操作。这样挂掉的参与者恢复之后就能和其他机器保持数据一致性了。(为了简单的让大家理解,笔者这里简化了新选举出来的协调者执行操作的具体细节,真实情况比我描述的要复杂)
简单概括一下就是,如果挂掉的那台机器已经执行了commit,那么协调者可以从所有未挂掉的参与者的状态中分析出来,并执行commit。如果挂掉的那个参与者执行了rollback,那么协调者和其他的参与者执行的肯定也是rollback操作。
所以,再多引入一个阶段之后,3PC解决了2PC中存在的那种由于协调者和参与者同时挂掉有可能导致的数据一致性问题

2pc和3pc的区别
相对于2PC,3PC主要解决的单点故障问题,并减少阻塞,因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行commit。而不会一直持有事务资源并处于阻塞状态。但是这种机制也会导致数据一致性问题,因为,由于网络原因,协调者发送的abort响应没有及时被参与者接收到,那么参与者在等待超时之后执行了commit操作。这样就和其他接到abort命令并执行回滚的参与者之间存在数据不一致的情况。
在2PC中一个参与者的状态只有它自己和协调者知晓,假如协调者提议后自身宕机,在协调者备份启用前一个参与者又宕机,其他参与者就会进入既不能回滚、又不能强制commit的阻塞状态,直到参与者宕机恢复
参与者如果在不同阶段宕机,我们来看看3PC如何应对:
阶段1: 协调者或协调者备份未收到宕机参与者的vote,直接中止事务;宕机的参与者恢复后,读取logging发现未发出赞成vote,自行中止该次事务
阶段2: 协调者未收到宕机参与者的precommit ACK,但因为之前已经收到了宕机参与者的赞成反馈(不然也不会进入到阶段2),协调者进行commit;协调者备份可以通过问询其他参与者获得这些信息,过程同理;宕机的参与者恢复后发现收到precommit或已经发出赞成vote,则自行commit该次事务
阶段3: 即便协调者或协调者备份未收到宕机参与者t的commit ACK,也结束该次事务;宕机的参与者恢复后发现收到commit或者precommit,也将自行commit该次事务

3PC存在的问题
在doCommit阶段,如果参与者无法及时接收到来自协调者的doCommit或者rebort请求时,会在等待超时之后,会继续进行事务的提交。
所以,由于网络原因,协调者发送的abort响应没有及时被参与者接收到,那么参与者在等待超时之后执行了commit操作。这样就和其他接到abort命令并执行回滚的参与者之间存在数据不一致的情况。

最终一致性_补偿事务(TCC)

核心思想是针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作,它大致分为三个阶段:
(1)Try 阶段主要是对业务系统做检测及资源预留
(2)Confirm 阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。
(3)Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。

优点: 跟 2PC 比起来,实现以及流程相对简单了一些,但数据的一致性比2PC也要差一些
缺点: 缺点还是比较明显的,在2,3步中都有可能失败。TCC属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,在一些场景中,一些业务流程可能用TCC不太好定义及处理。

TCC 的实践经验
蚂蚁金服TCC实践,总结以下注意事项:

1
2
3
4
5
➢业务模型分2阶段设计
➢并发控制
➢允许空回滚
➢防悬挂控制
➢幂等控制

TCC的三种异常处理情况
幂等处理
因为网络抖动等原因,分布式事务框架可能会重复调用同一个分布式事务中的一个分支事务的二阶段接口。所以分支事务的二阶段接口Confirm/Cancel需要能够保证幂等性。如果二阶段接口不能保证幂等性,则会产生严重的问题,造成资源的重复使用或者重复释放,进而导致业务故障。
对于幂等类型的问题,通常的手段是引入幂等字段进行防重放攻击。对于分布式事务框架中的幂等问题,同样可以祭出这一利器。
幂等记录的插入时机是参与者的Try方法,此时的分支事务状态会被初始化为INIT。然后当二阶段的Confirm/Cancel执行时会将其状态置为CONFIRMED/ROLLBACKED。
当TC重复调用二阶段接口时,参与者会先获取事务状态控制表的对应记录查看其事务状态。如果状态已经为CONFIRMED/ROLLBACKED,那么表示参与者已经处理完其分内之事,不需要再次执行,可以直接返回幂等成功的结果给TC,帮助其推进分布式事务。
空回滚
当没有调用参与方Try方法的情况下,就调用了二阶段的Cancel方法,Cancel方法需要有办法识别出此时Try有没有执行。如果Try还没执行,表示这个Cancel操作是无效的,即本次Cancel属于空回滚;如果Try已经执行,那么执行的是正常的回滚逻辑。
要应对空回滚的问题,就需要让参与者在二阶段的Cancel方法中有办法识别到一阶段的Try是否已经执行。很显然,可以继续利用事务状态控制表来实现这个功能。
当Try方法被成功执行后,会插入一条记录,标识该分支事务处于INIT状态。所以后续当二阶段的Cancel方法被调用时,可以通过查询控制表的对应记录进行判断。如果记录存在且状态为INIT,就表示一阶段已成功执行,可以正常执行回滚操作,释放预留的资源;如果记录不存在则表示一阶段未执行,本次为空回滚,不释放任何资源。
资源悬挂
问题:TC回滚事务调用二阶段完成空回滚后,一阶段执行成功
解决:事务状态控制记录作为控制手段,二阶段发现无记录时插入记录,一阶段执行时检查记录是否存在

最终一致性_本地消息表(异步确保)

它的核心思想是将需要分布式处理的任务通过消息或者日志的方式来异步执行,消息或日志可以存到本地文件、数据库或消息队列,再通过业务规则进行失败重试,它要求各服务的接口是幂等的。
如果消息消费方执行失败则发送失败消息给生产方,进行相应的业务补偿,实现最终一致性。

优点: 一种非常经典的实现,避免了分布式事务,实现了最终一致性。
缺点: 消息表会耦合到业务系统中,如果没有封装好的解决方案,会增加很多工作量。
这个方案的核心在于第二阶段的重试和幂等执行。失败后重试,这是一种补偿机制,它是能保证系统最终一致的关键流程。

最终一致性_MQ事务消息

不要用本地的消息表了,直接基于MQ来实现事务。比如阿里的RocketMQ就支持消息事务。

下图是一个简单的可靠消息服务的设计,通过这张图可以简单的了解一下可靠消息服务的思路。

由上图可以看出,可靠消息服务分为以下三步(括号内为执行失败的情况):
(1)系统A执行操作前先将消息发送给消息系统,系统A在执行完操作后,将确认消息发送给消息系统。(如果系统A执行失败,或者由于网络原因,消息系统一直没有收到确认消息,则将该条消息返回给系统A做处理)
(2)消息系统将消息投递给系统B执行,系统B将执行结果返回给消息系统。(在这里系统B可能执行失败,也可能受网络等因素,消息系统接收到一个失败的结果,遇到这种情况,则重复向系统B投递消息)
(3)如果消息系统收到系统B返回的成功的执行结果,则事务执行成功。(如果消息系统重复投递消息,一直收到失败的结果,超过一定次数后,则认为执行失败,将该条消息存入死信队列,做特殊处理)
可靠消息服务保证消息一直是可靠的,可执行的。所以系统A在执行前先将消息发送给消息系统,执行成功后再发送确认消息,确保系统A成功执行后,消息系统才会将消息发送到B系统,保证消息的可执行性;投递到B系统的消息执行失败后重复投递,超过一定次数后采用补偿模式,做特殊处理,保证消息的可靠性。

seata_AT模式

AT 模式是一种无侵入的分布式事务解决方案。
阿里seata框架,实现了该模式。

在 AT 模式下,用户只需关注自己的“业务 SQL”,用户的 “业务 SQL” 作为一阶段,Seata 框架会自动生成事务的二阶段提交和回滚操作。

疑问

2pc和3pc各组件超时了,应该怎么做(自动rooback,自动commit?,或一直保持锁定直到重新联通)

参考

一文读懂分布式事务及其解决方案:https://yunfan.blog.csdn.net/article/details/105429523#t7
分布式事务:www.okgoes.cn/blog/detail?blog_id=27596

dubbo、zookeeper、spring cloud、redis都能否做分布式事务控制?:https://www.zhihu.com/question/56839867
分布式事务解决方案的对比:blog.sina.com.cn/s/blog_3fba24680102z0w4.html
分布式事务解决方案:https://www.jianshu.com/p/ee4071d0c951
分布式事务的4种模式:https://zhuanlan.zhihu.com/p/141645172
突破Java面试(44)-分布式事务解决方案:https://www.imooc.com/article/289274/
六种分布式事务解决方案:https://www.136.la/jingpin/show-168550.html#2PC
聊聊分布式事务,再说说解决方案:https://www.cnblogs.com/savorboard/p/distributed-system-transaction-consistency.html
分布式事务及解决方案:https://blog.csdn.net/z1770223284/article/details/89791094
深入理解分布式系统的2PC和3PC:https://www.hollischuang.com/archives/1580

架构设计和高并发系列
读书_大型网站技术架构01_李智慧
读书_大型网站技术架构02_李智慧
读书_大型网站技术架构03_李智慧
读书_高并发设计40问之一基础
读书_高并发设计40问之二数据库
读书_高并发设计40问之三缓存
读书_高并发设计40问之四消息队列
读书_高并发设计40问之五分布式服务
读书_w3c架构师01通用设计与方法论
读书_w3c架构师02典型架构实践
读书_w3c架构师03数据库与缓存
分布式事务
高并发之缓存
高并发之降级
高并发之限流
数据库_读写分离
消息队列_01消息队列入门
消息队列_02rabbitMQ入门
消息队列_03rabbitMQ安装和使用

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×