写点什么

通俗易懂分布式事务之 2PC、3PC、Seata AT 模式、Seata TCC 模式

  • 2025-03-28
    福建
  • 本文字数:3584 字

    阅读完需:约 12 分钟

单机服务事务提交回滚操作是需要拿到 Connection 对象,调用提交 commit 方法或者 rollback 方法回滚的,例如下面操作

Connection conn = DriverManager.getConnection(...);try{  con.setAutoCommit(false);  Statement stmt = con.createStatement();  //1 or more queries or updates  con.commit();}catch(Exception e){  con.rollback();}finally{  con.close();}
复制代码

要想提交或者回滚,必须拥有 Connection 对象,然而在分布式环境,jvm 都是不同的,自然就拿不到其他服务的 Connection 对象,所以在分布式环境,我们无法保证原子性。因此分布式事务就需要另寻出路。

1. 术语

  • 全局事务:分布式环境,操作涉及很多服务,全局事务能保证这些服务的原子性,要提交就全部服务都会提交,要回滚全部服务都会回滚

  • 分支事务:分支事务建立在全局事务当中,是属于单个服务的事务

2. 2PC

2PC 即两阶段提交协议,是将整个事务流程分为两个阶段,准备阶段(Prepare phase)、提交阶段(commit phase)与本地事务的区别就是 会加入一个事务协调者的角色,这个事务协调者控制整体的事务提交

2.1 流程

  1. PreCommit 阶段


    事务协调者开启全局事务,参与者向事务协调者注册分支事务,然后走自己的业务代码,在这过程中如果发生了异常参与者会发送信息到事务协调者我异常了,需要全部回滚。如果一切正常,告诉事务协调者我可以提交了,等待事务协调者发送 commit 指令

  2. Commit 阶段


    事务协调者向所有分支事务发送 commit 指令,释放数据库资源

在这里举个具体的例子我们有两个服务:订单服务、库存服务用户需要买东西,首先要创建订单,创建订单前需要去锁定库存,然后再去创建订单。

1. 事务协调者开启全局事务2. 库存服务开启事务,向事务协调者注册一个分支事务3. 库存服务锁定库存4. 订单服务开启事务,向事务协调者注册一个分支事务5. 订单服务创建订单6. 事务协调者进行全局提交事务7. 库存服务提交事务8. 订单服务提交事务9. 全局事务完成
复制代码

在任何一个流程中异常,事务协调者都会发起全局回滚事务,这种方式,在全局事务完成前,Connection 对象都不会释放,因为你释放了你就无法控制了,缺点很明显,如果订单服务需要处理很久,库存服务 Connection 对象都不会释放,一直占用着,这种是强原子性的很浪费资源

2.2 实现 2PC

XA 协议,是 X/Open 组织提出的跨异构技术实现 2PC 的接口标准。使用 XA 协议首先前提就是,需要关系型数据库支持,目前主流数据库:

  • MySQL: InnoDB 引擎支持 XA 事务,并实现了 XA 接口。

  • PostgreSQL: 从版本 8.0 开始支持 XA 事务,通过插件接口实现。

  • Oracle: 支持 XA 事务,使用 OracleXADataSource 提供 XA 接口。

  • SQL Server: 支持 XA 事务,使用 MSDTC 提供 XA 接口。

  • DB2: 支持 XA 事务,使用 DB2 Universal JDBC 驱动器提供 XA 接口。

  • Sybase ASE: 支持 XA 事务,使用 JConnect 提供 XA 接口。

2.2.1 MySQL 实现流程:

  1. START

第一步开启 XA 事务库存 DB:

XA START 'xid'UPDATE  product SET num = num - 1 where id = 100XA END 'xid'
复制代码

订单 DB:

XA START 'xid'INSERT INTO order values(xxx)XA END 'xid'
复制代码
  1. PREPARE

第二步,准备就绪,等待事务协调者同意我提交库存 DB:

XA PREPARE 'xid'
复制代码

订单 DB:

XA PREPARE 'xid'
复制代码
  1. commit

全部提交库存 DB

XA COMMIT 'xid'
复制代码

订单 DB:

XA COMMIT 'xid'
复制代码

2.2.3 seata 支持 XA

XA 协议 JDK 接口定义:javax.sql.XADataSourceseata 框架支持 XA 协议,seataXA 模式文档:https://seata.apache.org/zh-cn/docs/v1.6/dev/mode/xa-mode/seata 官方 XA 模式 demo:https://github.com/apache/incubator-seata-samples/tree/master/xa-sample/springboot-feign-seata-xa

2.3 2PC 缺陷

  • 强一致性,每个分支事务得等待所有分支事务都准备好,才能提交释放

  • 协调者发生故障。分支事务会一直阻塞下去。

3. 3PC

三阶段提交协议(3PC)主要是为了解决两阶段提交协议的阻塞问题,2pc 存在的问题是当协作者崩溃时,参与者一直阻塞。与两阶段提交不同的是

  1. 引入超时机制

  2. 在最前面引入 CanCommit 阶段,为了防止参与者服务不可用,询问各个服务能不能开启事务

3.1 流程:

  1. CanCommit 阶段


    事务询问阶段,协调者向参与者发送 CanCommit 请求,询问是否可以执行事务提交操作。如果有参与者返回 NO,就不进行下一步操作了

  2. PreCommit 阶段


    进入 PreCommit 后就和 2PC 一样了,区别就是有超时机制

  3. Commit 阶段


    提交,释放数据库资源,与 2PC 一样

3.2 缺点

还是要等到全局事务完毕资源才释放,占用资源大

4. Seata AT 模式

Seata AT 模式文档:https://seata.apache.org/zh-cn/docs/v1.6/dev/mode/at-modeSeata AT 模式的核心是对业务无侵入,是一种改进后的 2PC

主要的实现是,在每个服务的数据库中新建一张 undo_log 表,结构如下:



content: 更改后的内容,rollback_info: 回滚的内容

每个分支事务执行 SQL 都会解析 SQL,保存 content,rollback_info,插入到 undo_log 表中。如果全局事务通知需要回滚,去通过对比 content 和解析 rollback_info,执行 sql 达到回滚的效果,如果全局事务通知全局事务成功,异步删除 undo_log 的记录。这种方式不需要等待全局事务的提交才提交,能解决 2PC、3PC 资源占用的问题,实际就是异常就去补偿的思想

4.1 流程

  1. 事务入口会开启全局事务

  2. 分支事务执行 SQL 之前会解析 SQL,生成前置镜像

  3. 解析出 SQL 如果是更新修改数据这种情况,seata 会锁住更新修改的数据,另一个全局事务进来想要更改更新修改的数据就需要等待全局事务结束才能进行更改对应着 seata 的 lock_table 表



  1. 执行 SQL

  2. 根据 SQL 生成后置镜像,如果到时候需要回滚,直接执行后置镜像

  3. 插入数据到 undo_log 表

  4. 分支事务注册事务(非入口事务通过传递 xid 来判断是加入哪个事务)

  5. 提交本地事务

  6. 如果所有分支事务都是正常的,就释放锁,然后异步删除所有的分支事务 undo_log 的记录

  7. 如果发生异常,所有事务都对前置镜像进行解析,生成 SQL,执行回滚操作

  8. 如果发生异常,需要回滚,后置镜像当中的值,不等于当前值,代表有其他业务一样的更改了这行数据,这时候就需要人工去处理了(往往如果设置了数据库字段 update_time 自动更新时间会很容易导致这个情况出现,不能用数据库的自动更新时间,自动更新时间得去到业务代码实现里)



4.2 缺点

seata AT 模式,事务是软状态需要考虑数据最终一致性,性能相对来说不是那么高,得去加锁,得动态去解析 SQL 插入数据库增加了和数据库的交互

5. Seata TCC 模式

Seata TCC 模式文档:https://seata.apache.org/zh-cn/docs/v1.6/dev/mode/tcc-mode

TCC 是分布式事务中的二阶段提交协议,它的全称为 Try-Confirm-Cancel,即资源预留(Try)、确认操作(Confirm)、取消操作(Cancel),TCC 模式对代码的入侵很大,但它的性能很好,还可以便捷的解决、空回滚、幂等、悬挂问题。

空回滚:没有执行 try 却执行了 cancel。参与者分支注册完成之后会执行参与者一阶段 try RPC 方法发送 rpc 时候网络延迟抖动,事务协调者全局回滚,参与者没有执行 try 却进入 cancel

幂等:多次进入 try。执行完二阶段之后,由于网络抖动或者宕机问题,会造成事务协调者收不到参与者执行 confirm 的返回结果,事务协调者会重复发起调用,直到二阶段执行结果成功

悬挂:执行了 cancel 又进入 try。进入 try 方法时,出现网路拥堵,由于 seata 全局事务有超时限制,执行 try 方法超时后,进行全局回滚,回滚完成后如果此时 RPC 请求才到达参与者 ,执行 try 方法进行资源预留,从而造成悬挂。

Seata 社区这个博客写的挺好 https://seata.apache.org/zh-cn/blog/seata-tcc/ 解释了 seata 是怎么样处理解决、空回滚、幂等、悬挂问题。

5.1 流程

假设一个转账需求,A 给 B 转 100,一般做法是判断够不够钱,如果够钱 A-100,B+100 在 TCC 模式下把这个需求拆分为 Try-Confirm-Cancel

  1. try 阶段(预留资源),查询 A 够不够钱,够的话冻结 A 100 块钱

  2. confirm 阶段(确认操作),执行业务代码,预先给 B + 100

  3. 如果一切正常,执行 A-100,B + 100

  4. Cancel 阶段(预留资源的取消,使资源回到初始状态),如果有异常,执行 try 的回滚逻辑,A 解除冻结的 100



5.2 缺点

TCC 模式有代码侵入,需要把一个业务拆分为三个方法,事务具有软状态,确认和取消操作都可能出现问题,需要考虑如何处理失败情况以保证最终一致性

行业拓展

分享一个面向研发人群使用的前后端分离的低代码软件——JNPF

基于 Java Boot/.Net Core 双引擎,它适配国产化,支持主流数据库和操作系统,提供五十几种高频预制组件,内置了常用的后台管理系统使用场景和实用模版,通过简单的拖拉拽操作,开发者能够高效完成软件开发,提高开发效率,减少代码编写工作。

JNPF 基于 SpringBoot+Vue.js,提供了一个适合所有水平用户的低代码学习平台,无论是有经验的开发者还是编程新手,都可以在这里找到适合自己的学习路径。

此外,JNPF 支持全源码交付,完全支持根据公司、项目需求、业务需求进行二次改造开发或内网部署,具备多角色门户、登录认证、组织管理、角色授权、表单设计、流程设计、页面配置、报表设计、门户配置、代码生成工具等开箱即用的在线服务。

用户头像

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
通俗易懂分布式事务之2PC、3PC、Seata AT模式、Seata TCC模式_伤感汤姆布利柏_InfoQ写作社区