分布式事务之 Seata
前言
在企业应用程序开发中,随着分布式框架发展,我们生产环境会有会很多数据库的实例,特别是在微服务领域中,我们会设计每个业务Service模块都会对应一个自身业务模块的DB存储节点。然后再对这个DB存储节点做高可用部署。
事务问题
那么我们在编写Service中的业务逻辑时,肯定会遇到一个业务操作会远程调用到其他不同的业务模块,那么对应的产生数据就会落盘到不同的存储节点中,为了保障多个数据库实例之间的事物ACID特性,就遇到了分布式事务的问题。
分布式事务
1、如果整个业务调用链路均成功,那么整个调用链路对应的数据库做事物提交。
2、如果调用链路中抛出了异常,那么整个调用链路对应的数据库做回滚操作。
Seata
在Seata分布式事务解决方案中,一般有以下这些角色:
RM (Resource Manager) 管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
TC (Transaction Coordinator) - 事务协调者
TM (Transaction Manager) - 事务管理器,AP上进行集成
AP (Application Program),访问RM的应用程序
图中的TC,事务协调者组件需要进行单独部署,并且加入到整个微服务集群的注册中心中,目前所有常见的注册中心均支持,如Nacos,Zookeeper,Etcd,Eureka,Consul。并需要在AP中做对应连接配置设置(需要保障TC模块的高可用性,如果该模块宕机,分布式事务将无从谈起)。
刚性事务:通常无业务改造,强一致性,原生支持回滚/隔离性,低并发,适合短事务。
对应方案:XA 协议(2PC、JTA、JTS)、3PC
柔性事务:有业务改造,最终一致性,实现补偿接口,实现资源锁定接口,高并发,适合长事务。
对应方案:TCC/FMT、Saga(状态机模式、Aop模式)、本地事务消息、消息事务(半消息)
什么是长事物,什么是短事物呢?
长事物:长时间占用了一个资源(X锁,间隙锁,表锁,页锁),修改的数据较多或者执行速度较慢,业务流程长,那么其他事物需要访问此资源时将会被堵塞等待,这样便容易导致死锁。
短事物:对资源的占用很短暂,修改的数据较少或执行速度很快完成,业务流程短,事物很快结束,故此产生死锁的概率较小。
目前Seata中间件支持4中分布式事务的管理模式:
AT模式
AT模式算是Seata中最具特色的模式,总的来说就是优化过的2PC模式,也是属于二阶段提交的一种,使用这种模式需要在本地数据库节点中创建一张undolog表,通过代理DataSourceProxy从而拦截到应用程序执行的DML SQL语句,对SQL语句进行语义解析,从而转化为查询SQL语句,把执行前对应的数据镜像存入undolog表中以作为二阶段回滚使用。以上这些步骤均为代理模式完成,所以实现了零业务逻辑代码入侵。
但此方案并非完美,存在一定的问题,性能损失还是相当大的,一次DML SQL将会多出一次语义转化并执行查询SQL,以及新增数据镜像到undolog表,如果发生二阶段回滚,还需要获取本地锁,再操作回退原始数据镜像。并且存在一个比较严重的问题,如果在回滚的时候,其他本地事物没有走Seata的全局事物管理去获取全局锁,而是直接走了本地事物修改了undolog中记录的某条数据镜像所对应的数据信息,那么在Seata进行回滚时将会发现回滚数据镜像与当前本地数据匹配不了,将会出现数据丢失问题,有点类似于CAS中的ABA问题。
TCC模式
TCC模式较为简单,其实也是2PC两阶段提交,此模式和AT模式的区别就是需要自己去实现一阶段 prepare 准备, 二阶段commit,以及rollback逻辑。Seata的事物管理器会依次调用实现方法。
Saga模式
Saga是由一系列的本地事务构成。每一个本地事务在更新完数据库之后,会发布一条消息或者一个事件来触发Saga中的下一个本地事务的执行。如果一个本地事务因为某些业务规则无法满足而失败,Saga会执行在这个失败的事务之前成功提交的所有事务的补偿操作。所以Saga算是实现起来最为繁琐的一种模式,但基于Saga的状态机可用非常灵活的控制每一个分布式事物步骤。对于长事物的场景,则更需要自身更细粒的控制每一种情况。不关注整个分布式事务的状态,只需要做好每个事件的处理,以及本身方法的幕等性即可。但他的事物没有阶段提交一说,所以事物都是本地提交,如果在回滚途中,其他数据进行了数据读取并修改值,则会发生胀读,胀写的情况,对于这种情况我想还需要自己去实现对应的一套txid的方案,以避免胀读,胀写。
XA模式
XA模式首先必须需要数据库支持XA协议,其实现有2PC,3PC,不过其中的实现完全是由数据库自身提供,应用程序不需要做任何处理,本质和AT模式很相似,但区别就是数据库在进行XA事物的过程中会为了强一致性而保持数据资源持续占用(X锁,间隙锁,表锁,页锁都是根据数据范围而定),如果是长事物,则在高并发场景非常容易导致死锁出现。并且对于数据库来说性能下降得比较厉害。如果都是短事物则可用使用此模式。