Spring Cloud集成seata分布式事务—AT模式

本文将介绍基于springcloud使用阿里巴巴分布式事务框架seata的AT模式(1.0.0版本),AT模式基本上能满足我们使用分布式事务80%的需求(非关系型数据库与中间件的操作、跨公司服务的调用跨语言的应用调用需要结合SEATA-TCC模式)。关于seata的介绍可以点击这里进入seata官网。

seata-at模式效果

1、引入seata框架,配置好seata基本配置,建立undo_log表

2、消费者引入全局事务注解@GlobalTransactional

3、生产者引入全局事务注解@GlobalTransactional

完成!!

一、SEATA分布式事务基本术语

  • TC - 事务协调者:维护全局和分支事务的状态,驱动全局事务提交或回滚。
  • TM - 事务管理器:定义全局事务的范围:开始全局事务、提交或回滚全局事务。
  • RM - 资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
  • AT模式-Automatic (Branch) Transaction Mode 自动化分支事务
  • TCC Try-(Confirm/Cancel) seata的手工分支事务

二、前提准备

三、SEATA的AT模式前提

AT模式是指Automatic (Branch) Transaction Mode 自动化分支事务,使用AT模式的前提是

  • 基于支持本地 ACID 事务的关系型数据库。
  • Java 应用,通过 JDBC 访问数据库。

四、TM与RM的搭建

基于以上两点,我们搭建两个微服务,分别是service-tm(事务发起者) 与service-at(资源管理器/AT模式分布式事务)。

4.1 搭建seata服务端

4.1.1 搭建seata服务端(作为TC存在)

4.1.2 mysql运行script.sql

4.2 创建maven父工程seata-demo (源码点此)

扫描二维码关注公众号,回复: 11884498 查看本文章

建议下载源码配套查看,父工程除了引入SpringCloud相关依赖外

还需要引入spring-cloud-alibaba-seata包,此包包含了seata-all与springboot-seata-starter

<dependency>
   <groupId>com.alibaba.cloud</groupId>
   <artifactId>spring-cloud-alibaba-seata</artifactId>
   <version>2.2.0.RELEASE</version>
</dependency>

openFeign

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

druid(用于数据源代理) 

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.21</version>
</dependency>

4.3 创建子工程seata-at(作为RM存在,源码点此

4.3.1 Service部分 (此处为了方便理解,把controller合并,实际代码则不然)

此处@GlobalTransactional注解到方法中开启全局事务

  • @Transactional注解与@GlobalTransactional注解合用目的是:由于一次本地事务提交是一个分支事务,一个分支事务会跟tc进行交互,@Transactional能够将多个sql合并成一个本地事务,减少tc交互(请求)提升性能。
  • XID代表本次全局事务的事务id,即A->B->C通过Feign的请求链路下,XID是一致的,因此方便seata对该分支事务进行处理。而此处将其打印验证结果。
@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<String, String> 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<String, String> 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<String, String> params);
}

至此,AT模式项目代码比较重要的部分已经完成。

五、SEATA分布式事务验证

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<String, String> 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模式的搭建

猜你喜欢

转载自blog.csdn.net/bbcckkl/article/details/104362049