本文将介绍Seata的引入方式和使用方式。环境基于雨润后端的开发环境(已配置好seata的Common包)。用法讲解基于现有的电商(yurun-service-mall的seata分支)项目进行概述。
1.电商项目概览
雨润的电商项目采用的是一种微服务架构方式。其具体的架构图如下:
如图所指示,mall-app-sale和mall-app-operation调用底层的交易、商品、用户中心等服务。用户中心使用的是mall-user数据库,其他底层服务均使用mall-trade数据库。
2.Seata的引入方式
- 整个工程的父pom文件引入
```xml
<dependencies>
<dependency>
<groupId>com.yurun</groupId>
<artifactId>yurun-common-seata</artifactId>
<version>${yurun.common.version}</version>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.4.1</version>
</dependency>
</dependencies>
</dependencyManagement>
- 涉及到分布式事务的服务(调用方或被调用方都要引入seata包)
```xml
<dependencies>
<!--seata分布式事务-->
<dependency>
<groupId>com.yurun</groupId>
<artifactId>yurun-common-seata</artifactId>
</dependency>
</dependencies>
在电商中,架构图中的所有服务的pom文件都引入了此项依赖
3.使用方式
接下来我会介绍几种常见的使用方式,大家可以根据具体的情况来选择。
3.1常规Service层使用
常规Service层的使用方式,和Spring的本地事务使用方式一样,在Service层上加上@GlobalTransaction
注解。(注:如果原来的方法有@Transactional注解,也无所谓可以都加在方法上不影响)
3.2方法已经try-catch的回滚方法
大部分项目中,不存在这样的情况。但是电商中就有这样的情况,一个Controller层方法被try-catch,里面调用各个底层服务。这种接口在 mall-app-sale和mall-operation中基本上都是。其解决案例可以看一下具体的几个接口:(/MALL140102 , /MALL150102),其关键点是通过Seata提供api方式回滚,下面代码模拟大致解决思路
手动回滚全局事务的代码:
GlobalTransactionContext.reload(RootContext.getXID()).rollback();
3.3一个方法中的某些数据库操作不走全局事务
有些场景下你希望同一个方法中的某些操作不进入全局事务管控,失败就失败,不管他,比如,我们的内购的要求是订单生成成功,库存可以扣减不成功。具体的案例可以看电商接口:(MALLAPP180101),其主要解决思路是,解绑xid。
关键代码
public void testDemo(){
//............
需要全局事务的代码
//............
String unbindXid = RootContext.unbind();
try {
//不用全局事务的代码块
} catch (Exception e) {
log.error("内购订单生成-记录活动明细和扣减库存部分异常", e);
} finally {
if (unbindXid != null) {
RootContext.bind(unbindXid);
}
}
//......
需要全局事务的代码
//.....
}
3.5服务中包含队列,ES等AT模式无法控制的操作时
这种情况下,个人选择将SQL相关操作和队列或ES的操作所使用的Seata模式分开,SQL相关操作还是用AT模式来控制,而队列、ES的操作使用TCC模式。具体例子可以看电商的(createNewOrder 和 delShopItem)一个是创建订单需要发送延时消息到队列,一个是删除商品,需要同时删除ES中的商品。TCC具体如何去编写。请看TCC如何使用的文章。TCC总结下来就是3步
①定义@LocalTCC接口
②实现try 、commit和rollback接口
③在Service层引用即可