五阶段--分布式事务--订单业务添加seata-at 全局事务协调器

《分布式事务(三)Seata分布式事务框架-AT模式介绍》 中介绍了 Seata AT 事务原理,介绍了 AT 事务的三个角色:TC(事务协调器)、TM(事务管理器)和RM(资源管理器),其中 TM 和 RM 是嵌入在业务应用中的,而 TC 则是一个独立服务。

a

一 TC全局事务协调器 --seata server

1.解压'课前资料/分布式事务/seata-server-1.3.0.zip'

2.三个配置文件,conf文件中拉进idea中进行编辑

2.1 register.conf--向注册中心注册

a

Seata Server 要向注册中心进行注册,这样,其他服务就可以通过注册中心去发现 Seata Server,与 Seata Server 进行通信。

Seata 支持多款注册中心服务:nacos 、eureka、redis、zk、consul、etcd3、sofa。

我们项目中要使用 eureka 注册中心,eureka服务的连接地址、注册的服务名,这需要在 registry.conf 文件中进行配置:

2.2 file.conf--seata server 运行过程中,在seata数据库中记录日志

a

2.3  bin包下seata-server.bat--使用内存设置成256M,测试环境下内存可以设置小一点

3.执行seata-server.bat启动seata-server

方式1: 双击 seata-server.bat 启动 Seata Server。注意,dos窗口启动后不能关闭,否则代表关闭seata-server!!

方式2: 也可以在seata-server.bat所在的文件目录栏输入cdm启动dos窗口,再输入seata-server.bat命令启动seata-server全局协调器

注意:下次再启动seata server时要先启动数据库和eureka服务.

结果:查看 Eureka 注册中心 Seata Server 的注册信息:以后可能配备多个事务协调器

二 在业务模块中,添加Seata AT事务

1  添加seata依赖

在order-parent父工程中注释的依赖打开并添加

 <!--分布式事务seata依赖-->
        <dependency>
          <groupId>com.alibaba.cloud</groupId>
          <artifactId>spring-cloud-alibaba-seata</artifactId>
          <version>${spring-cloud-alibaba-seata.version}</version>
          <exclusions>
            <exclusion>
              <artifactId>seata-all</artifactId>
              <groupId>io.seata</groupId>
            </exclusion>
          </exclusions>
        </dependency>
        <dependency>
          <groupId>io.seata</groupId>
          <artifactId>seata-all</artifactId>
          <version>${seata.version}</version>
        </dependency>

此时,添加了seata依赖,其他三个子工程都无法打开

2  三个配置文件:

  • application.yml--事务组命名

order工程中的配置文件配置seata命名服务名称,目的为了这一组事务能够使用对应的协调器

  cloud:
    alibaba:
      seata:
        #三个模块 order,storage,account 都要在这同一事物组中
        tx-service-group: order_tx_group
  • registry.conf--注册中心地址,用来发现事务协调器(TC)

在redource包下创建registry.conf文件进行编辑

需要从注册中心获得 TC 的地址,这里配置注册中心的地址。

TC 在注册中心注册的服务ID在下面 file.conf 中指定。

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "eureka"

  nacos {
    serverAddr = "localhost"
    namespace = ""
    cluster = "default"
  }
  eureka {
    serviceUrl = "http://localhost:8761/eureka"
    # application = "default"
    # weight = "1"
  }
  redis {
    serverAddr = "localhost:6379"
    db = "0"
    password = ""
    cluster = "default"
    timeout = "0"
  }
  zk {
    cluster = "default"
    serverAddr = "127.0.0.1:2181"
    session.timeout = 6000
    connect.timeout = 2000
    username = ""
    password = ""
  }
  consul {
    cluster = "default"
    serverAddr = "127.0.0.1:8500"
  }
  etcd3 {
    cluster = "default"
    serverAddr = "http://localhost:2379"
  }
  sofa {
    serverAddr = "127.0.0.1:9603"
    application = "default"
    region = "DEFAULT_ZONE"
    datacenter = "DefaultDataCenter"
    cluster = "default"
    group = "SEATA_GROUP"
    addressWaitTime = "3000"
  }
  file {
    name = "file.conf"
  }
}

config {
  # file、nacos 、apollo、zk、consul、etcd3、springCloudConfig
  type = "file"

  nacos {
    serverAddr = "localhost"
    namespace = ""
    group = "SEATA_GROUP"
  }
  consul {
    serverAddr = "127.0.0.1:8500"
  }
  apollo {
    app.id = "seata-server"
    apollo.meta = "http://192.168.1.204:8801"
    namespace = "application"
  }
  zk {
    serverAddr = "127.0.0.1:2181"
    session.timeout = 6000
    connect.timeout = 2000
    username = ""
    password = ""
  }
  etcd3 {
    serverAddr = "http://localhost:2379"
  }
  file {
    name = "file.conf"
  }
}
  • file.conf

在redource包下创建file.conf文件进行编辑

在这里我们指定 TC 的服务ID seata-server

vgroupMapping.order_tx_group = "seata-server"

order_tx_group 对应 application.yml 中注册的事务组名。

transport {
  # tcp udt unix-domain-socket
  type = "TCP"
  #NIO NATIVE
  server = "NIO"
  #enable heartbeat
  heartbeat = true
  # the client batch send request enable
  enableClientBatchSendRequest = true
  #thread factory for netty
  threadFactory {
    bossThreadPrefix = "NettyBoss"
    workerThreadPrefix = "NettyServerNIOWorker"
    serverExecutorThread-prefix = "NettyServerBizHandler"
    shareBossWorker = false
    clientSelectorThreadPrefix = "NettyClientSelector"
    clientSelectorThreadSize = 1
    clientWorkerThreadPrefix = "NettyClientWorkerThread"
    # netty boss thread size,will not be used for UDT
    bossThreadSize = 1
    #auto default pin or 8
    workerThreadSize = "default"
  }
  shutdown {
    # when destroy server, wait seconds
    wait = 3
  }
  serialization = "seata"
  compressor = "none"
}
service {
  #transaction service group mapping
  # order_tx_group 与 yml 中的 “tx-service-group: order_tx_group” 配置一致
  # “seata-server” 与 TC 服务器的注册名一致
  # 从eureka获取seata-server的地址,再向seata-server注册自己,设置group
  vgroupMapping.order_tx_group = "seata-server"
  #only support when registry.type=file, please don't set multiple addresses
  order_tx_group.grouplist = "127.0.0.1:8091"
  #degrade, current not support
  enableDegrade = false
  #disable seata
  disableGlobalTransaction = false
}

client {
  rm {
    asyncCommitBufferLimit = 10000
    lock {
      retryInterval = 10
      retryTimes = 30
      retryPolicyBranchRollbackOnConflict = true
    }
    reportRetryCount = 5
    tableMetaCheckEnable = false
    reportSuccessEnable = false
  }
  tm {
    commitRetryCount = 5
    rollbackRetryCount = 5
  }
  undo {
    dataValidation = true
    logSerialization = "jackson"
    logTable = "undo_log"
  }
  log {
    exceptionRate = 100
  }
}

3  通过自定义的自动配置类,创建数据源代理对象;

Seata AT 事务对业务代码无侵入,全自动化处理全局事务,其功能是靠 Seata 的数据源代理工具实现的。

这里我们创建 Seata 的数据源代理,并排除 Spring 默认的数据源。

  • order包下创建自定义配置类
package cn.tedu.order;

import com.alibaba.druid.pool.DruidDataSource;
import com.zaxxer.hikari.HikariDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

/**自定义的数据源自动配置类,与spring中默认的数据源自动配置类冲突,
 * 需要在启动类中,排除spring默认的自动配置
 * */
@Configuration
public class DSAutoConfiguration {
    //创建原始数据源对象
    //Hikari使用的数据库地址参数不是 url,而是 jdbcUrl
    @ConfigurationProperties(prefix = "spring.datasource")//指定数据源前缀
    @Bean
    public DataSource dataSource(){
        return new HikariDataSource();
       // return new DruidDataSource(); //二选一
    }
    //创建数据源代理对象
    @Primary  //首选对象
    @Bean
    public DataSource dataSourceProxy(DataSource ds){
        return new DataSourceProxy(ds);//被代理的目标对象
    }
}
  • application.yml文件配置原始数据源

  •  orderApplication启动类上,排除 Spring 默认的数据源
package cn.tedu.order;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.openfeign.EnableFeignClients;

@EnableFeignClients
@MapperScan("cn.tedu.order.mapper")
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//排除spring默认的数据源
public class OrderApplication {

    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }

}

4  在业务方法上,添加注解,启动全局事务

  • @GlobalTransactional -- 用于启动全局事务,只在第一个模块添加 
  • @Transactional -- 控制本地事务

Seata AT 对业务无侵入,所以启动全局事务非常简单,只需要添加一个 @GlobalTransactional 注解即可。在OrderServiceImpl类中添加两个注解:

 一步一步地添加全局事务并测试,先把远程调用方法取消,这里先把 storage 和 account 调用注掉:

5  启动 order 项目进行测试

  • 按顺序启动服务:Eureka-->Seata Server-->Easy Id Generator-->Order

  • 调用保存订单,地址:

http://localhost:8083/create?userId=1&productId=1&count=10&money=100

观察Order工程控制台,看到全局事务和订单的分支事务已经启动,并可以看到全局事务ID(XID)和分支事务ID(Branch ID):

然后观察数据库中新添加的订单数据:

a

 6 acount和storage工程的application.yml也要添加事务组命名

 7  复制resource包下的registry.conf和file.conf文件到account以及storage工程的resource包下,执行clean,两个文件才可以被其他两个工程使用

 8 自定义配置类 DSAutoConfiguration 也复制到account及storage工程中

package cn.tedu.storage;

import com.zaxxer.hikari.HikariDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;

/**自定义的数据源自动配置类,与spring中默认的数据源自动配置类冲突,
 * 需要在启动类中,排除spring默认的自动配置
 * */
@Configuration
public class DSAutoConfiguration {
    //创建原始数据源对象
    //Hikari使用的数据库地址参数不是 url,而是 jdbcUrl
    @ConfigurationProperties(prefix = "spring.datasource")//指定数据源前缀
    @Bean
    public DataSource dataSource(){
        return new HikariDataSource();
       // return new DruidDataSource(); //二选一
    }
    //创建数据源代理对象
    @Primary  //首选对象
    @Bean
    public DataSource dataSourceProxy(DataSource ds){
        return new DataSourceProxy(ds);//被代理的目标对象
    }
}

9 account工程和storage工程启动类中排除spring默认数据源配置

package cn.tedu.storage;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;

@MapperScan("cn.tedu.storage.mapper")
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//排除spring默认的数据源
public class StorageApplication {

    public static void main(String[] args) {
        SpringApplication.run(StorageApplication.class, args);
    }

}
package cn.tedu.account;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;

@MapperScan("cn.tedu.account.mapper")
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//排除spring默认的数据源
public class AccountApplication {

    public static void main(String[] args) {

        SpringApplication.run(AccountApplication.class, args);
    }

}

10 account和storage工程的*ServiceImpl实现类中添加控制本地事务@Transational注解

11 打开远程调用的方法,使订单业务的三个工程串联起来

 12 启动 storage和account工程 进行测试

12.1 启动 storage 项目进行测试

按顺序启动项目:

  1. Eureka
  2. Seata Server
  3. Easy Id Generator
  4. Storage
  5. Order

调用保存订单,地址:
http://localhost:8083/create?userId=1&productId=1&count=10&money=100

订单会调用库存,这两个服务会分别启动一个分支事务,两个分支事务一起组成一个全局事务:

a

观察两个项目的控制台都有Seata AT事务的日志,Storage 项目控制台如下:

然后观察数据库中新添加的订单和减少的库存:

减少的库存:

a

12.2  启动 account 项目进行测试 

按顺序启动项目:

  1. Eureka
  2. Seata Server
  3. Easy Id Generator
  4. Storage
  5. Account
  6. Order

调用保存订单,地址:
http://localhost:8083/create?userId=1&productId=1&count=10&money=100

订单会调用库存和账户,这三个服务会分别启动一个分支事务,三个分支事务一起组成一个全局事务:

a

观察三个项目的控制台都有Seata AT事务的日志,account 项目控制台如下: 

a

然后观察数据库中的订单表、库存表和账户表。

这是账户表,看到金额已经被扣减:

 a

13  测试出现异常,回滚的情况

第一步:在account工程的 mapper层  AccountMapper 接口中添加查找账户信息的方法

第二步: 在resource包下 的mapper包中的 AccountMapper.xml文件中使用SQL语句实现持久层方法的查找方法

第三步:在account工程的 AccountServiceImpl 实现类中 添加执行失败时的回滚策略

重启 account 项目,并调用保存订单:
http://localhost:8083/create?userId=1&productId=1&count=10&money=400

查看数据库表 order、storage 和 account,如果执行成功会新增订单、减少库存、扣减金额,如果多次请求可能会执行失败则数据没有变化,被回滚了。

失败时,在 order 和 storage 控制台可以看到回滚日志。

这是 storage 的回滚日志:
a

 account 的控制台会报错:

最后,把模拟异常处理策略注释掉

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值