当前位置: 首页 > news >正文

springcloud二-Seata3- Seata各事务模式

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 1. Seata各事务模式
    • 1.1 XA模式
      • 1.1.1 配置与使用
    • 1.2 AT模式
      • 1.2.1 工作机制
      • 1.2.2 读写隔离
        • 1.2.2.1 写隔离
        • 1.2.2.2 读隔离
      • 1.2.3 使用AT模式
      • 1.2.4 AT和XA的区别
    • 1.3 TCC模式
      • 1.3.1 工作机制
      • 1.3.2 TCC设计
        • 1.3.2.1 业务操作分析
        • 1.3.2.2 并发控制
        • 1.3.2.3 允许空回滚
        • 1.3.2.4 防悬挂控制
        • 1.3.2.5 幂等控制
        • 1.3.2.6 Seata解决⽅法
      • 1.3.3 TCC代码实现
        • 1.3.3.1 添加冻结字段
        • 1.3.3.2 TCC接⼝定义&实现
      • 1.3.4 接口测试
      • 1.3.5 TCC核⼼注解及参数描述
      • 1.3.6 优缺点
    • 1.4 Saga模式
    • 1.5 四种模式对⽐
  • 总结


前言

1. Seata各事务模式

Seata是⼀款开源的分布式事务解决⽅案, 致⼒于在微服务架构下提供⾼性能和简单易⽤的分布式事务服务. 它提供了多种事务模式, 为开发者提供了⼀站式的分布式事务解决⽅案.

AT模式
• TCC模式
• Saga模式
• XA模式

1.1 XA模式

XA 模式是从 1.2 版本⽀持的事务模式. XA 规范 是 X/Open 组织定义的分布式事务处理标准. Seata XA模式是利⽤事务资源 (数据库、消息服务等 ) 对 XA 协议的⽀持, 以 XA 协议的机制来管理分⽀事务的⼀种事务模式

意思就是直接利用mysql对事务进行提交和回滚

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

在这里插入图片描述
执行阶段:
可回滚:业务 SQL 操作放在 XA 分支中进行,由资源(数据库)对 XA 协议的支持来保证 可回滚
持久化:XA 分支完成后,执行 XA prepare,同样,由资源对 XA 协议的支持来保证 持久化(即,之后任何意外都不会造成无法回滚的情况)

完成阶段:
分支提交:执行 XA 分支的 commit
分支回滚:执行 XA 分支的 rollback

RM就是数据库,资源

在这里插入图片描述

执行阶段(E xecute):
XA start/XA end/XA prepare + SQL + 注册分支

完成阶段(F inish):
XA commit/XA rollback

TM和RM就是我们的业务系统集成的
TM:事务管理器
TC:事务协调者
RM:资源管理器

整体机制:

  1. 开启事务: 事务管理器 (TM ) 开启⼀个全局事务, 并与事务协调器 (TC ) 建⽴连接, TC返回⼀个全局事务ID (XID ) 给TM
  2. 分⽀事务注册与执⾏:资源管理器 (RM ) 收到业务操作请求后, 会向TC注册分⽀事务, 执⾏业务SQL, 并携带XID以保证事务的⼀致性.
    • 分⽀事务状态报告:RM执⾏完分⽀事务后, 向TC报告分⽀事务的执⾏状态.
    • 事务提交或回滚决策:TM在所有分⽀事务执⾏完毕后, 会通知TC事务结束. TC接收到事务结束通知后, 会检查各分⽀事务的执⾏状态. 如果所有分⽀事务都成功, 则TC通知所有RM提交事务. 如果有任意⼀个分⽀事务失败, 则TC通知所有RM回滚事务.
    • 分⽀事务提交或回滚:RM接收到TC的提交或回滚指令后, 执⾏相应的commit或rollback操作

在这里插入图片描述
在这里插入图片描述
在这个方法1中
create方法就是TM
里面的orderMapper这些方法就是RM
然后TC就是seata服务端

1.1.1 配置与使用

  1. 在application.yml中配置seata的事务模式.
seata:data-source-proxy-mode: XA
  1. 给发起全局事务的⼊⼝⽅法添加 @GlobalTransactional 注解

GlobalTransactional表示发起全局的事务

    @GlobalTransactional@Overridepublic Long create(OrderInfo orderInfo) {try {//插入订单orderMapper.insert(orderInfo);//扣余额accountApi.deduct(orderInfo.getUserId(), orderInfo.getMoney());//扣库存storageApi.deduct(orderInfo.getCommodityCode(), orderInfo.getCount());}catch (Exception e){log.error("下单失败, e: ", e);throw new RuntimeException("下单失败, e:", e);}return orderInfo.getId();}
  1. 重启服务, 并测试

在这里插入图片描述
余额用户表
在这里插入图片描述
库存表

先来一个正确的测试

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

这样就成功了

在这里插入图片描述
可以看到这就是事务的提交过程
PhaseTwo_Committed就是二阶段提交的意思

在这里插入图片描述
这就是正常提交的操作
在这里插入图片描述
可以看到什么xid都是有的

现在来演示回滚操作

{"userId": 1001,"commodityCode": 2001,"count": 1000,"money": 100
}

使用这个一定会库存不足
如果没有回滚的话,那么就会创建订单,扣余额,但是没有扣除库存
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
发现什么都没有变

在这里插入图片描述
明显看到了日志rollback,说明回滚了

优点
◦ 事务强⼀致性:XA模式能够满⾜ACID原则, 确保分布式事务的强⼀致性.
◦ 实现简单且⽆代码侵⼊: 常⽤数据库都⽀持XA协议, 使⽤Seata的XA模式⽆需修改业务代码, 只需进⾏简单的配置即可

缺点
◦ 性能较差:⼀阶段需要锁定数据库资源, 等待⼆阶段结束才释放, 导致事务资源⻓时间得不到释放, 锁定周期⻓, 从⽽影响性能
◦ 依赖关系型数据库: XA模式依赖数据库实现事务, 对于⼀些⾮关系型数据库或不⽀持XA协议的数据库, ⽆法使⽤.

1.2 AT模式

AT 模式是 Seata 创新的⼀种⾮侵⼊式的分布式事务解决⽅案. Seata 在内部做了对数据库操作的代理层, 我们使⽤ Seata AT 模式时, 实际上⽤的是 Seata ⾃带的数据源代理 DataSourceProxy, Seata 在这层代理中加⼊了很多逻辑, ⽐如插⼊回滚 undo_log ⽇志, 检查全局锁等

就是以前可以直接操作数据库,但是现在要经过这个代理层才可以操作数据库了
Seata AT模式针对两阶段提交协议的演变:
• ⼀阶段:业务数据和回滚⽇志记录在同⼀个本地事务中提交, 释放本地锁和连接资源.
• ⼆阶段:
◦ 提交异步化, ⾮常快速地完成.
◦ 回滚通过⼀阶段的回滚⽇志进⾏反向补偿

在这里插入图片描述
整体机制
• ⼀阶段:
◦ 注册分⽀事务:TM注册全局事务, 资源管理器 (RM ) 向事务协调器 (TC ) 注册分⽀事务
◦ 记录undo_log:RM在执⾏业务SQL操作1.4前, 会先解析SQL语句, 记录SQL更新前的快照和更新后的快照到undo_log⽇志表中. undo_log记录了⾜够的信息, 以便在需要回滚时能够恢复数据.
◦ 执⾏SQL并提交本地事务:RM执⾏业务SQL操作, 并直接提交本地事务, 此时数据会真实地提交到数据库中.
◦ 报告事务状态:RM向TC报告分⽀事务的执⾏状态, 告知其本地事务已提交.
• ⼆阶段:
◦ 提交成功:如果所有分⽀事务都成功, TC会通知RM清理undo_log相关的补偿信息, 完成整个分布式事务的处理.
◦ 提交失败:如果有任意⼀个分⽀事务失败, TC会通知RM进⾏回滚. RM根据undo_log中的补偿信息对数据进⾏反向补偿, 从⽽实现事务的回滚.

在这里插入图片描述

1.2.1 工作机制

看官方文档
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.2.2 读写隔离

1.2.2.1 写隔离

还是看官方文档
在这里插入图片描述

写隔离
在多线程并发操作同⼀个数据时, 有可能会出现脏写问题, 如图:
在这里插入图片描述
问题: 此时数据库的数据为1000, 那么久丢失了⼀次更新, 出现脏写了.
----》tx2白更新了

就是一个流程中,两个事务操作一个数据,导致出现问题

Seata的AT模式解决思路就是引⼊全局锁的概念, 在释放本地锁之前, 先拿到全局锁, 避免同⼀时刻有另外⼀个事务来操作当前数据.
• ⼀阶段本地事务提交前, 需要确保先拿到 全局锁 .
• 拿不到 全局锁 , 不能提交本地事务.
• 拿 全局锁 的尝试被限制在⼀定范围内, 超出范围将放弃, 并回滚本地事务, 释放本地锁
在这里插入图片描述
以⼀个⽰例来说明:
两个全局事务 tx1 和 tx2, 分别对 a 表的 m 字段进⾏更新操作, m 的初始值 1000.
tx1 先开始, 开启本地事务, 拿到本地锁, 更新操作 m = 1000 - 100 = 900. 本地事务提交前, 先拿到该记录的 全局锁 , 本地提交释放本地锁. tx2 后开始, 开启本地事务, 拿到本地锁, 更新操作 m = 900 - 100 =800. 本地事务提交前, 尝试拿该记录的 全局锁, tx1 全局提交前, 该记录的全局锁被 tx1 持有, tx2 需要重试等待 全局锁

在这里插入图片描述
tx1 ⼆阶段全局提交, 释放 全局锁. tx2 拿到 全局锁 提交本地事务

在这里插入图片描述

如果 tx1 的⼆阶段全局回滚, 则 tx1 需要重新获取该数据的本地锁, 进⾏反向补偿的更新操作, 实现分⽀的回滚.
此时, 如果 tx2 仍在等待该数据的 全局锁, 同时持有本地锁, 则 tx1 的分⽀回滚会失败. 分⽀的回滚会⼀直重试, 直到 tx2 的 全局锁 等锁超时, 放弃 全局锁 并回滚本地事务释放本地锁, tx1 的分⽀回滚最终成功.
因为整个过程 全局锁 在 tx1 结束前⼀直是被 tx1 持有的, 所以不会发⽣ 脏写 的问题

1.2.2.2 读隔离

在数据库本地事务隔离级别 读已提交 (Read Committed ) 或以上的基础上, Seata (AT 模式 ) 的默认全局隔离级别是 读未提交 (Read Uncommitted ) .—》会脏读
如果应⽤在特定场景下, 必需要求全局的 读已提交 , ⽬前 Seata 的⽅式是通过 SELECT FOR UPDATE 语句的代理.

在这里插入图片描述

SELECT FOR UPDATE 语句的执⾏会申请 全局锁 , 如果 全局锁 被其他事务持有, 则释放本地锁 (回滚SELECT FOR UPDATE 语句的本地执⾏ ) 并重试. 这个过程中, 查询是被 block 住的, 直到 全局锁 拿到,即读取的相关数据是 已提交 的, 才返回.
出于总体性能上的考虑, Seata ⽬前的⽅案并没有对所有 SELECT 语句都进⾏代理, 仅针对 FORUPDATE 的 SELECT 语句.

1.2.3 使用AT模式

根据工作机制来说,因为数据的操作记录都会记录到一个undo_log中,所以我们要先创建一个undo_log表

官网:步骤 2:创建 UNDO_LOG 表

注意这个表是创建在微服务的数据的数据库seata_test中的,不是seata服务器绑定的数据库seata

CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

在这里插入图片描述

然后是配置事务模式

seata:data-source-proxy-mode: AT

然后就可以测试了

初始状态

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
正常提交测试

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

branchType=AT我们看到这个,分支的类型为AT
然后是回滚测试
在这里插入图片描述

发现数据都是没有变化的,说明回滚了

在这里插入图片描述
发现还有undo_log deleted undo_log 日志删除的信息

1.2.4 AT和XA的区别

在这里插入图片描述
总结
• 如果业务对性能要求较⾼且可以接受最终⼀致性, 推荐使⽤AT模式.
• 如果业务对数据⼀致性要求极⾼且对性能要求不⾼, 推荐使⽤XA模式, 如银⾏.

1.3 TCC模式

1.3.1 工作机制

TCC事务就是在一开始资源预占,不执行事务,后面才执行事务
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
开发人员手动编写实现,XA和AT的话,直接配置好,使用注解就可以了
但是TCC就不行了

TCC 模式是 Seata ⽀持的⼀种由业务⽅(开发人员)细粒度控制的侵⼊式分布式事务解决⽅案, 是继 AT 模式后第⼆种⽀持的事务模式, 最早由蚂蚁⾦服贡献. 其分布式事务模型直接作⽤于服务层, 不依赖底层数据库, 可以灵活选择业务资源的锁定粒度, 减少资源锁持有时间, 可扩展性好, 可以说是为独⽴部署的 SOA 服务⽽设计的.

比如redis回滚。因为redis不支持AT,XA,所以就不能使用AT和XA回滚,只能TCC了,因为TCC不依赖底层数据库,因为是开发人员手动写的try,commit和rollback

在这里插入图片描述

Seata的全局事务, 整体是 两阶段提交 的模型. 全局事务是由若⼲分⽀事务组成的, 分⽀事务要满⾜ 两阶段提交 的模型要求, 即需要每个分⽀事务都具备⾃⼰的
• ⼀阶段 prepare ⾏为
• ⼆阶段 commit 或 rollback ⾏为

根据两阶段⾏为模式的不同, 将分⽀事务划分为 Automatic Transaction Mode 和 TCC TransactionMode.
AT 模式和TCC模式⾮常相似, 每阶段都是独⽴事务, 不同的是TCC通过⼈⼯编码来实现数据的恢复.

AT 模式基于 ⽀持本地 ACID 事务 的 关系型数据库:
• ⼀阶段 prepare ⾏为:在本地事务中, ⼀并提交业务数据更新和相应回滚⽇志记录.
• ⼆阶段 commit ⾏为:⻢上成功结束, ⾃动 异步批量清理回滚⽇志.
• ⼆阶段 rollback ⾏为:通过回滚⽇志, ⾃动 ⽣成补偿操作, 完成数据回滚.
相应的, TCC 模式, 不依赖于底层数据资源的事务⽀持:
• ⼀阶段 prepare ⾏为:调⽤ ⾃定义 的 prepare 逻辑.
• ⼆阶段 commit ⾏为:调⽤ ⾃定义 的 commit 逻辑.
• ⼆阶段 rollback ⾏为:调⽤ ⾃定义 的 rollback 逻辑.
所谓 TCC 模式, 是指⽀持把 ⾃定义 的分⽀事务纳⼊到全局事务的管理中

redis是非关系型数据库,所以AT不支持redis
TCC就可以用于非关系型数据库

在两阶段提交协议 (2PC, Two Phase Commitment Protocol ) 中, 资源管理器 (RM, resourcemanager ) 需要提供"准备"、“提交"和"回滚” 3 个操作. ⽽事务管理器 (TM, transaction manager )分 2 阶段协调所有资源管理器, 在第⼀阶段询问所有资源管理器"准备"是否成功, 如果所有资源均"准备"成功则在第⼆阶段执⾏所有资源的"提交"操作, 否则在第⼆阶段执⾏所有资源的"回滚"操作, 保证所有资源的最终状态是⼀致的, 要么全部提交要么全部回滚.资源管理器有很多实现⽅式, 其中 TCC (Try-Confirm-Cancel ) 是资源管理器的⼀种服务化的实现.

TCC 是⼀种⽐较成熟的分布式事务解决⽅案, 可⽤于解决跨数据库、跨服务业务操作的数据⼀致性问题. TCC 其 Try、Confirm、Cancel 3 个⽅法均由业务编码实现, 故 TCC 可以被称为是服务化的资源管理器.TCC 的 Try 操作作为⼀阶段, 负责资源的检查和预留. Confirm 操作作为⼆阶段提交操作, 执⾏真正的业务. Cancel 是⼆阶段回滚操作, 执⾏预留资源的取消, 使资源回到初始状态.

在这里插入图片描述

⽤⼾实现 TCC 服务之后, 该 TCC 服务将作为分布式事务的其中⼀个资源, 参与到整个分布式事务中. 事务管理器分 2 阶段协调 TCC 服务, 在第⼀阶段调⽤所有 TCC 服务的 Try ⽅法, 在第⼆阶段执⾏所有 TCC 服务的 Confirm 或者 Cancel ⽅法. 最终所有 TCC 服务要么全部都是提交的, 要么全部都是回滚的.

1.3.2 TCC设计

1.3.2.1 业务操作分析

接⼊ TCC 前, 业务操作只需要⼀步就能完成, 但是在接⼊ TCC 之后, 需要考虑如何将其分成 2 阶段完成,把资源的检查和预留放在⼀阶段的 Try 操作中进⾏, 把真正的业务操作的执⾏放在⼆阶段的 Confirm 操作中进⾏.

update storage_tbl set count = count - #{count} where commodity_code = #{commodity_code}

以这个为例

接⼊TCC之后, 就需要考虑, 如何将扣库存分成两步完成.
• Try 操作:资源的检查和预留.
在扣库存场景下, Try 操作要做的事情就是先检查 A 商品库存是否⾜够, 再冻结要扣的 20 个 (预留资源 )
. 此阶段不会发⽣真正的扣库存.
• Confirm 操作:执⾏真正业务的提交.
在扣库存场景下, Confirm 阶段⾛的事情就是发⽣真正的扣库存, 把A商品中已经冻结的 30 个库存扣掉.
• Cancel 操作:预留资源的是否释放.
在扣库存场景下, 扣库存操作取消, Cancel 操作执⾏的任务是释放 Try 操作冻结的 20个库存, 使 A 商品回到初始状态.
在这里插入图片描述

1.3.2.2 并发控制

⽤⼾在实现 TCC 时, 应当考虑并发性问题, 将锁的粒度降到最低, 以最⼤限度的提⾼分布式事务的并发性.
以下还是以A商品扣库存为例, “A商品 有 100 个库存, 事务 T1 要扣除其中的 20个, 事务 T2 也要扣除 20个, 出现并发”.
在⼀阶段 Try 操作中, 分布式事务 T1 和分布式事务 T2 分别冻结资⾦的那⼀部分资⾦, 相互之间⽆⼲扰.
这样在分布式事务的⼆阶段, ⽆论 T1 是提交还是回滚, 都不会对 T2 产⽣影响, 这样 T1 和 T2 在同⼀笔业务数据上并⾏执⾏

在这里插入图片描述

说明TCC是支持并发控制的

1.3.2.3 允许空回滚

TCC也允许空回滚

事务协调器在调⽤ TCC 服务的⼀阶段 Try 操作时, 可能会出现因为丢包⽽导致的⽹络超时,此时事务管理器会触发⼆阶段回滚, 调⽤ TCC 服务的 Cancel 操作, ⽽ Cancel 操作调⽤未出现超时.
TCC 服务在未收到 Try 请求的情况下收到 Cancel 请求, 这种场景被称为空回滚. 空回滚在⽣产环境经常出现, ⽤⼾在实现TCC服务时, 应允许空回滚的执⾏, 即收到空回滚时返回成功.

在这里插入图片描述
空回滚就是TCC服务(RM)没有执行try,却收到了Cancle的请求,就是空回滚,直接就进行回滚了—》不行的

1.3.2.4 防悬挂控制

事务协调器在调⽤ TCC 服务的⼀阶段 Try 操作时, 可能会出现因⽹络拥堵⽽导致的超时, 此时事务管理器会触发⼆阶段回滚, 调⽤ TCC 服务的 Cancel 操作, Cancel 调⽤未超时. 在此之后, 拥堵在⽹络上的⼀阶段 Try 数据包被 TCC 服务收到, 出现了⼆阶段 Cancel 请求⽐⼀阶段 Try 请求先执⾏的情况, 此 TCC 服务在执⾏晚到的 Try 之后, 将永远不会再收到⼆阶段的 Confirm 或者 Cancel , 造成 TCC 服务悬挂.
⽤⼾在实现 TCC 服务时, 要允许空回滚, 但是要拒绝执⾏空回滚之后 Try 请求, 要避免出现悬挂.

意思就是try没有执行,空回滚了,执行cancle了,但是后面try又执行了,但是二阶段不会再次执行了—》不行

所以在执行cancle的时候判断try有没有执行,在执行try的时候,判断cancle有没有执行

在这里插入图片描述

1.3.2.5 幂等控制

⽆论是⽹络数据包重传, 还是异常事务的补偿执⾏, 都会导致 TCC 服务的 Try、Confirm 或者 Cancel 操作被重复执⾏. ⽤⼾在实现 TCC 服务时, 需要考虑幂等控制, 即 Try、Confirm、Cancel 执⾏⼀次和执⾏多次的业务结果是⼀样的.

1.3.2.6 Seata解决⽅法

TCC 模式中存在的三⼤问题是幂等、悬挂和空回滚. 在 Seata1.5.1 版本中, 增加了⼀张事务控制表tcc_fence_log,包含事务的 XID 和 BranchID 信息, 来解决这个问题

CREATE TABLE IF NOT EXISTS `tcc_fence_log`
(
`xid` VARCHAR(128) NOT NULL COMMENT 'global id',
`branch_id` BIGINT NOT NULL COMMENT 'branch id',
`action_name` VARCHAR(64) NOT NULL COMMENT 'action name',
`status` TINYINT NOT NULL COMMENT
'status(tried:1;committed:2;rollbacked:3;suspended:4)',
`gmt_create` DATETIME(3) NOT NULL COMMENT 'create time',
`gmt_modified` DATETIME(3) NOT NULL COMMENT 'update time',
PRIMARY KEY (`xid`, `branch_id`),
KEY `idx_gmt_modified` (`gmt_modified`),
KEY `idx_status` (`status`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;

• 空回滚
在 Try ⽅法执⾏时插⼊⼀条记录, 表⽰⼀阶段执⾏了, 执⾏ Cancel ⽅法时读取这条记录, 如果记录不存在, 说明 Try ⽅法没有执⾏, 以此来避免空回滚

• 悬挂
在 Rollback 阶段, 如果查询到事务控制表中没有记录, 说明Cancle先于Try执⾏了. 因此插⼊⼀条status=4 状态的记录. 当Try阶段执⾏时, 判断status=4 , 则说明有⼆阶段 Cancel 已执⾏, 并返回 false以阻⽌⼀阶段 Try ⽅法执⾏成功

• 幂等
在 TCC 事务控制表中增加⼀个记录状态的字段 status, 该字段有 4 个值, 分别为:
a. tried(1) : 表⽰ Try 阶段已经执⾏过
b. committed(2): 表⽰⼆阶段 Commit 已经执⾏完成
c. rollbacked(3): 表⽰⼆阶段 Rollback 已经执⾏完成
d. suspended(4): 表⽰空回滚/悬挂/中⽌状态.
⼆阶段 Confirm/Cancel ⽅法执⾏后, 将状态改为 committed 或 rollbacked 状态. 当重复调⽤⼆阶段Confirm/Cancel ⽅法时, 判断事务状态即可解决幂等问题

Seata1.5.1 版本之前版本,这三个解决办法就要手动去写了

1.3.3 TCC代码实现

项目spring-cloud-seata-tcc-demo

我们是可以这样的,库存服务使用TCC,订单服务使用AT

以库存服务为例, 实现TCC

先创建事务控制表

为了解决幂等, 悬挂和空回滚, Seata1.5.1版本增加了⼀张事务控制表tcc_fence_log,包含事务的 XID 和BranchID 信息, 来解决这个问题.

在这里插入图片描述
在微服务中的数据库中执行

1.3.3.1 添加冻结字段

这个是为了模仿预占资源
为了完成⼀阶段(Try)资源的预检, 以及T2阶段事务的提交或回滚, 需要增加⼀个新的字段, 表⽰冻结部分

ALTER TABLE storage_tbl ADD COLUMN freeze_count INT(11) unsigned DEFAULT 0 COMMENT '冻结库存';

在这里插入图片描述

修改相应的实体类

@Data
@TableName("storage_tbl")
public class StorageInfo {@TableIdprivate Long id;private String commodityCode;private Integer count;private Integer freezeCount;
}
1.3.3.2 TCC接⼝定义&实现

TCC的Try、Confirm、Cancel⽅法都需要⾃⼰来实现.

Tcc基本使用

public interface StorageTccService {@TwoPhaseBusinessAction(name = "DubboTccActionOne", commitMethod = "commit", rollbackMethod = "rollback")public boolean prepare(BusinessActionContext actionContext, @BusinessActionContextParameter(paramName = "a") String a);public boolean commit(BusinessActionContext actionContext);public boolean rollback(BusinessActionContext actionContext);
}

Seata 会把一个 TCC 接口当成一个 Resource,也叫 TCC Resource。在业务接口中核心的注解是 @TwoPhaseBusinessAction,表示当前方法使用 TCC 模式管理事务提交,并标明了 Try,Confirm,Cancel 三个阶段。name属性,给当前事务注册了一个全局唯一的的 TCC bean name。同时 TCC 模式的三个执行阶段分别是:

Try 阶段,预定操作资源(Prepare) 这一阶段所以执行的方法便是被 @TwoPhaseBusinessAction 所修饰的方法。如示例代码中的 prepare 方法。
Confirm 阶段,执行主要业务逻辑(Commit) 这一阶段使用 commitMethod 属性所指向的方法,来执行Confirm 的工作。
Cancel 阶段,事务回滚(Rollback) 这一阶段使用 rollbackMethod 属性所指向的方法,来执行 Cancel 的工作。

在这里插入图片描述
其次,可以在 TCC 模式下使用 BusinessActionContext 在事务上下文中传递查询参数。如下属性:
xid 全局事务id
branchId 分支事务id
actionName 分支资源id,(resource id)—》就是DubboTccActionOne
actionContext 业务传递的参数,可以通过 @BusinessActionContextParameter 来标注需要传递的参数。

prepare接收参数String a用BusinessActionContextParameter标记,把参数a放入actionContext,然后传递给rollback这些方法

在定义好 TCC 接口之后,我们可以像 AT 模式一样,通过 @GlobalTransactional 开启一个分布式事务。

注意,如果 TCC 参与者是本地 bean(非远程RPC服务),本地 TCC bean 还需要在接口定义中添加 @LocalTCC 注解,比如,
在这里插入图片描述

反正看官网就对了

然后开始实现接口

TwoPhaseBusinessAction注解应该放在实现类里面,和@Service是一个道理
因为接口并没有实现,TwoPhaseBusinessAction注解放在接口里面没有意义

public interface StorageTccService {boolean prepare(String commodityCode, Integer count);boolean commit(BusinessActionContext actionContext);boolean rollback(BusinessActionContext actionContext);
}
@Service
@LocalTCC   //因为没有调用远程,都是本地的操作,所以用LocalTCC
@Slf4j
public class StorageTccServiceImpl implements StorageTccService {@Autowiredprivate StorageMapper storageMapper;@Override//useTCCFence = true表示会自动处理空回滚,业务悬挂,幂等等问题@TwoPhaseBusinessAction(name = "storageDeduct", commitMethod = "commit", rollbackMethod = "rollback",useTCCFence = true)public boolean prepare(@BusinessActionContextParameter("commodityCode") String commodityCode,@BusinessActionContextParameter("count")Integer count) {//BusinessActionContextParameter表示把参数放入BusinessActionContextlog.info("一阶段try");try {UpdateWrapper<StorageInfo> updateWrapper = new UpdateWrapper<>();updateWrapper.lambda().setSql("count = count - "+ count).setSql("freeze_count = freeze_count + " +count)//冻结部分。资源预留.eq(StorageInfo::getCommodityCode, commodityCode);storageMapper.update(updateWrapper);return true;} catch (Exception e) {log.error("扣减库存失败, e:", e);throw new RuntimeException("扣减库存失败!", e);}}@Overridepublic boolean commit(BusinessActionContext actionContext) {log.info("二阶段commit");//去除冻结部分String commodityCode = (String) actionContext.getActionContext("commodityCode");Integer count = (Integer) actionContext.getActionContext("count");UpdateWrapper<StorageInfo> updateWrapper = new UpdateWrapper<>();updateWrapper.lambda().setSql("freeze_count = freeze_count - " +count)//释放预留.eq(StorageInfo::getCommodityCode, commodityCode);Integer res = storageMapper.update(updateWrapper);return res==1;}@Overridepublic boolean rollback(BusinessActionContext actionContext) {log.info("二阶段rollback");//释放冻结库存,恢复原有库存String commodityCode = (String) actionContext.getActionContext("commodityCode");Integer count = (Integer) actionContext.getActionContext("count");UpdateWrapper<StorageInfo> updateWrapper = new UpdateWrapper<>();updateWrapper.lambda().setSql("count = count + "+ count).setSql("freeze_count = freeze_count - " +count)//释放预留.eq(StorageInfo::getCommodityCode, commodityCode);Integer res = storageMapper.update(updateWrapper);return res==1;}
}

然后controller也要使用tcc来操作了

@Slf4j
@RequestMapping("/storage")
@RestController
public class StorageController {//    @Autowired
//    private StorageService storageService;@Autowiredprivate StorageTccService storageTccService;/**s* 扣库存* @param code 商品编号* @param count 要扣减的数量*/@RequestMapping("/{code}/{count}")public ResponseEntity<String> deduct(@PathVariable("code") String code,@PathVariable("count") Integer count){log.info("扣减库存, code:{}, count:{}", code, count);storageTccService.prepare(code, count);return ResponseEntity.ok("success");}
}

这样就成功了

1.3.4 接口测试

初始状态

在这里插入图片描述
在这里插入图片描述

正常提交测试

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
事务控制表新增了一条内容

storageDeduct就是我们在注解TwoPhaseBusinessAction中定义的名称
状态为2,是commit

在这里插入图片描述
我们在commit的时候打断点,然后调试服务

再次测试‘
在这里插入图片描述
发现状态为1,说明已经try了

在这里插入图片描述
观看storage的日志
发现有branchType=TCC,说明分支类型是TCC
但是Account的分支类型还是AT
所以AT和TCC是可以放在一起使用的

状态三是rollback完成—》让try成功
在这里插入图片描述
所以我们修改一下order-service的顺序,让try的时候成功,然后在回滚
而不是在try的时候就失败了

在这里插入图片描述
然后在扣减余额的时候定义异常,表示失败了,要回滚–》都要回滚
在这里插入图片描述
在这里插入图片描述
说明真的回滚了

branch register success这个是分支注册成功

rm handle branch rollback表示分支回滚,分支类型是TCC
在这里插入图片描述

发现状态真的变为3了

在这里插入图片描述
数据只扣了12,说明没有更改,回滚了

我们来测试状态4:表⽰空回滚/悬挂/中⽌状态
在这里插入图片描述
我们在try的时候发出异常—》try没有完成,但是有异常,要回滚----》空回滚

在这里插入图片描述
去掉account的异常

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
但是数据并没有改变,这个是因为我们配置了对空回滚的处理,防止了空回滚的问题

1.3.5 TCC核⼼注解及参数描述

TCC有两个核⼼注解 @TwoPhaseBusinessAction 和 @LocalTCC , 以及两个重要参数:BusinessActionContext 和 @BusinessActionContextParameter

• @LocalTCC
@LocalTCC 注解⽤来表⽰实现了⼆阶段提交的本地的TCC接⼝.—》表示二阶段提交调用的是本地的接口

BusinessActionContext
事务上下⽂. 可以使⽤此⼊参在TCC模式下, 在事务上下⽂中, 传递查询参数. 如下属性:
◦ xid 全局事务id
◦ branchId 分⽀事务id
◦ actionName 分⽀资源id, (resource id )
actionContext 业务传递参数Map, 可以通过@BusinessActionContextParameter来标注需要传递的参数

@BusinessActionContextParameter
⽤此注解标注需要在事务上下⽂中传递的参数. 被此注解修饰的参数, 会被设置在
BusinessActionContext中, 可以在commit和rollback阶段中, 可以通过BusinessActionContext的
getActionContext⽅法获取传递的业务参数值. 如下:
context.getActionContext(“id”).toString();

@TwoPhaseBusinessAction
@TwoPhaseBusinessAction 表⽰了当前⽅法使⽤TCC模式管理事务提交, 被
@TwoPhaseBusinessAction 所修饰的⽅法便是Try阶段执⾏的逻辑.
@TwoPhaseBusinessAction 属性说明:
a. name: 给当前事务注册了⼀个全局唯⼀的的TCC bean name. 如代码⽰例中, name =“storageTccDeduct”.
b. commitMethod: 对应TCC模型的⼆阶段Confirm阶段执⾏的⽅法名称
c. commitArgsClasses: commitMethod 默认的参数为BusinessActionContext, 如果需要修改, 需要指定commitArgsClasses
d. rollbackMethod: 对应TCC模型的⼆阶段Cancel执⾏的⽅法名称.
e. rollbackArgsClasses: rollbackMethod默认的参数为BusinessActionContext, 如果需要修改, 需要指定rollbackArgsClasses
f. useTCCFence: 是否启⽤栅栏功能, 主要⽤于解决 TCC 模式下的幂等性、空回滚和悬挂问题. 需要创建事务控制表. 默认为false, 即不启⽤栅栏功能.

在这里插入图片描述

@LocalTCC
public class NormalTccActionImpl implements NormalTccAction {@TwoPhaseBusinessAction(name = "tccActionForTest", commitMethod = "commit",rollbackMethod = "rollback", commitArgsClasses = {BusinessActionContext.class,TccParam.class}, rollbackArgsClasses = {BusinessActionContext.class, TccParam.class})@Overridepublic String prepare(@BusinessActionContextParameter("a") int a,@BusinessActionContextParameter(paramName = "b", index = 0) List b,@BusinessActionContextParameter(isParamInProperty = true) TccParam tccParam) {return "a";}@Overridepublic boolean commit(BusinessActionContext actionContext,@BusinessActionContextParameter("tccParam") TccParam param) {return false;}@Overridepublic boolean rollback(BusinessActionContext actionContext,@BusinessActionContextParameter("tccParam") TccParam param) {return false;}public boolean otherMethod(){return true;}
}

啥意思呢
意思就是commit要指定其他参数的时候,比如TccParam
这个参数要在prepare中定义好@BusinessActionContextParameter(isParamInProperty = true) TccParam tccParam
而且还要在TwoPhaseBusinessAction中进行声明commitArgsClasses ={BusinessActionContext.class, TccParam.class}
这样才能传递参数

这样就是不想从BusinessActionContext 中获取变量的方法

1.3.6 优缺点

TCC (Try-Confirm-Cancel ) 模式是⼀种分布式事务解决⽅案, 适⽤于需要⾼性能和强⼀致性的复杂业务场景.
优点:
• 性能⾼: TCC模式没有全局锁, 事务提交和回滚由应⽤代码实现, 资源锁定时间短, 性能较⾼
• 灵活性强: 可以⾃定义Try、Confirm和Cancel逻辑, 适应复杂的业务场景.
• 不依赖数据库事务: 不依赖底层数据库, 能够实现跨数据库、跨应⽤资源管理, 提供更细粒度的控制,可以⽤于⾮事务型数据库

缺点:
• 开发成本⾼: 需要⽤⼾⾃⼰编写Try、Confirm和Cancel接⼝, 增加开发⼯作量
• 适⽤场景有限; 对于复杂业务, 补偿逻辑可能会⾮常复杂, 补偿成本过⾼的业务不适合使⽤TCC模式.

1.4 Saga模式

Saga模式

Saga 模式是 SEATA 提供的长事务解决方案,在 Saga 模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务都由业务开发实现。

在这里插入图片描述

适用场景:
业务流程长、业务流程多
参与者包含其它公司或遗留系统服务,无法提供 TCC 模式要求的三个接口

在这里插入图片描述

优势:
一阶段提交本地事务,无锁,高性能
事件驱动架构,参与者可异步执行,高吞吐
补偿服务易于实现

缺点:
不保证隔离性
在这里插入图片描述
在这里插入图片描述
这个需要我们去画这个json的状态图
—》实现起来比较复杂
状态机设计器

在这里插入图片描述

可以导出为json文件,就可以用了

1.5 四种模式对⽐

在这里插入图片描述

总结

http://www.dtcms.com/a/556846.html

相关文章:

  • MySQL 全链路性能调优:从 “凌晨三点被叫醒“ 到 “0.1 秒响应“ 的实战心法(超能优化版)
  • linux命令-用户管理-7
  • 【JavaScript】Pointer Events 与移动端交互
  • 客户评价 网站织梦cms侵权
  • 文件上传下载
  • 深入GoChannel:并发编程的底层奥秘
  • JS面试基础(一) 垃圾回收,变量与运算符
  • 2025年渗透测试面试题总结-225(题目+回答)
  • 重庆电商平台网站建设合肥推广优化公司
  • Linux命令行基础:常用命令快速上手(附代码示例)
  • 在Ubuntu Desktop操作系统下,rustdesk客户端如何设置成开机自动启动?
  • 建设静态网站怎么制作网页链接在微信上发
  • Pandas-DataFrame 数据结构详解
  • 用层还是表格做网站快淘宝建设网站的好处
  • 2025年渗透测试面试题总结-224(题目+回答)
  • 详细了解TLS、HTTPS、SSL原理
  • 弹性力学| 应力应变关系
  • 网站建设实习收获多平台网页制作
  • BPE(Byte Pair Encoding)详解:从基础原理到现代NLP应用
  • 【Java学习路线| 最佳食用指南 60days】
  • nfs的运用
  • 【企业架构】TOGAF架构标准规范-迁移计划
  • 做网站用asp还是php亚马逊建站服务
  • 数据结构(15)
  • 《算法闯关指南:优选算法--前缀和》--29.和为k的子数组,30.和可被k整除的子数组
  • 如何在GitHub仓库中添加MIT开源许可证
  • 在Linux(deepin-community-25)下安装MongoDB
  • WebView 最佳封装模板(BaseWebActivity + WebViewHelper)
  • 珲春市建设局网站中国设计网字体
  • 杭州英文网站建设杭州微信小程序外包