跳到主要内容

分布式事务之 Seata

· 阅读需 6 分钟

在企业应用开发中,随着分布式框架的发展,生产环境里会有很多数据库实例。特别是在微服务领域,我们通常会让每个业务 Service 模块对应一个自身业务的 DB 存储节点,再对这个存储节点做高可用部署。

事务问题

编写 Service 业务逻辑时,一个业务操作往往会远程调用其他业务模块,产生的数据会落盘到不同的存储节点。为了保障多个数据库实例之间事务的 ACID 特性,就遇到了分布式事务问题。

分布式事务要做到的事情可以概括为两条:

1)如果整个业务调用链路均成功,那么整个调用链路对应的数据库做事务提交。

2)如果调用链路中抛出了异常,那么整个调用链路对应的数据库做回滚操作。

Seata 的角色划分

在 Seata 分布式事务解决方案中,一般有以下这些角色:

  • TC (Transaction Coordinator):事务协调者。
  • TM (Transaction Manager):事务管理器,在 AP 上进行集成。
  • RM (Resource Manager):资源管理器,管理分支事务处理的资源,与 TC 交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
  • 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 锁、间隙锁、表锁、页锁,视数据范围而定)。如果是长事务,在高并发场景下非常容易出现死锁,数据库性能也会下降得比较厉害。如果都是短事务,则可以使用此模式。

小结

分布式事务的本质是让跨库调用链路要么一起提交、要么一起回滚。Seata 通过 TC、TM、RM 的角色分工把这件事产品化,并提供了 AT、TCC、Saga、XA 四种模式。AT 靠数据镜像做到零入侵,适合大多数常规场景;TCC 和 Saga 把补偿逻辑交给业务,换取更高的并发与灵活性;XA 依赖数据库原生支持,只适合短事务。选型时先分清业务是长事务还是短事务、能接受强一致还是最终一致,再对号入座。

评论 / COMMENTS