nacos+feign+seata 集成分布式事务,以及踩坑过程

942 阅读4分钟

 这是一段完整的配置spring cloud +feign+nacos 项目下,增加 seata 的 AT 模式作为的分布式事务解决方案,同时希望可以通过这个流程下来,

可以看到我在过程中参考的信息来源,去总结如何引入框架,同时存在疑问信息,从哪些现有信息里找到解决方案的方式有些帮助。

版本信息:

springBoot:2.2.5.RELEASE

nacos:2.0 以上

seata:1.5.2

1、下载和初始化 seata 数据库

(1)下载

访问:

Releases · seata/seata · GitHub

下载找到Assets下的 seata-server 压缩包。

(2)初始化所需的 Mysql 数据库

此处示例用的是 mysql,还可以支持 oracle 和 postgresql

创建一个DB,此处命名为 db_seata,进入库中

执行解压缩文件中 seat/script/server/db/mysql.sql 的 SQL 语句

(3)为业务服务对应数据库添加 undo_log

这里有熟悉的,是不是很像 mysql 的undo_log 文件机制,seata 的实现其实也是模拟 mysql 的 undo_log 的实现方案去做到回滚的。

但是目前 seata 回滚的 sql 还是存在一些限制,详情可查阅:SQL限制

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、修改启动的配置文件

seata/config/application.yml

参考的配置方式,来源于同级目录下 application.example.yml

里面针对各个类型的配置示例,都提供出来,包括以下的三点,

既然用到 nacos 和 mysql,那么配置中心、注册中心、store 干脆都使用上。

(1)修改配置中心

seata:

  config:

    type: nacos

    nacos:

      server-addr: 127.0.0.1:8848

      group : "SEATA_GROUP"

      namespace: ""

      dataId: "seataServer.properties"

      username: "nacos"

      password: "nacos"

参考:nacos

(2)修改注册中心

registry:

  type: nacos

  nacos:

    ##注册的服务名称

    application: seata-server

    ##nacos的地址端口

    server-addr: 127.0.0.1:8848

    ##此处注意自己的业务服务的 group 和 namespace 配置,匹配得到才能访问,这里仅仅举例

    group : "SEATA_GROUP"

    namespace: ""

    #对应的账号密码

    username: "nacos"

    password: "nacos"

(3)修改 DB 连接到 mysql

store:   

 # support: file 、 db 、 redis

    mode: db

    db:

      datasource: druid

      db-type: mysql

      driver-class-name: com.mysql.jdbc.Driver

      url: jdbc:mysql://127.0.0.1:3306/seata?rewriteBatchedStatements=true

      user: mysql

      password: mysql

      min-conn: 5

      max-conn: 100

      global-table: global_table

      branch-table: branch_table

      lock-table: lock_table

      distributed-lock-table: distributed_lock

      query-limit: 100

      max-wait: 5000

大致图样:

2、上传配置文件到 Nacos 的配置中心。

参考官方文档:nacos

进入目录 seata/script/config-center

(1)修改配置文件

修改 config.txt 里面的mode 为 db 数据库,并调整链接

(2)上传到nacos

方式一:

sh ${SEATAPATH}/script/config-center/nacos/nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP -t 5a3c7d6c-f497-4d68-a71a-2e5e3340b3ca -u username -w password

//localhost -p 8848 --> nacos 的地址

//-g SEATA_GROUP -t  5a3c7d6c-f497-4d68-a71a-2e5e3340b3ca ---> 对应前面配置 nacos 的注册中心的 group 和 namespace,namespace 需要用业务服务的,若用的 public ,-t 这部分可删除

// -u username -w password -->username:nacos 账号,password:nacos 密码

sh ./script/config-center/nacos/nacos-config.sh -h 127.0.0.1 -p 8848 -g SEATA_GROUP -t 5a3c7d6c-f497-4d68-a71a-2e5e3340b3ca -u nacos -w nacos

(seata 和业务服务均需要在这个初始化完成后再启动)

方式二:

在对应nacos 创建一个 seataServer.properties 将配置信息,黏贴进去。

​编辑

3、业务服务的配置

(1)添加依赖

prarent pom 添加依赖

<dependency>

    <groupId>io.seata</groupId>

    <artifactId>seata-spring-boot-starter</artifactId>

    <version>1.5.2</version>

    <exclusions>

        <exclusion>

            <groupId>io.seata</groupId>

            <artifactId>seata-all</artifactId>

        </exclusion>

    </exclusions>

</dependency>

<dependency>

    <groupId>io.seata</groupId>

    <artifactId>seata-all</artifactId>

    <version>1.5.2</version>

</dependency>

依赖关联回滚的服务添加依赖

<dependency>

    <groupId>io.seata</groupId>

    <artifactId>seata-spring-boot-starter</artifactId>

    <version>1.5.2</version>

</dependency>

(2)业务服务 application 文件修改

##这里和前方配置 seata 的配置文件是一致的

seata:

  config:

    # support: nacos, consul, apollo, zk, etcd3

    type: nacos

    nacos:

      server-addr: 127.0.0.1:8848

      group: "SEATA_GROUP"

      dataId: "seataServer.properties"

      username: "nacos"

      password: "nacos"

  registry:

    type: nacos

    nacos:

      ##注册的服务名称

      application: seata-server

      ##nacos的地址端口

      server-addr: 127.0.0.1:8848

      ##此处注意自己的业务服务的 group 和 namespace 配置,匹配得到才能访问,这里仅仅举例

      group : "SEATA_GROUP"

      ##对应的账号密码

      username: "nacos"

      password: "nacos"

若是配置为 dataId: "seataServer.properties",那么配置文件走的是这里,seata 的 application.yal 关于 mode 、store、决定生效取决于配置文件

(3)feign 的全局 xid 传输

通过全局配置,增加服务通信信息中,可以携带 seata 的 xid 进行传输

@Configuration

public class FeignSupportConfig {

  /**

   * feign请求拦截器

   *

   * @return

   */

  @Bean

  public RequestInterceptor requestInterceptor(){

    return new FeignBasicAuthRequestInterceptor();

  }

}


@Slf4j

public class FeignBasicAuthRequestInterceptor implements RequestInterceptor {

  @Override

  public void apply(RequestTemplate requestTemplate) {

    requestTemplate.header(AsapConstants.DATA_SOURCE_CODE, AsapConstants.DATA_SOURCE);

    requestTemplate.header(AsapConstants.REQUEST_ID, TraceIdUtils.getTraceId());

    RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

    if (ObjectUtils.isEmpty(requestAttributes)){

      return ;

    }

    ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;

    HttpServletRequest request = attributes.getRequest();

    Enumeration<String> headerNames = request.getHeaderNames();

    //服务通信携带初始请求的 header

    if (headerNames != null) {

      while (headerNames.hasMoreElements()) {

        String name = headerNames.nextElement();

        String values = request.getHeader(name);

        // 跳过 content-length

        if ("content-length".equals(name)){

          continue;

        }

        requestTemplate.header(name, values);

      }

      //传递 seata 回滚 xid

      String xid = RootContext.getXID();

      requestTemplate.header(RootContext.KEY_XID, xid);


    } else {

      log.info("feign interceptor error header:{}", requestTemplate);

    }

  }

写 feign  通信接口时,添加配置文件依赖。

4、启动服务

seata-server.bat -p 8091 -h 127.0.0.1

启动成功后观察,启动的 seata web 服务时 7091 端口。 访问127.0.0.1:7091

业务服务启动后,结合  @GlobalTransactional 注解验证测试有无效果。

5、常见问题解决方案

Seata常见问题

6、遇到的问题

(1)服务部署过程中,seataServer.properties 的生效优先于seata的 config 文件application.yml ,过程中修改数据库的信息,仅仅修改这里不生效。

(2)由于项目中存在全局异常的处理,所以导致明明抛出异常但是事务还是不会回滚。

看 seata 的代码,response 的状态码异常才会 触发回滚。

7、常见​编辑 seata事务回滚失败的情况

(1)xid 在服务中传输存在问题,打日志检查,处理方式见第 3(2)。

(2)feign 的 db 没脸上,事务范围内,执行过程中在执行 sql 后打上 断点,然后登录seata 的管理台,检查是否存在事务和全局锁信息,有刚刚执行 Sql 的锁信息。

(3)全局异常导致失效,可以通过全局异常捕获过滤这些接口,或是通过手动执行事务的方式触发回滚。

try {

    TransactionManager manager = TransactionManagerHolder.get();

    manager.rollback(RootContext.getXID());

}catch (Exception e){

   log.error("手动回滚全局异常失败:{}",e);

}