SpringCloud多数据源接入Seata和ShardingJDBC最佳实践

一、前言

该方案基于seata官方示例进行修改,整合了spring-cloud-alibaba + seata + sharding-jdbc + mybatis + 多数据源,去除了mybatis-plus、sharding-transaction-base-seata-at依赖,采用手动配置seata和sharding-jdbc数据源的方式进行整合,通过@GlobalTransactional进行分布式事务的使用,和分库分表前的seata使用上没有区别。
注意:sharding-jdbc不同版本之间配置和设计差异较大,请合理选择sharding-jdbc版本

seata:1.4.2
sharding-jdbc:4.1.1
spring-boot:2.3.5
spring-cloud:Hoxton.SR9
spring-cloud-alibaba:2.2.5.RELEASE
原示例源码:https://github.com/seata/seata-samples/tree/master/springcloud-seata-sharding-jdbc-mybatis-plus-samples
本文档源码:https://github.com/jasonkung22/seata-samples/tree/master/springcloud-seata-sharding-jdbc-mybatis-plus-samples

二、原理剖析

1、剖析Seata分布式事务实现原理

数据源自动代理

seata开启自动代理数据源之后,每次注册dataSourceBean之后,SeataDataSourceBeanPostProcessor都会自动对数据源进行代理image.png

注册全局事务

当调用微服务A的@GlobalTransactional注解的方法时,会向TC注册全局事务,并得到全局事务ID(xid)
image.png

微服务间事务传递

当微服务A调用微服务B时,SeataRestTemplateInterceptor会将xid放入请求Header中传入微服务B,微服务B通过SeataHandlerInterceptor将请求Header中加载到当前线程中image.pngimage.png

是否添加 @Transactional 的区别
  1. 如果有@Transactional注解,会在本地事务提交时,向TC注册分支事务
  2. 如果没有@Transactional注解,会每次执行完SQL后,都向TC注册分支事务(如果一个方法中有3个数据操作,就会向TC注册3个分支事务)
提交或回滚全局事务
  1. 当微服务A的@GlobalTransactional方法正常执行完,TM会通知TC全局事务成功,TC再通知所有分支事务进行二阶段提交
  2. 当微服务A的@GlobalTransactional方法抛出异常时,TM会通知TC全局事务失败,TC再通知所有分支事务进行二阶段回滚

2、剖析ShardingJDBC实现原理

分片规则自动配置

在yaml中spring.shardingsphere.sharding配置分片规则后,会加载到SpringBootShardingRuleConfigurationProperties中image.png

数据源自动配置

在yaml中spring.shardingsphere.datasource配置数据源后,org.apache.shardingsphere.shardingjdbc.spring.boot.SpringBootConfiguration会自动创建ShardingDataSource,并注入分片规则等配置image.png

引入seata分布式事务(基于@ShardingTransactionType )

引入sharding-transaction-base-seata-at依赖后,SeataATShardingTransactionManager会自动对ShardingDataSource中的实际数据源创建seata数据源代理image.png

执行流程

当service调用dao时,根据SqlSessionFactory的MapperLocations配置识别到logicDateSource,然后根据分片规则找到实际数据源,完成以下流程图的SQL执行

三、设计思路

1、基于@ShardingTransactionType (5.4.0版本已弃用)

该方式在ShardingJDBC5.4.0版本前,为官方推荐,网上大多数也是基于此方案进行配置。但经过实测存在其它一些问题,不满足我们的应用场景

按照官方文档对接即可,不需要进行设计,对接中发现的问题如下:

  1. 不支持事务传播,如果微服务A和微服务B都引入了shardingJDBC,当微服务A调用微服务B时,微服务B的方法上面没有加@ShardingTransactionType,微服务B的不会加入到全局事务中
  2. 自动配置对多数据源支持不友好,需要自行创建ShardingDataSource,并注入到SqlSessionFactoryBean中
  3. @ShardingTransactionType和@GlobalTransactional不能混用,可能出现未知问题,比如空指针https://github.com/apache/shardingsphere/issues/22356

2、基于@GlobalTransactional(推荐)

由于seata和shardingJDBC本质上都是对数据源进行代理,我们只需要将shardingJDBC逻辑数据源下的每一个实际数据源当成多数据源,分片成功之后,就可以确定某一个数据源,再像分库分表前一样使用seata就可以了。总体执行流程:
分库分表前:mybatis > seataDataSource > druidDataSource > connection
分库分表后:mybatis > shardingDataSource > seataDataSource > druidDataSource > connection

  1. 为了配置多数据源,需要禁用spring默认的数据源自动配置
  2. 为了达到上诉的执行流程,需要自定义数据源和代理数据源,因此也需要禁用sharding、seata数据源自动配置
  3. 由于已经禁用了spring、seata、sharding的数据源自动配置,所以需要程序自定义数据源并进行管理。为了进行区分,将数据源定义在spring.database下

四、具体实现

1、基于@ShardingTransactionType

引入分布式事务seata依赖

微服务项目需要同时spring-cloud-starter-alibaba-seata依赖,不然微服务间调用时,xid不会进行传递

<dependency>
  <groupId>org.apache.shardingsphere</groupId>
  <artifactId>sharding-transaction-base-seata-at</artifactId>
  <version>${sharding-sphere.version}</version>
</dependency>
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
添加seata.config配置

需要将该配置放在classpath下

client {
    application.id = order-server
    transaction.service.group = seata-group
}
关闭seata的数据源自动代理
seata:
  enable-auto-data-source-proxy: false
使用@ShardingTransactionType

在需要分布式事务的方法上,加上@ShardingTransactionType(TransactionType.BASE)就可以正常使用了(但是ShardingTransactionType缺少事务的传播机制,需要注意加@ShardingTransactionType注解)

2、基于@GlobalTransactional

关闭seata自动代理数据源
seata:
  enable-auto-data-source-proxy: false
排除spring、shardingJDBC数据源自动配置
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, SpringBootConfiguration.class})
自定义shardingJDBC自动配置
@Configuration
@ComponentScan("org.apache.shardingsphere.spring.boot.converter")
@EnableConfigurationProperties({
        SpringBootShardingRuleConfigurationProperties.class,
        SpringBootMasterSlaveRuleConfigurationProperties.class, SpringBootEncryptRuleConfigurationProperties.class,
        SpringBootPropertiesConfigurationProperties.class, SpringBootShadowRuleConfigurationProperties.class})
@ConditionalOnProperty(prefix = "spring.shardingsphere", name = "enabled", havingValue = "true", matchIfMissing = true)
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
@RequiredArgsConstructor
public class CustomShardingSphereAutoConfiguration {


    /**
     * Create transaction type scanner.
     *
     * @return transaction type scanner
     */
    @Bean
    public ShardingTransactionTypeScanner transactionTypeScanner() {
        return new ShardingTransactionTypeScanner();
    }
}
配置多数据源

在yaml中spring.database下配置数据源

spring:
  database:
    ds0:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://192.168.0.7:3308/seata_order_0?serverTimezone=UTC&characterEncoding=utf8
      username: root
      password: 123456
      initial-size: 10
      max-active: 15
      min-idle: 10
      max-wait: 60000
    ds1:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://192.168.0.7:3308/seata_order_1?serverTimezone=UTC&characterEncoding=utf8
      username: root
      password: 123456
      initial-size: 10
      max-active: 15
      min-idle: 10
      max-wait: 60000
配置分片规则

在yaml中spring.shardingsphere下配置分片规则等配置(该方案适用于一个微服务只有一个shardingDataSource,如果需要有多个,也需要自定义分片规则参数)

spring:
  shardingsphere:
    props:
      sql:
        show: true
    sharding:
      default-data-source-name: ds0
      tables:
        order_info:
          actual-data-nodes: ds$->{0..1}.order_info_$->{0..2}
          database-strategy:
            inline:
              algorithm-expression: ds$->{id % 2}
              sharding-column: id
          table-strategy:
            inline:
              algorithm-expression: order_info_$->{id % 3}
              sharding-column: id
注册并代理数据源

自定义druidDataSource、seataDataSource、shardingDataSource并注入到SqlSessionFactory和TransactionManager里面

@Configuration
@MapperScan(basePackages = "io.seata.order.mapper", sqlSessionFactoryRef = "dsSqlSessionFactory")
@RequiredArgsConstructor
public class DataSourceConfig {

    private final SpringBootShardingRuleConfigurationProperties shardingRule;

    private final SpringBootPropertiesConfigurationProperties props;

    @Bean(name = "ds0DataSource")
    @ConfigurationProperties(prefix = "spring.database.ds0")
    public DataSource ds0DataSource() {
        DruidDataSource druidDataSource = DruidDataSourceBuilder.create().build();
        druidDataSource.setTestWhileIdle(true);
        druidDataSource.setValidationQuery("SELECT 1");
        return druidDataSource;
    }

    @Bean(name = "ds1DataSource")
    @ConfigurationProperties(prefix = "spring.database.ds1")
    public DataSource ds1DataSource() {
        DruidDataSource druidDataSource = DruidDataSourceBuilder.create().build();
        druidDataSource.setTestWhileIdle(true);
        druidDataSource.setValidationQuery("SELECT 1");
        return druidDataSource;
    }


    @Bean(name = "ds0SeataDatasource")
    public DataSourceProxy ds0SeataDatasource(@Qualifier("ds0DataSource") DataSource ds0DataSource) {
        return new DataSourceProxy(ds0DataSource);
    }


    @Bean(name = "ds1SeataDatasource")
    public DataSourceProxy ds1SeataDatasource(@Qualifier("ds1DataSource") DataSource ds1DataSource) {
        return new DataSourceProxy(ds1DataSource);
    }


    @Bean(name = "dsShardingDataSource")
    @Conditional(ShardingRuleCondition.class)
    public DataSource dsShardingDataSource(@Qualifier("ds0SeataDatasource") DataSource ds0SeataDatasource,
            @Qualifier("ds1SeataDatasource") DataSource ds1SeataDatasource) throws SQLException {
        return ShardingDataSourceFactory.createDataSource(getDataSourceMap(ds0SeataDatasource, ds1SeataDatasource),
                new ShardingRuleConfigurationYamlSwapper().swap(shardingRule), props.getProps());
    }

    @Bean(name = "dsSqlSessionFactory")
    public SqlSessionFactory logSqlSessionFactory(
            @Qualifier("dsShardingDataSource") DataSource dsShardingDataSource)
            throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dsShardingDataSource);
        bean.setMapperLocations(
                new PathMatchingResourcePatternResolver().getResources("classpath*:mapping/ds/*.xml"));
        bean.setVfs(SpringBootVFS.class);
        return bean.getObject();
    }

    @Bean(name = "dsSqlSessionTemplate")
    public SqlSessionTemplate logSqlSessionTemplate(
            @Qualifier("dsSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

    @Bean(name = "dsTransactionManager")
    public DataSourceTransactionManager controlLogTransactionManager(
            @Qualifier("dsShardingDataSource") DataSource dsShardingDataSource) {
        return new DataSourceTransactionManager(dsShardingDataSource);
    }

    public Map<String, DataSource> getDataSourceMap(DataSource controlLogDataSource, DataSource controlLog2DataSource) {
        Map<String,DataSource> dataSourceMap = new HashMap<>();
        dataSourceMap.put("ds0", controlLogDataSource);
        dataSourceMap.put("ds1", controlLog2DataSource);
        return dataSourceMap;
    }
}
多数据源配置方式和上面一样,源码中有所体现

五、参考资料

Seata官方博客-Seata数据源代理解析

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,关于这个问题,我可以提供一些参考资料。首先,Spring Cloud是一个基于Spring框架的微服务架构开发工具包,提供丰富的开箱即用的组件和框架,可以较为方便地实现微服务的开发、部署和管理。而Nacos是阿里巴巴开源的一个面向服务中心的动态服务发现、配置管理和服务治理平台,可以提供服务注册与发现、配置管理、流量管理、域名解析等功能。Seata是阿里巴巴开源的一个分布式事务解决方案,可以提供数据源代理、事务协调、幂等性设计等功能,用于解决微服务架构下的分布式事务问题。 在Spring Cloud中集成Nacos和Seata,需要进行如下步骤: 1. 引入相应的依赖,例如在pom.xml文件中添加如下依赖: ```xml <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> <version>2.2.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>2.2.3.RELEASE</version> </dependency> <dependency> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> <version>1.4.2</version> </dependency> ``` 其中,spring-cloud-starter-alibaba-nacos-config和spring-cloud-starter-alibaba-nacos-discovery是Nacos的客户端依赖,seata-spring-boot-starter是Seata的客户端依赖。 2. 配置Nacos的地址和Seata的事务配置,例如在application.yml文件中添加如下配置: ```yaml spring: cloud: nacos: discovery: server-addr: localhost:8848 config: server-addr: localhost:8848 file-extension: yml seata: tx-service-group: my_test_tx_group application-id: ${spring.application.name} enabled: true mybatis: configuration: # ... seata: enabled: true application-id: ${spring.application.name} tx-service-group: my_test_tx_group config: transport: enabled: true type: TCP server: localhost:8091 heartbeat: true heartbeat.interval.ms: 5000 disableHeartbeatChecking: true client-selector: rnd client-list: 127.0.0.1:8091 shutdown.timeout.ms: 5000 registry: type: Nacos nacos: application: ${spring.application.name} serverAddr: localhost:8848 namespace: public ``` 其中,Nacos的地址和Seata的事务配置可以根据具体情况自行修改。 3. 配置完毕后,就可以使用Nacos作为Spring Cloud的注册中心和配置中心,使用Seata来解决分布式事务问题了。 以上是关于如何在Spring Cloud中集成Nacos和Seata的简要介绍,希望能够帮助到你。如果你还有其他问题,可以继续向我提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值