Spring Boot整合分布式事务简介

一. 分布式事务简介

1. 什么是分布式事务

百度百科对分布式事务的解释如下:

分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于分布式系统的不同节点之上。
当数据库的数据越来越大,数据库无法承受起压力,就会开始分库分表,分库分表后不同的库就会分布在不同的服务器上,明显的就会出现数据一致性问题。或者同一个事务中要调用的不同系统的不同的库,也要保证要不全都成功,要不全部回滚。

简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用。分布式事务需要保证这些小操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。

2. 分布式事务产生的原因

2.1 数据库分库分表

当数据库单表一年产生的数据超过1000W,那么就可能要考虑分库分表,也就是说要把原来的一个数据库变成多个数据库。这时候,如果一个操作既要访问01库,又访问02库,而且还要保证数据的一致性,那么就要用到分布式事务。

2.2 应用SOA化

所谓的SOA,就是业务的服务化。比如原来单机支撑了整个电商网站,现在对整个网站进行拆解,分离出了订单中心、库存中心。对于订单中心,有专门的数据库存储订单信息,库存中心也会有专门的数据库存储库存信息。这时候如果要同时对订单和库存进行操作,那么就会涉及到订单数据库和库存数据库,为了保证数据一致性,就需要用到分布式事务。

以上两种情况表象不同,但是本质相同,都是因为要操作的数据库变多了!

3. 分布式事务的应用场景

3.1 支付

我们在进行支付的时候,会对买家账户进行扣款,同时对卖家账户进行加钱。这些操作必须在一个事务里执行,要么全部成功,要么全部失败。而对于买家账户属于买家中心,对应的是买家数据库,而卖家账户属于卖家中心,对应的是卖家数据库,对不同数据库的操作必然需要引入分布式事务。

3.2 在线下单

买家在电商平台下单,往往会涉及到两个动作,一个是扣库存,第二个是更新订单状态,库存和订单一般属于不同的数据库,需要使用分布式事务保证数据一致性。

二. XA 事务

1. XA协议

XA(eXtended Architecture)是指由X/Open 组织提出的分布式交易处理的规范。XA 是一个分布式事务协议,由Tuxedo 提出,所以分布式事务也称为XA 事务。

2. XA协议构成

XA 协议主要定义了事务管理器TM(Transaction Manager,协调者)和资源管理器RM(Resource Manager,参与者)之间的接口。
其中,资源管理器往往由数据库实现,如Oracle、DB2、MySQL,这些商业数据库都实现了XA 接口,而事务管理器作为全局的调度者,负责各个本地资源的提交和回滚。
XA 事务是基于两阶段提交(Two-phaseCommit,2PC)协议实现的,可以保证数据的强一致性,许多分布式关系型数据管理系统都采用此协议来完成分布式。
阶段一为准备阶段,即所有的参与者准备执行事务并锁住需要的资源。当参与者Ready时,向TM 汇报自己已经准备好。
阶段二为提交阶段。当TM 确认所有参与者都Ready 后,向所有参与者发送COMMIT 命令。

XA 事务允许不同数据库的分布式事务,Oracle、MySQL 和SQL Server 都支持XA 事务。

3. XA事务构成

XA事务由一个或多个资源管理器(RM)、一个事务管理器(TM)和一个应用程序(ApplicationProgram)组成。

  • 资源管理器: 提供访问事务资源的方法,通常一个数据库就是一个资源管理器。
  • 事务管理器: 协调参与全局事务中的各个事务,需要和参与全局事务的所有资源管理器进行通信。
  • 应用程序: 定义事务的边界。

4. XA事务注意事项

XA 事务的缺点是性能不好,且无法满足高并发场景。一个数据库的事务和多个数据库间的XA 事务性能会相差很多。因此,要尽量避免XA 事务,如可以将数据写入本地,用高性能的消息系统分发数据,或使用数据库复制等技术。只有在其他办法都无法实现业务需求,且性能不是瓶颈时才使用XA。

三. 常见的分布式事务解决方案

1. 基于XA协议的两阶段提交(2PC)

XA是一个分布式事务协议,由Tuxedo提出。XA中大致分为两部分:事务管理器和本地资源管理器。其中本地资源管理器往往由数据库实现,比如Oracle、MySQL这些商业数据库都实现了XA接口,而事务管理器作为全局的调度者,负责各个本地资源的提交和回滚。

XA实现分布式事务的原理如下:

总的来说,XA协议比较简单,而且一旦商业数据库实现了XA协议,使用分布式事务的成本也比较低。

但是,XA也有致命的缺点,那就是性能不理想,特别是在交易下单链路,往往并发量很高,XA无法满足高并发场景。
XA目前在商业数据库支持的比较理想,在MySQL数据库中支持的不太理想,MySQL的XA实现,没有记录prepare阶段日志,主备切换会导致主库与备库数据不一致。许多NoSQL也没有支持XA,这让XA的应用场景变得非常狭隘。

XA两阶段提交的不足

  • 1.性能问题
    XA协议遵循强一致性。在事务执行过程中,各个节点占用着数据库资源,只有当所有节点准备完毕,事务协调者才会通知提交,参与者提交后释放资源。这样的过程有着非常明显的性能问题。
  • 2.协调者单点故障问题
    事务协调者是整个XA模型的核心,一旦事务协调者节点挂掉,参与者收不到提交或是回滚通知,参与者会一直处于中间状态无法完成事务。
  • 3.丢失消息导致的不一致问题
    在XA协议的第二个阶段,如果发生局部网络问题,一部分事务参与者收到了提交消息,另一部分事务参与者没收到提交消息,那么就导致了节点之间数据的不一致。

总结:

  • 优点: 尽量保证了数据的强一致,适合对数据强一致要求很高的关键领域(其实也不能100%保证强一致)
  • 缺点: 实现复杂,牺牲了可用性,对性能影响较大,不适合高并发高性能场景,如果分布式系统跨接口调用。

2. 消息事务+最终一致性

所谓的消息事务就是基于消息中间件的两阶段提交,本质上是对消息中间件的一种特殊利用,它是将本地事务和发消息放在了一个分布式事务里,保证要么本地操作成功成功并且对外发消息成功,要么两者都失败,开源的RocketMQ就支持这一特性。

业务示意图:

具体原理如下:

  • 1. A系统向消息中间件发送一条预备消息;
  • 2. 消息中间件保存预备消息并返回成功;
  • 3. A执行本地事务;
  • 4. A发送提交消息给消息中间件。

通过以上4步完成了一个消息事务。对于以上的4个步骤,每个步骤都可能产生错误,下面一一分析:

1.如果步骤一出错,则整个事务失败,不会执行A的本地操作;
2.如果步骤二出错,则整个事务失败,不会执行A的本地操作;
3.如果步骤三出错,这时候需要回滚预备消息,怎么回滚呢?答案是A系统实现一个消息中间件的回调接口,消息中间件会不断的执行回调接口,检查A事务执行是否执行成功,如果失败则回滚预备消息。
4.如果步骤四出错,这时候A的本地事务是成功的,那么消息中间件要回滚A吗?答案是不需要,其实通过回调接口,消息中间件能够检查到A执行成功了,这时候其实不需要A发提交消息了,消息中间件可以自己对消息进行提交,从而完成整个消息事务。

消息事务小结:

  • 优点:实现了最终一致性,不需要依赖本地数据库事务。
  • 缺点:实现难度大,主流MQ不支持,RocketMQ事务消息部分代码也未开源。

3. 本地消息表(异步确保)

基于消息中间件的两阶段提交往往用在高并发场景下,将一个分布式事务拆成一个消息事务(A系统的本地操作+发消息)和B系统的本地操作。其中B系统的操作由消息驱动,只要消息事务成功,那么A操作一定成功,消息也一定发出来了。这时候B会收到消息去执行本地操作,如果本地操作失败,消息会重投,直到B操作成功,这样就变相地实现了A与B的分布式事务。

原理如下:

虽然上面的方案能够完成A和B的操作,但是A和B并不是严格一致的,而是最终一致的。我们在这里牺牲了一致性,换来了性能的大幅度提升。当然,这种玩法也是有风险的,如果B一直执行不成功,那么一致性会被破坏,具体要不要玩,还是得看业务能够承担多少风险。

4. 补偿事务的TCC编程模式

所谓的补偿事务的TCC编程模式,也是两阶段提交的一个变种。TCC提供了一个编程框架,将整个业务逻辑分为三块:Try、Confirm和Cancel三个操作。

以在线下单为例,Try阶段会去扣库存,Confirm阶段则是去更新订单状态,如果更新订单失败,则进入Cancel阶段,会去恢复库存。总之,TCC就是通过代码人为实现了两阶段提交,不同的业务场景所写的代码都不一样,复杂度也不一样,因此,这种模式并不能很好地被复用。

四. 总结

分布式事务,本质上是对多个数据库的事务进行统一控制,按照控制力度可以分为:不控制、部分控制和完全控制。
  • 不控制就是不引入分布式事务。
  • 部分控制就是各种变种的两阶段提交,包括上面提到的消息事务+最终一致性、TCC模式,而完全控制就是完全实现两阶段提交。部分控制的好处是并发量和性能很好,缺点是数据一致性减弱了。
  • 完全控制则是牺牲了性能,保障了一致性,具体用哪种方式,最终还是取决于业务场景。

作为技术人员,一定不能忘了技术是为业务服务的,不要为了技术而技术,针对不同业务进行技术选型也是一种很重要的能力!