Seata学习(三):Seata AT模式练习
一、AT模式流程分析
1、概念
AT 模式是 Seata 创新的一种非侵入式的分布式事务解决方案,Seata 在内部做了对数据库
操作的代理层,我们使用 Seata AT 模式时,seata 会创建数据源DataSource的代理对象
DataSourceProxy,实际上用的是 Seata 自带的数据源代理DataSourceProxy,Seata 在
这层代理中加入了很多逻辑,比如插入回滚 undo_log 日志,检查全局锁等。
在 AT 模式下,用户只需关注自己的“业务 SQL”,用户的 “业务 SQL” 作为一阶段,
Seata 框架会自动生成事务的二阶段提交和回滚操作。
2、AT模式执行流程
AT 模式事务提交主要分为两阶段,分别是:
1)一阶段:
在一阶段中,Seata会拦截“业务SQL“,首先解析SQL语义,找到要更新的业务数据,
在数据被更新前,保存下来"undo(undo log,即原数据,更改之前的数据)",然后执
行”业务SQL“更新数据,更新之后再次保存数据”redo(redo log,即更改之后的数据)“,
最后生成行锁(保证库中数据的一致性,保证当前数据不会被其他线程操作),这些操作
都在本地数据库事务内完成,这样保证了一阶段的原子性。
业务SQL和undo log日志记录在同一个本地事务中提交,最后释放本地锁和连接资源。
2)二阶段:
相对一阶段,二阶段比较简单,二阶段主要做2件事情,即:
(1)提交异步化(提交全局事务),非常快速地完成
(2)回滚,通过一阶段的回滚日志进行反向补偿
如果之前的一阶段中有本地事务没有通过,那么就执行全局回滚,否在执行全局提交,
回滚用到的就是一阶段记录的"undo Log",通过回滚记录生成反向更新SQL并执行,
已完成分支的回滚。全局事务“提交/回滚”完成后会释放所有资源和删除所有日志。
AT模式流程如下图所示:
二、AT模式使用练习
以订单服务order 和 仓库服务stock 为例,来学习下 AT 模式的使用;
大致业务流程如下:订单服务通过OpenFegin远程调用库存服务,然后stock库存服务减
库存,order订单服务生成订单;
表结构设计:
1)订单表
2)仓库表
3)在 order 和 stock服务都要创建undo_log表,建表语句如下:
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;
1、引入依赖
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-seata</artifactId><exclusions><exclusion><groupId>io.seata</groupId><artifactId>seata-spring-boot-starter</artifactId></exclusion></exclusions></dependency><dependency><groupId>io.seata</groupId><artifactId>seata-spring-boot-starter</artifactId></dependency>
2、配置seata
在当前服务的配置文件中配置seata相关配置,(order与stock 配置完全一致);
配置内容如下:
#配置Seata
#注意:seata server、server Client 共用一个配置,即seataServer.properties
seata:#用于唯一标识一个 Seata Client(即一个微服务实例),通常与 spring.application.name 或服务注册名称一致application-id: seata-at-orderregistry:type: nacosnacos:server-addr: localhost:8848#注意:seata server 与 seata client 的 namespace、group 必须一致namespace: ""#seata server 所在的分组,默认是 SEATA_GROUPgroup: SEATA_GROUP#seata server 服务名称,若seata server没有修改,则可以不配置application: seata-servercluster: defaultusername: nacospassword: nacosconfig:type: nacosnacos:server-addr: 47.117.80.49:8848namespace: ""#在1.4之后新增的配置dataId: seataServer.properties#seata server 所在的分组,默认是 SEATA_GROUPgroup: SEATA_GROUP下边2个可以不配置username: nacospassword: nacos#配置当前分支事务的事务分组#事务组名称,seataServer.properties 中配置的 service.vgroupMapping.事务分组名称(如:service.vgroupMapping.my_group)一致,tx-service-group: my_group# 与TC 通信的配置service:# 将当前分支事务(TM/RM)的分组映射到TCC(即seata server)中的集群上(即registry.config 文件中注册中心的配置项的 cluster = "default"),#这样即使分支事务的分组不同也可参与同一个全局事务vgroup-mapping:my_group: default #key=事务分组名称,需要与 seataServer.properties中配置的 “ service.vgroupMapping.事务分组名称 ”的值,值是seata server中配置的集群名称,如:service.vgroupMapping.my_group=default
3、示例代码
AT模式使用时,只需要在order 服务中添加注解 @GlobalTransactional 来开启全局事务就
行了,其他不用修改现有逻辑。@GlobalTransactional 可以标注在 order服务的controller层
或service层,没什么区别。
示例代码如下:
@RestController
public class OrderController {@Autowiredprivate OrderService orderService;@GetMapping("/order/create")/*** Order 相当于TM,即主动开启分布式全局事务的角色;* Stock 相当于 RM** 问题:开启全局分布式事务后,本地服务为什么不需要开启本地事务了?* seata 中通过数据源代理 来执行业务逻辑,然后seata 会自动帮我们提交本地事务;* 即本地事务由seata来提交,而不是以前的由spring显示的提交*/@GlobalTransactional(timeoutMills = 120000)// 开启分布式事务public String create(){try {orderService.create();}catch (Exception e){/*** 将异常抛出去**///throw new RuntimeException("生成订单成功失败");}return "生成订单成功";}
}
注意:
在AT 模式中,若 RM 角色的服务(如:stock)使用@ControllerAdvice 处理了全局
异常(RM的异常没有抛出),这就导致在这个分布式事务的流程中RM 的角色的异常不
能被seata识别,认为分支事务执行通过,全局事务正常提交,这就会造成 RM(如 stock)
角色本地事务回滚了,而 RM(如:order) 角色事务提交了,库中数据异常的情况。