文章目录

  • 背景
  • 一、Seata是什么?
  • 二、使用步骤
  • 1.依赖版本
  • 2.下载Seata
  • 2.安装部署Seata
  • 3.使用
  • 1.创建undo_log表
  • 2.添加依赖
  • 3.修改配置
  • 4.创建拦截器传递XID
  • 5.使用@GlobalTransactional注解启动全局事务



背景

多个微服务之间使用FeignClient相互调用,无法保证在同一个事务中执行,当出现异常时,无法回滚。例如在一个方法里先后调用了serviceA.testA()方法和serviceB.testB()方法,此时调用testA成功了,但是调用testB时,因为serviceB服务正在重启发布,调用出现异常,这时无法使testA回滚。


一、Seata是什么?

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

二、使用步骤

1.依赖版本

  • SpringBoot 2.3.0.RELEASE
  • SpringCloud Hoxton.SR11
  • spring-cloud-starter-feign 1.4.7.RELEASE
  • seata-spring-boot-starter 1.4.2

2.下载Seata

本文使用的是1.4.2版本

2.安装部署Seata

下面是本文采用的部署方式。

  1. 将下载好的安装包seata-server-1.4.2.tar.gz解压。
  2. 进入conf文件夹分别修改registry.conf和file.conf。
  3. 修改registry.conf文件中的type为eureka,eureka对应的serviceUrl和Seata注册在eureka上的名称,这里直接取名seata。
  4. 修改file.conf文件中存储方式,这里使用的是默认的file,所以不用修改。如果使用db存储则需要修改数据源、数据库地址、用户名、密码以及在数据库中生成global_table、branch_table、lock_table三个表。
  5. 进入bin目录执行seata-server.sh启动Seata。

3.使用

1.创建undo_log表

每个需要用到seata的微服务,需要在本地创建一个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;

2.添加依赖

<dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>1.4.2</version>
        </dependency>

3.修改配置

## seata
## 指定微服务所在的分布式事务组
seata.tx-service-group=my_test_tx_group
## seata注册在eureka上的名称,这里seata.service.vgroup-mapping.xxx的xxx为seata.tx-service-group的值
seata.service.vgroup-mapping.my_test_tx_group=seata
## 注册中心类型
seata.registry.type=eureka
seata.registry.eureka.service-url=http://xxxx:7108/eureka/
seata.registry.eureka.application=seata
## 设置undo_log的表名,因为我不是默认的名称所以需要另外指定
seata.client.undo.log-table=tb_undo_log

4.创建拦截器传递XID

Seata 全局事务的传播机制就是指事务上下文的传播,根本上,就是 XID 的应用运行时的传播方式。
默认的,RootContext 的实现是基于 ThreadLocal 的,即 XID 绑定在当前线程上下文中。所以服务内部的 XID 传播通常是天然的通过同一个线程的调用链路串连起来的。默认不做任何处理,事务的上下文就是传播下去的。所以本地方法相互调用都是在同一个XID下的,也就能保证全局事务。但是如果微服务之间调用,要想在同一个全局事务下,则需要将XID传递到其他的为服务中。这样就能保证多个微服务的调用在同一个全局事务下,否则每个微服务都创建一个自己的XID,则都是在自己的全局事务中,就和传统的本地事务一样了。

package com.test.interceptor;

import feign.RequestInterceptor;
import feign.RequestTemplate;
import io.seata.core.context.RootContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Configuration;

@Slf4j
@Configuration
public class FeignInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate requestTemplate) {
        String xid = RootContext.getXID();
        if (StringUtils.isNotBlank(xid)) {
            log.info("feign xid:"+xid);
            requestTemplate.header(RootContext.KEY_XID, xid);
        }
    }
}

5.使用@GlobalTransactional注解启动全局事务

@Override
    @GlobalTransactional
    public void test() {
    	aService.testA();
        int i = 1/0;
        bService.testB();
    }

当testA执行成功后,int i = 1/0;抛出异常,testA回滚。