Skip to content

分布式事务之Seata

它把一个分布式事务理解成一个包含了若干分支事务的全局事务。 而全局事务的职责是协调它管理的分支事务达成一致性,要么一起成功提交,要么一起失败回滚。

该图片来自互联网

Seata 中存在几种重要角色

TC(Transaction Coordinator)

事务协调者。管理全局的分支事务的状态,用于全局性事务的提交和回滚。

TM(Transaction Manager)

事务管理者。用于开启、提交或回滚事务。

RM(Resource Manager)

资源管理器。用于分支事务上的资源管理,向 TC 注册分支事务,上报分支事务的状态,接收 TC 的命令来提交或者回滚分支事务。

其中 TM 和 RM 是作为 Seata 的客户端与业务系统集成在一起,TC 作为 Seata 的服务端独立部署。

Seata工作流程

在 Seata 中,分布式事务的执行流程:

  1. TM 开启分布式事务(TM 向 TC 注册全局事务记录);
  2. 按业务场景,编排数据库、服务等事务内资源(RM 向 TC 汇报资源准备状态 );
  3. TM 结束分布式事务,事务一阶段结束(TM 通知 TC 提交/回滚分布式事务);
  4. TC 汇总事务信息,决定分布式事务是提交还是回滚;
  5. TC 通知所有 RM 提交/回滚 资源,事务二阶段结束;

Seata事务模式

Seata AT模式

在生产环境上,我实际使用过的模式

该图片来自互联网

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

Seata AT模式是如何做到对业务无侵入的?

该图片来自互联网

在一阶段,Seata 会拦截“业务 SQL”,首先解析 SQL 语义,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,然后执行“业务 SQL”更新业务数据,在业务数据更新之后,再将其保存成“after image”,最后生成行锁。以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性

二阶段提交或回滚

  • 二阶段提交:二阶段如果是提交的话,因为“业务 SQL”在一阶段已经提交至数据库, 所以 Seata 框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。

    该图片来自互联网

  • 二阶段回滚:二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的“业务 SQL”,还原业务数据。回滚方式便是用“before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 “after image”,如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。

    该图片来自互联网

Seata TCC模式

该图片来自互联网

TCC 模式需要用户根据自己的业务场景实现 Try、Confirm 和 Cancel 三个操作;事务发起方在一阶段执行 Try 方式,在二阶段提交执行 Confirm 方法,二阶段回滚执行 Cancel 方法

  • Try:资源的检测和预留;
  • Confirm:执行的业务操作提交;要求 Try 成功 Confirm 一定要能成功;
  • Cancel:预留资源释放;

TCC 设计

  • TCC 设计 - 允许空回滚。Cancel 接口设计时需要允许空回滚。在 Try 接口因为丢包时没有收到,事务管理器会触发回滚,这时会触发 Cancel 接口,这时 Cancel 执行时发现没有对应的事务 xid 或主键时,需要返回回滚成功。让事务服务管理器认为已回滚,否则会不断重试,而 Cancel 又没有对应的业务数据可以进行回滚。
  • TCC 设计 - 防悬挂控制。悬挂的意思是:Cancel 比 Try 接口先执行,出现的原因是 Try 由于网络拥堵而超时,事务管理器生成回滚,触发 Cancel 接口,而最终又收到了 Try 接口调用,但是 Cancel 比 Try 先到。按照前面允许空回滚的逻辑,回滚会返回成功,事务管理器认为事务已回滚成功,则此时的 Try 接口不应该执行,否则会产生数据不一致,所以我们在 Cancel 空回滚返回成功之前先记录该条事务 xid 或业务主键,标识这条记录已经回滚过,Try 接口先检查这条事务xid或业务主键如果已经标记为回滚成功过,则不执行 Try 的业务操作。(其实就是幂等)
  • TCC 设计 - 幂等控制。幂等性的意思是:对同一个系统,使用同样的条件,一次请求和重复的多次请求对系统资源的影响是一致的。因为网络抖动或拥堵可能会超时,事务管理器会对资源进行重试操作,所以很可能一个业务操作会被重复调用,为了不因为重复调用而多次占用资源,需要对服务设计时进行幂等控制,通常我们可以用事务 xid 或业务主键判重来控制。在写代码时,TCC三个方法均需保证幂等性

与Seata AT模式比较
相对于 AT 模式,TCC 模式对业务代码有一定的侵入性,但是 TCC 模式无 AT 模式的全局行锁,TCC 性能会比 AT 模式高很多。但就是需要业务端写很多代码,这个是缺点,强侵入性

Seata Saga模式

长事务解决方案。在 Saga 模式下,分布式事务内有多个参与者,每一个参与者都是一个冲正补偿服务,需要用户根据业务场景实现其正向操作和逆向回滚操作。

  1. Saga模式适用于业务流程长且需要保证事务最终一致性的业务系统,Saga 模式一阶段就会提交本地事务,无锁、长流程情况下可以保证性能。
  2. 不能保证隔离。原因: Saga 模式由于一阶段已经提交本地数据库事务,且没有进行“预留”动作,所以不能保证隔离性

    Saga 模式不保证事务的隔离性,在极端情况下可能出现脏写。比如在分布式事务未提交的情况下,前一个服务的数据被修改了,而后面的服务发生了异常需要进行回滚,可能由于前面服务的数据被修改后无法进行补偿操作。这时的一种处理办法可以是“重试”继续往前完成这个分布式事务。由于整个业务流程是由状态机编排的,即使是事后恢复也可以继续往前重试。所以用户可以根据业务特点配置该流程的事务处理策略是优先“回滚”还是“重试”,当事务超时的时候,Server 端会根据这个策略不断进行重试。

    由于 Saga 不保证隔离性,所以我们在业务设计的时候需要做到“宁可长款,不可短款”的原则,长款是指在出现差错的时候站在我方的角度钱多了的情况,钱少了则是短款,因为如果长款可以给客户退款,而短款则可能钱追不回来了,也就是说在业务设计的时候,一定是先扣客户帐再入帐,如果因为隔离性问题造成覆盖更新,也不会出现钱少了的情况。

Seata Saga模式原理

基于状态机引擎的 Saga 实现: 它基于事件驱动架构,每个步骤都是异步执行的,步骤与步骤之间通过事件队列流转, 极大的提高系统吞吐量。每个步骤执行时会记录事务日志,用于出现异常时回滚时使用,事务日志会记录在与业务表所在的数据库内,提高性能。

“向前”

Saga 服务设计经验和TCC类似,内容也是一致的,Saga多了一个 【自定义事务恢复策略】 由于Saga 事务不保证隔离性,在极端情况下可能由于脏写无法完成回滚操作,所以状态机引擎除了提供”回滚”能力还需要提供”向前“执行的恢复操作,让业务最终执行成功。 用户可以根据业务特点配置该流程的事务处理策略是优先“回滚”还是“重试”,当事务超时的时候,服务器端会根据这个策略不断进行重试。

Seata XA模式

前提:

  1. 支持XA 事务的数据库。
  2. Java 应用,通过 JDBC 访问数据库。

    XA 协议是由X/Open 公司于1991 年发布的一套标准协议。 XA 是eXtended Architecture 的缩写,因此该协议旨在解决如何在异构系统中保证全局事务的原子性。

在 Seata 定义的分布式事务框架内,利用事务资源(数据库、消息服务等)对 XA 协议的支持,以 XA 协议的机制来管理分支事务的一种 事务模式。 从编程模型上,XA 模式与 AT 模式保持完全一致。

除了Seata还有哪些分布式事务框架?

  1. ByteTCC:美团点评开源的分布式事务框架,基于TCC补偿机制实现,支持高性能和高可用,提供了丰富的API接口和工具支持
  2. Nacos-AT:阿里巴巴开源的分布式事务框架,基于TCC补偿机制实现,可与Nacos和Spring Cloud等分布式框架无缝集成
  3. SkyWalking-Tx: Apache SkyWalking社区开源的分布式事务框架,支持TCC和Saga等事务模式,提供了完善的监控和追踪功能。
  4. TCC-Transaction:华为开源的分布式事务框架,基于TCC补偿机制实现,支持高性能和高可用,提供了简单易用的API接口和工具支持。
  5. Atomikos:开源的Java事务管理器,支持XA协议,可用于实现分布式事务的控制和管理,提供了简单易用的API接口和工具支持。

使用Seata会遇到哪些问题

  1. 在高并发下出现的问题:AT模式获取全局锁失败

    Seata的AT、XA模式都是基于全局事务实现的,在高并发的场景下会出现获取全局锁异常,因此这两种模式都不适用高并发场景; Seata TCC模式性能比AT模式的好一点,但是并发量大于100的话还是不适合; 如果基本没有什么并发量的话,可以选择AT模式;并发量在一百内的话可以使用TCC模式 高并发场景,不适合使用Seata,适合用中间件,例如用rocketmq替代Seata,可以弥补Seata的不足

  2. 高并发超时

    容易犯的错误: 服务调用->发现注解->创建事务->等待锁->获取锁->业务处理

    调整代码顺序就可以解决: 用户请求->等待锁->获取锁->服务调用->发现注解->创建事务->业务处理

  3. 还遇到一些字段长度的问题,调整表的字段长度。但是在最新版的Seata上想这些问题都已经解决了