使用shardingsphere实现了读写分离或者某些数据库字段加密的需求,在分布式环境下处理事务问题尤为关键,shardingsphere是支持分布式事务的,各有优劣。

  • ShardingSphere XA‌:适合短事务且对性能要求较低的场景(如非云化环境)。
  • Seata‌:在分布式系统中表现更灵活,支持高并发但需容忍短暂数据不一致。

本文使用一个例子介绍使用seata方式。开始之前你需要做一些准备工作

  • 搭建nacos、mysql环境
  • 准备3个springboot微服务,用于模仿用户下单行为
  • 搭建seata‌环境配置seata并注册在nacos上,使用seata‌的db方式,建立seata‌所必须的表结构

源码、sql地址

https://github.com/Fjh7863/sharding-jdbc-seata-demo/tree/main

seata‌配置文件

server:
  port: 7091

spring:
  application:
    name: seata-server

logging:
  config: classpath:logback-spring.xml
  file:
    path: ${user.home}/logs/seata

console:
  user:
    username: seata
    password: seata
    
seata:
  config:
    # support: nacos, consul, apollo, zk, etcd3
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP
      username: nacos
      password: nacos
  registry:
    # support: nacos, eureka, redis, zk, consul, etcd3, sofa
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP
      cluster: default
      username: nacos
      password: nacos
      context-path:
#  server:
#    service-port: 8091 #If not configured, the default is '${server.port} + 1000'
  security:
    secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
    tokenValidityInMilliseconds: 1800000
    ignore:
      urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.jpeg,/**/*.ico,/api/v1/auth/login

服务pom文件

依赖的版本并不是最新,如果需要更高级的版本,需要自行研究springboot、springcloud、seata兼容性问题

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>order-service</artifactId>
    <version>1.0</version>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.4.9</spring-boot.version>
        <spring-cloud-alibaba.version>2021.1</spring-cloud-alibaba.version>
        <spring-cloud.version>2020.0.6</spring-cloud.version>
        <sharding-sphere.version>4.1.1</sharding-sphere.version>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.9</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <dependencies>

        <!--nacos-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <!--seata-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        </dependency>


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

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions><!-- 去掉springboot默认配置 -->
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency> <!-- 引入log4j2依赖 -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>

        <!-- mysql驱动,适用于 8.x版本-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.26</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!--shardingsphere-->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>${sharding-sphere.version}</version>
        </dependency>

        <!--shardingsphere 配合seata-->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-transaction-base-seata-at</artifactId>
            <version>${sharding-sphere.version}</version>
        </dependency>
    </dependencies>


    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>

    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <configuration>
                    <mainClass>com.order.OrderServiceApplication</mainClass>
                    <skip>true</skip>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>

        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
            <!-- 防止加载不到 application.properties -->
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.yml</include>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                    <include>**/*.conf</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>

    </build>

</project>

pom文件解释

  • sharding-transaction-base-seata-at 是shardingsphere 配合seata的核心依赖,SeataATShardingTransactionManager.java类,实现了TMClient、RMClient的注册以及数据源的代理,实现了事务的开启、提交、回滚控制。
  • 所有服务的pom依赖均相同

服务配置文件

注意使用时一定要关闭seata的数据源自动代理问题,否则会导致seata使用出现未知问题。enable-auto-data-source-proxy:false

server:
  port: 30002

# MyBatis
mybatis:
  #   配置类型别名
#  typeAliasesPackage: com.example.springbootdemo.mysql
  # 配置mapper的扫描,找到所有的mapper.xml映射文件
  mapperLocations: classpath*:*/mapper/*.xml
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  # 加载全局的配置文件
#  configLocation: classpath:mybatis/mybatis-config.xml
#  executor-type: batch

spring:
  application:
    name: storage-service
  cloud:
    nacos:
      discovery:
        # 服务分组
        group: service_group
        server-addr: http://localhost:8848

  shardingsphere:
    datasource:
      names: ds0
      ds0:
          type: com.alibaba.druid.pool.DruidDataSource
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://127.0.0.1:3306/test?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false
          username: root
          password: root
  main:
    allow-bean-definition-overriding: true

# Seata 配置
seata:
  # 是否启用数据源bean的自动代理
  enable-auto-data-source-proxy: false
  tx-service-group: default_tx_group  #分布式事务分组 配置项一定要等于 seata的service.vgroupMapping 配置名
  config:
    type: nacos
    nacos:
      server-addr: http://localhost:8848
      group: SEATA_GROUP
      username: nacos
      password: nacos
  registry:
    type: nacos
    nacos:
      # Nacos 服务地址
      server-addr: http://localhost:8848
      group: SEATA_GROUP
      application: seata-server # 必须和服务器配置一样
      username: nacos
      password: nacos
      cluster: default

  service:
    vgroup-mapping:
      default_tx_group: default #这里的default_tx_group应与上面的tx-service-group相同,default为Seata server中的集群名称
    disable-global-transaction: false
  client:
    rm:
      # 着每个事务分支在成功执行完毕后会主动向 Seata Server 报告其成功状态
      report-success-enable: true
      # 重试次数
      report-retry-count: 5

启用shardingsphere的seata模式

  • 在每个服务resources目录下新建seata.conf文件,注意需要关注seata.conf是否被打包到jar中
client {
    application.id = storage-service  #微服务名称
    transaction.service.group = default_tx_group  #事务组名称
}

测试流程

  • 启动nacos
  • 启动seata并注册到nacos
  • 启动微服务并注册到nacos
  • 模拟用户下单,触发扣减库存、扣减金额、生成订单

微服务链接seata日志

  • RM注册成功

在这里插入图片描述

  • TM注册成功
    在这里插入图片描述

执行结果

  • 事务回滚
    全局事务发起方
    在这里插入图片描述
    全局事务加入方
    在这里插入图片描述
    在这里插入图片描述

  • 事务提交
    全局事务发起方
    在这里插入图片描述
    全局事务加入方
    在这里插入图片描述
    在这里插入图片描述
    **

注意事项

  • 全局事务发起方service方法
    @Transactional(rollbackFor = Exception.class)
    @ShardingTransactionType(TransactionType.BASE)
    @Override
    public Boolean order(Order order) {

        // 库存扣减
        Boolean storage = storageServiceFeign.decrStorage(order.getProductId(), order.getCount());

        // 账户扣减
        Boolean account = accountServiceFeign.account(order.getUserId(), order.getMoney());

        // 生成订单
        Long order1 = orderMapper.createOrder(order);

//        int i = 1 / 0;


        return Boolean.TRUE;
    }
  • 全局事务加入方service方法
    @ShardingTransactionType(TransactionType.BASE)
    @Override
    public Boolean account(Long userId, BigDecimal money) {
        String xid = RootContext.getXID();
        System.out.println("account-service Current XID: " + xid);
        return accountMapper.account(userId, money);
    }

存在的问题

  • 如果所有服务都接入了sharding-jdbc,在事务加入方的service方法上不可以添加@Transactional(rollbackFor = Exception.class),否则会导致全局事务失效,但是这样会导致服务自身直接调用service方法时会以无事务方式运行。(添加@ShardingTransactionType(TransactionType.BASE)也不可以发起本地事务)
  • 可以尝试使用其他博客中推荐的方式,将数据源手动代理

推荐几篇解析博客

https://juejin.cn/post/7291832105105342491
https://seata.apache.org/zh-cn/blog/seata-datasource-proxy
https://github.com/apache/incubator-seata-samples/tree/sample-refactor

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐