关于java:Seata整合SpringBoot和Mybatis

57次阅读

共计 6825 个字符,预计需要花费 18 分钟才能阅读完成。

一、背景

在上一节中,咱们学习了 Seata 的集群部署,在这篇文章中,咱们应用 SpringBoot 整合 Seata 实现分布式事务性能,此处应用的是 SeataAT模式。

二、实现性能

咱们存在 2 个服务 账户服务 account-service 订单服务 order-service,在订单服务中调用 账户服务。

订单服务中调用账户服务是通过 RestTemplate来实现的。

测试场景:

1、账户服务失常,订单服务失常,后果:账户服务失常扣款,产生订单。

2、账户服务失常,订单服务失常,在整个分布式事务中产生了异样,后果: 账户服务没有扣款,没有产生订单。

三、每个服务应用到的技术

1、账户服务

SpringBoot、Seata、Mybatis、nacos、druid

2、订单服务

SpringBoot、Seata、Mybatis、nacos、Hikari

其中 SpringBoot 整合 Seata 是通过 seata-spring-boot-starter 这个来实现的,不应用 seata-all来实现。

四、服务实现

1、账户服务实现

账户服务,提供一个简略的扣除账户余额的性能,比较简单。

留神项:

1、开启主动数据源代理。

2、引入druid,不须要主动配置数据源。

3、留神事务分组

1、引入 jar 包

此处只引入几个 外围的 包,其余的包没有列在下方,比方 mybatis 等,留神和 seata 整合应用的是seata-spring-boot-starter

<dependency>
  <groupId>io.seata</groupId>
  <artifactId>seata-spring-boot-starter</artifactId>
  <version>1.4.2</version>
</dependency>
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid-spring-boot-starter</artifactId>
  <version>1.2.4</version>
</dependency>
<dependency>
  <groupId>com.alibaba.nacos</groupId>
  <artifactId>nacos-client</artifactId>
  <version>1.3.2</version>
</dependency>

2、我的项目配置

3、建表语句

create database seata_account;
use seata_account;
create table account(
    id int unsigned auto_increment primary key comment '主键',
    name varchar(20) comment '用户名',
    balance bigint comment '账户余额, 单位分'
) engine=InnoDB comment '账户表';
insert into account(id,name,balance) values (1,'张三',100000);
CREATE TABLE IF NOT EXISTS `undo_log`
(
    `branch_id`     BIGINT       NOT NULL COMMENT 'branch transaction id',
    `xid`           VARCHAR(128) NOT NULL COMMENT 'global transaction id',
    `context`       VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
    `rollback_info` LONGBLOB     NOT NULL COMMENT 'rollback info',
    `log_status`    INT(11)      NOT NULL COMMENT '0:normal status,1:defense status',
    `log_created`   DATETIME(6)  NOT NULL COMMENT 'create datetime',
    `log_modified`  DATETIME(6)  NOT NULL COMMENT 'modify datetime',
    UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB COMMENT ='AT transaction mode undo table';

每个业务库必须存在一张 undo_log

2、订单服务实现

提供一个接口,实现产生订单,扣除账户余额。

注意事项:

1、订单服务 敞开默认的数据源代理,本人配置数据源代理。

2、应用 Hikari数据源来实现,因为应用的是 AT模式,所以须要应用 DataSourceProxy 来代理数据源。

3、订单服务调用账户服务是采纳的 RestTemplate,因而须要手动配置 RestTemplate 的拦截器,实现 xid 的传输。

4、在 seata1.4.2 中存在一个 bug,如果业务表中数据类型是 datetime 类型,可能 undolog 无奈序列化胜利,能够采纳 timestamp 或别的形式来解决。

5、业务库中须要存在 undo_log 表。

1、引入 jar 包

此处不引入 druid,留神和 seata 整合应用的是seata-spring-boot-starter

<dependency>
  <groupId>io.seata</groupId>
  <artifactId>seata-spring-boot-starter</artifactId>
  <version>1.4.2</version>
</dependency>
<dependency>
  <groupId>com.alibaba.nacos</groupId>
  <artifactId>nacos-client</artifactId>
  <version>1.3.2</version>
</dependency>

2、我的项目配置

3、配置数据源代理

package com.huan.seata.config;

import com.zaxxer.hikari.HikariDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

/**
 * @author huan.fu 2021/9/24 - 上午 10:34
 */
@Configuration
public class DataSourceConfig {
    
    @Autowired
    private DataSourceProperties dataSourceProperties;
    
    @Bean
    public DataSource dataSourceProxy() {HikariDataSource hikariDataSource = new HikariDataSource();
        hikariDataSource.setJdbcUrl(dataSourceProperties.getUrl());
        hikariDataSource.setUsername(dataSourceProperties.getUsername());
        hikariDataSource.setPassword(dataSourceProperties.getPassword());
        hikariDataSource.setDriverClassName(dataSourceProperties.getDriverClassName());
        return new DataSourceProxy(hikariDataSource);
    }
}

AT模式种,数据源代理肯定要是 DataSourceProxy这个。

4、配置 RestTemplate 传递 xid

5、@GlobalTransactional 分布式事务

@Service
@RequiredArgsConstructor
@Slf4j
public class BusinessServiceImpl implements BusinessService {
    
    private final OrderService orderService;
    private final RestTemplate restTemplate;
    
    @Override
    @GlobalTransactional(rollbackFor = Exception.class)
    public void createAccountOrder(Integer accountId, Long amount, boolean hasException) {System.out.println("createAccountOrder:" + RootContext.getXID());
        // 1、近程扣减账户余额
        remoteDebit(accountId, amount);
        
        // 2、下订单
        orderService.createOrder(accountId, amount);
        
        if (hasException) {throw new RuntimeException("产生了异样,分布式事物须要会滚");
        }
    }
    
    private void remoteDebit(Integer accountId, Long amount) {
        String url = "http://localhost:50001/account/debit?id=" + accountId + "&amount=" + amount;
        String result = restTemplate.getForObject(url, String.class);
        log.info("近程扣减库存后果:[{}]", result);
    }
}

3、事务分组须要和配置核心对应上

此处以订单服务来演示,如何和配置核心对应上的。

每个服务的事务分组可能不一样,然而须要和配置核心对应上。比方:order-service 中的配置分组为:seata.tx-service-group=tx_order_service_group
配置核心必须存在 service.vgroupMapping.tx_order_service_group=default 配置项,default 是集群,是服务端配置文件中指定的

五、演示

1、没有产生异样

拜访:http://localhost:50002/createOrder?accountId=1&amount=10&hasException=false

失常创立订单,和扣除余额。

2、产生异样

拜访:http://localhost:50002/createOrder?accountId=1&amount=10&hasException=true

不产生订单,不扣除余额。

六、可能遇到的问题

1.Nacos 作为 Seata 配置核心时,我的项目启动报错找不到服务。如何排查,如何解决?

A: 异样:io.seata.common.exception.FrameworkException: can not register RM,err:can not connect to services-server.

  1. 查看 nacos 配置列表,seata 配置是否曾经导入胜利
  2. 查看 nacos 服务列表,serverAddr 是否曾经注册胜利
  3. 查看 client 端的 registry.conf 外面的 namespace,registry.nacos.namespace 和 config.nacos.namespace 填入 nacos 的命名空间 ID,默认 ””,server 端和 client 端对应,namespace 为 public 是 nacos 的一个保留控件,如果您须要创立本人的 namespace,最好不要和 public 重名,以一个理论业务场景有具体语义的名字来命名
  4. nacos 上服务列表,serverAddr 地址对应 ip 地址应为 seata 启动指定 ip 地址,如:sh seata-server.sh -p 8091 -h 122.51.204.197 -m file
  5. 查看 seata/conf/nacos-config.txt 事务分组 service.vgroupMapping.trade_group=default 配置与我的项目分组配置名称是否统一
  6. telnet ip 端口 查看端口是都凋谢,以及防火墙状态

2、应用 AT 模式须要的注意事项有哪些?

  1. 必须应用代理数据源,有 3 种模式能够代理数据源:
  • 依赖 seata-spring-boot-starter 时,主动代理数据源,无需额定解决。
  • 依赖 seata-all 时,应用 @EnableAutoDataSourceProxy (since 1.1.0) 注解,注解参数可抉择 jdk 代理或者 cglib 代理。
  • 依赖 seata-all 时,也能够手动应用 DatasourceProxy 来包装 DataSource。
  1. 配置 GlobalTransactionScanner,应用 seata-all 时须要手动配置,应用 seata-spring-boot-starter 时无需额定解决。
  2. 业务表中必须蕴含单列主键,若存在复合主键,请参考问题 13。
  3. 每个业务库中必须蕴含 undo_log 表,若与分库分表组件联用,分库不分表。
  4. 跨微服务链路的事务须要对相应 RPC 框架反对,目前 seata-all 中曾经反对:Apache Dubbo、Alibaba Dubbo、sofa-RPC、Motan、gRpc、httpClient,对于 Spring Cloud 的反对,请大家援用 spring-cloud-alibaba-seata。其余自研框架、异步模型、音讯生产事务模型请联合 API 自行反对。
  5. 目前 AT 模式反对的数据库有:MySQL、Oracle、PostgreSQL 和 TiDB。
  6. 应用注解开启分布式事务时,若默认服务 provider 端退出 consumer 端的事务,provider 可不标注注解。然而,provider 同样须要相应的依赖和配置,仅可省略注解。
  7. 应用注解开启分布式事务时,若要求事务回滚,必须将异样抛出到事务的发起方,被事务发起方的 @GlobalTransactional 注解感知到。provide 间接抛出异样 或 定义错误码由 consumer 判断再抛出异样。

3、AT 模式和 Spring @Transactional 注解连用时须要留神什么?

@Transactional 可与 DataSourceTransactionManager 和 JTATransactionManager 连用别离示意本地事务和 XA 分布式事务,大家罕用的是与本地事务联合。当与本地事务联合时,@Transactional 和 @GlobalTransaction 连用,@Transactional 只能位于标注在 @GlobalTransaction 的同一办法档次或者位于 @GlobalTransaction 标注办法的内层。这里分布式事务的概念要大于本地事务,若将 @Transactional 标注在外层会导致分布式事务空提交,当 @Transactional 对应的 connection 提交时会报全局事务正在提交或者全局事务的 xid 不存在。

4、数据库开启自动更新工夫戳导致脏数据无奈回滚

因为业务提交,seata 记录以后镜像后,数据库又进行了一次工夫戳的更新,导致镜像校验不通过。

解决方案 1: 敞开数据库的工夫戳自动更新。数据的工夫戳更新,如批改、创立工夫由代码层面去保护,比方 MybatisPlus 就能做主动填充。

解决方案 2: update 语句别把没更新的字段也放入更新语句。

5、Seata 应用注册核心注册的地址有什么限度?

Seata 注册核心不能注册 0.0.0.0 或 127.0.0.1 的地址,当主动注册为上述地址时能够通过启动参数 -h 或容器环境变量 SEATA_IP 来指定。当和业务服务处于不同的网络时注册地址能够指定为 NAT_IP 或公网 IP,但须要保障注册核心的健康检查探活是通顺的。

以上的几个问题,来自 seata 官网 : http://seata.io/zh-cn/docs/overview/faq.html

七、代码地址

代码地址:https://gitee.com/huan1993/spring-cloud-parent/tree/master/seata/seata-springboot-mybatis

正文完
 0