本文将介绍基于springcloud使用阿里巴巴分布式事务框架seata的AT模式(1.0.0版本),AT模式基本上能满足我们使用分布式事务80%的需求(非关系型数据库与中间件的操作、跨公司服务的调用跨语言的应用调用需要结合SEATA-TCC模式)。关于seata的介绍可以点击这里进入seata官网。
1、引入seata框架,配置好seata基本配置,建立undo_log表
2、消费者引入全局事务注解@GlobalTransactional
3、生产者引入全局事务注解@GlobalTransactional
完成!!
AT模式是指Automatic (Branch) Transaction Mode 自动化分支事务,使用AT模式的前提是
基于以上两点,我们搭建两个微服务,分别是service-tm(事务发起者) 与service-at(资源管理器/AT模式分布式事务)。
4.1 搭建seata服务端
4.1.1 搭建seata服务端(作为TC存在)
4.1.2 mysql运行script.sql
4.2 创建maven父工程seata-demo (源码点此)
建议下载源码配套查看,父工程除了引入SpringCloud相关依赖外
还需要引入spring-cloud-alibaba-seata包,此包包含了seata-all与springboot-seata-starter
com.alibaba.cloud
spring-cloud-alibaba-seata
2.2.0.RELEASE
openFeign
org.springframework.cloud
spring-cloud-starter-openfeign
druid(用于数据源代理)
com.alibaba
druid
1.1.21
4.3 创建子工程seata-at(作为RM存在,源码点此)
4.3.1 Service部分 (此处为了方便理解,把controller合并,实际代码则不然)
此处@GlobalTransactional注解到方法中开启全局事务
@Slf4j
@RestController
public class AtServiceImpl implements AtService {
@Autowired
AtDAO atDAO;
/**
* rm-at的服务端业务逻辑方法
* 这里使用Transactional注解的目的是将多个sql合并成一个本地事务
* 减少tc交互,提升性能
* 为了方便理解,此处把controller合并
* @param params - name 入参
* @return String
*/
@Override
@GlobalTransactional(timeoutMills = 60000 * 2)
@Transactional(rollbackFor = Exception.class ,propagation = Propagation.REQUIRED)
@PostMapping("/at-insert")
public String insert(@RequestBody Map params) {
log.info("------------------> xid = " + RootContext.getXID());
atDAO.insert(params);
return "success";
}
}
取得XID的方式为:
RootContext.getXID()
其余代码不重要,直接看源码
4.3.2 application.yml配置文件
server:
port: 7900
tomcat.uri-encoding: utf-8
spring:
application:
name: service-at
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/seata_test?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: root
#一定要配置druid
type: com.alibaba.druid.pool.DruidDataSource
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
#使用file作为存储
seata:
enabled: true
#此处applicationId与serviceGroup保持实例一致便可
application-id: ${spring.application.name}
tx-service-group: seataGroup-${seata.application-id}
service:
vgroup-mapping: default
grouplist: 127.0.0.1:8091
config:
type: file
file:
name: file.conf
registry:
type: file
file:
name: file.conf
4.4 创建子工程seata-tm
4.4.1 部分源码
@Slf4j
@RestController
public class TmServiceImpl implements TmService {
@Autowired
private TmDAO tmDAO;
@Autowired
private ServiceATFeign atFeign;
/**
* 请求本地事务插入一条记录
* 再请求at服务插入一条记录
*
* @param params - name
* @return String
*/
@Override
@Transactional
@GlobalTransactional(timeoutMills = 60000 * 2)
@GetMapping("/insert-at")
public String insertAt(Map params) {
log.info("------------------> xid = " + RootContext.getXID());
//本地事务插入一条数据
tmDAO.insert(params);
//远程at事务插入一条数据
atFeign.insertAT(params);
//强行抛出异常回滚
throw new RuntimeException("AT服务测试回滚");
//return "success";
}
}
值得注意的是,这里和本地事务一样,不能够使用catch将异常进行捕获 ,即便是使用@ExceptionHandler进行捕获,也会使得事务被吞掉。如果一定要使用异常捕获,那么可以使用手工全局事务回滚:
try{
…………
}catch(Exception e){
log.error(e);
//调用此段代码进行手工事务回滚
GlobalTransactionContext.reload(RootContext.getXID()).rollback();
}
Feign的调用没有什么不同
@FeignClient(value = "service-at")
public interface ServiceATFeign {
/**
* 调用at服务插入记录
*
* @param params - name
* @return String
*/
@PostMapping(value = "/at-insert")
String insertAT(Map params);
}
至此,AT模式项目代码比较重要的部分已经完成。
5.1 启动seata-server
本文使用file单机模式启动seata-server,见到这一步则说明启动成功
5.2 先注释掉service-tm下service的 @Transactional 否则看不到undo_log
@Override
// @Transactional
@GlobalTransactional(timeoutMills = 60000 * 2)
public String insertAt(Map params) {
log.info("------------------> xid = " + RootContext.getXID());
tmDAO.insert(params);
atFeign.insertAT(params);
throw new RuntimeException("AT服务测试回滚");
// return "success";
}
5.3 debug模式启动seata-tm、seata-at
seata-server见到以下日志则说明注册成功
在service-tm此处执行断点
浏览器请求
执行到第一个断点,即tmDAO.insert(params)处
这时候查看日志表service_tm已经插入到一条日志tanzj
查看数据表undo_log
看到一条日志,我们将其bejson
rollback_info:此处记录了该字段修改前的镜像和修改后的镜像,是seata回退的核心
{
"@class": "io.seata.rm.datasource.undo.BranchUndoLog",
"xid": "192.168.0.103:8091:2035687749",
"branchId": 2035687750,
"sqlUndoLogs": ["java.util.ArrayList", [{
"@class": "io.seata.rm.datasource.undo.SQLUndoLog",
"sqlType": "INSERT",
"tableName": "service_tm",
"beforeImage": {
"@class": "io.seata.rm.datasource.sql.struct.TableRecords$EmptyTableRecords",
"tableName": "service_tm",
"rows": ["java.util.ArrayList", []]
},
"afterImage": {
"@class": "io.seata.rm.datasource.sql.struct.TableRecords",
"tableName": "service_tm",
"rows": ["java.util.ArrayList", [{
"@class": "io.seata.rm.datasource.sql.struct.Row",
"fields": ["java.util.ArrayList", [{
"@class": "io.seata.rm.datasource.sql.struct.Field",
"name": "id",
"keyType": "PrimaryKey",
"type": 4,
"value": 93
}, {
"@class": "io.seata.rm.datasource.sql.struct.Field",
"name": "aName",
"keyType": "NULL",
"type": 12,
"value": "tanzj"
}]]
}]]
}
}]]
}
查看service-tm和service-at的xid:
执行下去,tm抛出异常后,undo_log日志消失,插入的数据为空,则说明分布式事务执行完成。
至此完成SEATA-AT模式的搭建