• SpringCloud - Spring Cloud Alibaba 之 Seata分布式事务服务;AT事务模式(二十)


    阅读本文前可先参考:

    https://blog.csdn.net/MinggeQingchun/article/details/126100300

    https://blog.csdn.net/MinggeQingchun/article/details/126176893

    AT 模式

    Seata AT 模式

    前提

    1、基于支持本地 ACID 事务的关系型数据库

    2、Java 应用,通过 JDBC 访问数据库

    整体机制

    两阶段提交协议的演变:

    1、一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源

    2、二阶段:

    (1)提交异步化,非常快速地完成

    (2)回滚通过一阶段的回滚日志进行反向补偿

    写隔离

    1、一阶段本地事务提交前,需要确保先拿到 全局锁 

    2、拿不到 全局锁 ,不能提交本地事务

    3、拿 全局锁 的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁

    读隔离

    在数据库本地事务隔离级别 读已提交(Read Committed) 或以上的基础上,Seata(AT 模式)的默认全局隔离级别是 读未提交(Read Uncommitted) 。

    如果应用在特定场景下,必需要求全局的 读已提交 ,目前 Seata 的方式是通过 SELECT FOR UPDATE 语句的代理。

    一、AT事务模式:SpringBoot单体应用多数据源AT分布式事务

    在Spring Boot单体项目中,使用了多数据源,就要保证多个数据源的数据一致性,即产生了分布式事务的问题,采用Seata的AT事务模式来解决该分布式事务问题

    以下图购物下单为例

    1、创建数据库、表、插入数据等

    (1)accountdb账户库、account账户表

    1. SET NAMES utf8mb4;
    2. SET FOREIGN_KEY_CHECKS = 0;
    3. -- ----------------------------
    4. -- Table structure for account
    5. -- ----------------------------
    6. DROP TABLE IF EXISTS `account`;
    7. CREATE TABLE `account` (
    8. `id` int(20) NOT NULL AUTO_INCREMENT,
    9. `user_id` int(20) NULL DEFAULT NULL,
    10. `balance` decimal(20, 0) NULL DEFAULT NULL,
    11. `update_time` datetime(6) NULL DEFAULT NULL,
    12. PRIMARY KEY (`id`) USING BTREE
    13. ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
    14. SET FOREIGN_KEY_CHECKS = 1;

    (2)productdb产品库、product产品表

    1. SET NAMES utf8mb4;
    2. SET FOREIGN_KEY_CHECKS = 0;
    3. -- ----------------------------
    4. -- Table structure for product
    5. -- ----------------------------
    6. DROP TABLE IF EXISTS `product`;
    7. CREATE TABLE `product` (
    8. `id` int(20) NOT NULL AUTO_INCREMENT,
    9. `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
    10. `price` decimal(10, 2) NULL DEFAULT NULL,
    11. `stock` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
    12. `add_time` datetime(6) NULL DEFAULT NULL,
    13. `update_time` datetime(6) NULL DEFAULT NULL,
    14. PRIMARY KEY (`id`) USING BTREE
    15. ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
    16. SET FOREIGN_KEY_CHECKS = 1;

    (3)orderdb订单库、orders 订单表

    1. SET NAMES utf8mb4;
    2. SET FOREIGN_KEY_CHECKS = 0;
    3. -- ----------------------------
    4. -- Table structure for orders
    5. -- ----------------------------
    6. DROP TABLE IF EXISTS `orders`;
    7. CREATE TABLE `orders` (
    8. `id` int(20) NOT NULL AUTO_INCREMENT,
    9. `user_id` int(20) NULL DEFAULT NULL,
    10. `product_id` int(20) NULL DEFAULT NULL,
    11. `pay_amount` decimal(20, 0) NULL DEFAULT NULL,
    12. `add_time` datetime(6) NULL DEFAULT NULL,
    13. `update_time` datetime(6) NULL DEFAULT NULL,
    14. PRIMARY KEY (`id`) USING BTREE
    15. ) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
    16. SET FOREIGN_KEY_CHECKS = 1;

    (4)undo_log表 

    Seata AT 模式

    1. -- 注意此处0.7.0+ 增加字段 context
    2. CREATE TABLE `undo_log` (
    3. `id` bigint(20) NOT NULL AUTO_INCREMENT,
    4. `branch_id` bigint(20) NOT NULL,
    5. `xid` varchar(100) NOT NULL,
    6. `context` varchar(128) NOT NULL,
    7. `rollback_info` longblob NOT NULL,
    8. `log_status` int(11) NOT NULL,
    9. `log_created` datetime NOT NULL,
    10. `log_modified` datetime NOT NULL,
    11. PRIMARY KEY (`id`),
    12. UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
    13. ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

    注:

    每个库必须创建 undo_log 表,是 Seata AT模式必须创建的表,主要用于分支事务的回滚

    Seata AT 模式

     

    2、创建 SpringBoot单体应用

    1、创建一个 springboot应用,命名 springcloud-alibaba-2-seata-distributed-transaction

    2、添加依赖(非Spring CLoud 微服务项目,没有Spring CLoud依赖)

    1. <groupId>com.companygroupId>
    2. <artifactId>springcloud-alibaba-2-seata-distributed-transactionartifactId>
    3. <version>1.0.0version>
    4. <name>springcloud-alibaba-2-seata-distributed-transactionname>
    5. <description>Demo project for Spring Bootdescription>
    6. <properties>
    7. <java.version>1.8java.version>
    8. <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
    9. <project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
    10. <spring-boot.version>2.3.12.RELEASEspring-boot.version>
    11. <spring-cloud-alibaba.version>2.2.7.RELEASEspring-cloud-alibaba.version>
    12. properties>
    13. <dependencies>
    14. <dependency>
    15. <groupId>org.springframework.bootgroupId>
    16. <artifactId>spring-boot-starter-webartifactId>
    17. dependency>
    18. <dependency>
    19. <groupId>org.springframework.bootgroupId>
    20. <artifactId>spring-boot-starter-actuatorartifactId>
    21. dependency>
    22. <dependency>
    23. <groupId>org.springframework.bootgroupId>
    24. <artifactId>spring-boot-starter-testartifactId>
    25. <scope>testscope>
    26. <exclusions>
    27. <exclusion>
    28. <groupId>org.junit.vintagegroupId>
    29. <artifactId>junit-vintage-engineartifactId>
    30. exclusion>
    31. exclusions>
    32. dependency>
    33. <dependency>
    34. <groupId>org.projectlombokgroupId>
    35. <artifactId>lombokartifactId>
    36. dependency>
    37. <dependency>
    38. <groupId>mysqlgroupId>
    39. <artifactId>mysql-connector-javaartifactId>
    40. dependency>
    41. <dependency>
    42. <groupId>org.mybatis.spring.bootgroupId>
    43. <artifactId>mybatis-spring-boot-starterartifactId>
    44. <version>2.1.3version>
    45. dependency>
    46. <dependency>
    47. <groupId>io.seatagroupId>
    48. <artifactId>seata-spring-boot-starterartifactId>
    49. <version>1.3.0version>
    50. dependency>
    51. <dependency>
    52. <groupId>com.baomidougroupId>
    53. <artifactId>dynamic-datasource-spring-boot-starterartifactId>
    54. <version>3.2.0version>
    55. dependency>
    56. dependencies>
    57. <dependencyManagement>
    58. <dependencies>
    59. <dependency>
    60. <groupId>com.alibaba.cloudgroupId>
    61. <artifactId>spring-cloud-alibaba-dependenciesartifactId>
    62. <version>${spring-cloud-alibaba.version}version>
    63. <type>pomtype>
    64. <scope>importscope>
    65. dependency>
    66. <dependency>
    67. <groupId>org.springframework.cloudgroupId>
    68. <artifactId>spring-cloud-dependenciesartifactId>
    69. <version>Hoxton.SR12version>
    70. <type>pomtype>
    71. <scope>importscope>
    72. dependency>
    73. <dependency>
    74. <groupId>org.springframework.bootgroupId>
    75. <artifactId>spring-boot-dependenciesartifactId>
    76. <version>${spring-boot.version}version>
    77. <type>pomtype>
    78. <scope>importscope>
    79. dependency>
    80. dependencies>
    81. dependencyManagement>
    82. <build>
    83. <plugins>
    84. <plugin>
    85. <groupId>org.apache.maven.pluginsgroupId>
    86. <artifactId>maven-compiler-pluginartifactId>
    87. <configuration>
    88. <source>1.8source>
    89. <target>1.8target>
    90. <encoding>UTF-8encoding>
    91. configuration>
    92. plugin>
    93. <plugin>
    94. <groupId>org.springframework.bootgroupId>
    95. <artifactId>spring-boot-maven-pluginartifactId>
    96. plugin>
    97. <plugin>
    98. <groupId>org.mybatis.generatorgroupId>
    99. <artifactId>mybatis-generator-maven-pluginartifactId>
    100. <version>1.4.0version>
    101. <configuration>
    102. <configurationFile>src/main/resources/generatorConfig.xmlconfigurationFile>
    103. <verbose>trueverbose>
    104. <overwrite>trueoverwrite>
    105. configuration>
    106. plugin>
    107. plugins>
    108. <resources>
    109. <resource>
    110. <directory>src/main/javadirectory>
    111. <includes>
    112. <include>**/*.xmlinclude>
    113. includes>
    114. resource>
    115. <resource>
    116. <directory>src/main/resourcesdirectory>
    117. <includes>
    118. <include>**/*.*include>
    119. includes>
    120. resource>
    121. resources>
    122. build>

    3、application.properties配置文件

    1. #内嵌服务器端口
    2. server.port=8081
    3. #应用服务名称
    4. spring.application.name=springcloud-alibaba-2-seata-distributed-transaction
    5. # 设置默认的数据源或者数据源组,默认值即为master
    6. spring.datasource.dynamic.primary=order-ds
    7. # 订单order数据源配置
    8. spring.datasource.dynamic.datasource.order-ds.url=jdbc:mysql://localhost:3306/orderdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useSSL=false
    9. spring.datasource.dynamic.datasource.order-ds.driver-class-name=com.mysql.cj.jdbc.Driver
    10. spring.datasource.dynamic.datasource.order-ds.username=root
    11. spring.datasource.dynamic.datasource.order-ds.password=admin123456
    12. # 商品product数据源配置
    13. spring.datasource.dynamic.datasource.product-ds.url=jdbc:mysql://localhost:3306/productdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useSSL=false
    14. spring.datasource.dynamic.datasource.product-ds.driver-class-name=com.mysql.cj.jdbc.Driver
    15. spring.datasource.dynamic.datasource.product-ds.username=root
    16. spring.datasource.dynamic.datasource.product-ds.password=admin123456
    17. # 账户account数据源配置
    18. spring.datasource.dynamic.datasource.account-ds.url=jdbc:mysql://localhost:3306/accountdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useSSL=false
    19. spring.datasource.dynamic.datasource.account-ds.driver-class-name=com.mysql.cj.jdbc.Driver
    20. spring.datasource.dynamic.datasource.account-ds.username=root
    21. spring.datasource.dynamic.datasource.account-ds.password=admin123456
    22. # 是否启动对Seata的集成
    23. spring.datasource.dynamic.seata=true
    24. #-----------------------------------------------------------
    25. #单机版 tc server 配置
    26. # Seata应用编号,默认为 ${spring.application.name}
    27. seata.application-id=springboot-seata
    28. # Seata事务组编号,用于TC集群名,一般格式为:${spring.application.name}-group
    29. seata.tx-service-group=springboot-seata-group
    30. # 虚拟组和分组的映射 seata.service.vgroup-mapping.${seata.tx-service-group}=default
    31. seata.service.vgroup-mapping.springboot-seata-group=default
    32. # 分组和Seata服务的映射,此处default指上面 seata.service.vgroup-mapping.springboot-seata-group 的值 default
    33. seata.service.grouplist.default=192.168.133.129:8091
    34. # 存储模式 默认 file模式
    35. seata.config.type=file
    36. # 默认为 file
    37. seata.registry.type=file
    38. #------------------------------------------------------------

    4、编写相应的 controller、model、mapper、service类,这里只给出调用顺序相关的类

     controller测试类

    1. @Slf4j //lombok
    2. @RestController
    3. public class OrderController {
    4. @Autowired
    5. private OrderService orderService;
    6. @RequestMapping("/order")
    7. public Integer createOrder(@RequestParam("userId") Integer userId,
    8. @RequestParam("productId") Integer productId) throws Exception {
    9. log.info("请求下单, 用户:{}, 商品:{}", userId, productId);
    10. return orderService.createOrder(userId, productId);
    11. }
    12. }

    order逻辑类

    注:

    (1)@DS注解;多数据源切换

    (2)@GlobalTransactional注解;seata全局事务注解

    主服务加上@GlobalTransactional注解即可,被调用服务不用加@GlobalTransactional和@Transactional

    1. @Slf4j
    2. @Service
    3. public class OrderServiceImpl implements OrderService {
    4. @Autowired
    5. private OrdersMapper ordersMapper;
    6. @Autowired
    7. private AccountService accountService;
    8. @Autowired
    9. private ProductService productService;
    10. @Override
    11. /**
    12. * MyBatis-Plus 使用 @DS注解 做多数据源切换
    13. * 语法:@DS(value = "数据源名称")
    14. * 1、依赖:
    15. *
    16. * com.baomidou
    17. * dynamic-datasource-spring-boot-starter
    18. * 3.0.0
    19. *
    20. * 2、yml 或 properties 配置
    21. * # 设置默认的数据源或者数据源组,默认值即为master
    22. * spring.datasource.dynamic.primary=order-ds
    23. *
    24. * # 订单order数据源配置
    25. * spring.datasource.dynamic.datasource.order-ds.url=jdbc:mysql://localhost:3306/orderdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useSSL=false
    26. * spring.datasource.dynamic.datasource.order-ds.driver-class-name=com.mysql.cj.jdbc.Driver
    27. * spring.datasource.dynamic.datasource.order-ds.username=root
    28. * spring.datasource.dynamic.datasource.order-ds.password=admin123456
    29. *
    30. * # 商品product数据源配置
    31. * spring.datasource.dynamic.datasource.product-ds.url=jdbc:mysql://localhost:3306/productdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useSSL=false
    32. * spring.datasource.dynamic.datasource.product-ds.driver-class-name=com.mysql.cj.jdbc.Driver
    33. * spring.datasource.dynamic.datasource.product-ds.username=root
    34. * spring.datasource.dynamic.datasource.product-ds.password=admin123456
    35. * 3、@DS注解到实现类或者实现类的方法上才可以
    36. * 当注解添加到类上,意味着此类里的方法都使用此数据源;
    37. * 当注解添加到方法上时,意味着此方法上使用的数据源优先级高于其他一切配置
    38. * 注:
    39. * (1)注解添加在dao.mapper上无效
    40. * (2)注解添加到interface Service类上无效
    41. * (3)注解添加到interface Service方法上无效
    42. */
    43. @DS(value = "order-ds")
    44. @GlobalTransactional //seata全局事务注解
    45. public Integer createOrder(Integer userId, Integer productId) throws Exception {
    46. Integer amount = 1; // 购买数量暂时设置为 1
    47. log.info("当前 XID: {}", RootContext.getXID());
    48. //1、减库存
    49. Product product = productService.reduceStock(productId, amount);
    50. //2、减余额
    51. accountService.reduceBalance(userId, product.getPrice());
    52. //3、下订单
    53. Orders order = new Orders();
    54. order.setUserId(userId);
    55. order.setProductId(productId);
    56. order.setPayAmount(product.getPrice().multiply(new BigDecimal(amount)));
    57. order.setAddTime(new Date());
    58. ordersMapper.insertSelective(order);
    59. //造成异常,测试是否回滚
    60. //int a = 10/0;
    61. log.info("下订单: {}", order.getId());
    62. // 返回订单编号
    63. return order.getId();
    64. }
    65. }

     product逻辑类

    1. @Slf4j
    2. @Service
    3. public class ProductServiceImpl implements ProductService {
    4. @Autowired
    5. private ProductMapper productMapper;
    6. @Override
    7. @DS(value = "product-ds")
    8. public Product reduceStock(Integer productId, Integer amount) throws Exception {
    9. log.info("当前 XID: {}", RootContext.getXID());
    10. // 检查库存
    11. Product product = productMapper.selectByPrimaryKey(productId);
    12. if (product.getStock() < amount) {
    13. throw new Exception("库存不足");
    14. }
    15. // 扣减库存
    16. int updateCount = productMapper.reduceStock(productId, amount);
    17. // 扣除成功
    18. if (updateCount == 0) {
    19. throw new Exception("库存不足");
    20. }
    21. //造成异常,测试是否回滚
    22. //int a = 10/0;
    23. // 扣除成功
    24. log.info("扣除 {} 库存成功", productId);
    25. return product;
    26. }
    27. }

    account逻辑类

    1. @Slf4j
    2. @Service
    3. public class AccountServiceImpl implements AccountService {
    4. @Autowired
    5. private AccountMapper accountMapper;
    6. @Override
    7. @DS(value = "account-ds")
    8. public void reduceBalance(Integer userId, BigDecimal money) throws Exception {
    9. log.info("当前 XID: {}", RootContext.getXID());
    10. // 检查余额
    11. Account account = accountMapper.selectAccountByUserId(userId);
    12. if (account.getBalance().doubleValue() < money.doubleValue()) {
    13. throw new Exception("余额不足");
    14. }
    15. // 扣除余额
    16. int updateCount = accountMapper.reduceBalance(userId, money);
    17. // 扣除成功
    18. if (updateCount == 0) {
    19. throw new Exception("余额不足");
    20. }
    21. //造成异常,测试是否回滚
    22. //int a = 10/0;
    23. log.info("扣除用户 {} 余额成功", userId);
    24. }
    25. }

    5、启动seata-server;浏览器输入访问 http://localhost:8081/order?userId=1&productId=1

    可分别在 OrderServiceImpl、ProductServiceImpl、AccountServiceImpl 实现类中 写入如下代码进行事务回滚测试

    1. //造成异常,测试是否回滚
    2. int a = 10/0;

    @DS注解 

    MyBatis-Plus 使用 @DS注解 做多数据源切换 

    语法:

    @DS(value = "数据源名称")

    1、依赖:

    1. <dependency>
    2. <groupId>com.baomidougroupId>
    3. <artifactId>dynamic-datasource-spring-boot-starterartifactId>
    4. <version>3.2.0version>
    5. dependency>

    2、yml 或 properties 配置 

    1. # 设置默认的数据源或者数据源组,默认值即为master
    2. spring.datasource.dynamic.primary=order-ds
    3. # 订单order数据源配置
    4. spring.datasource.dynamic.datasource.order-ds.url=jdbc:mysql://localhost:3306/orderdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useSSL=false
    5. spring.datasource.dynamic.datasource.order-ds.driver-class-name=com.mysql.cj.jdbc.Driver
    6. spring.datasource.dynamic.datasource.order-ds.username=root
    7. spring.datasource.dynamic.datasource.order-ds.password=admin123456
    8. # 商品product数据源配置
    9. spring.datasource.dynamic.datasource.product-ds.url=jdbc:mysql://localhost:3306/productdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useSSL=false
    10. spring.datasource.dynamic.datasource.product-ds.driver-class-name=com.mysql.cj.jdbc.Driver
    11. spring.datasource.dynamic.datasource.product-ds.username=root
    12. spring.datasource.dynamic.datasource.product-ds.password=admin123456
    13. # 账户account数据源配置
    14. spring.datasource.dynamic.datasource.account-ds.url=jdbc:mysql://localhost:3306/accountdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useSSL=false
    15. spring.datasource.dynamic.datasource.account-ds.driver-class-name=com.mysql.cj.jdbc.Driver
    16. spring.datasource.dynamic.datasource.account-ds.username=root
    17. spring.datasource.dynamic.datasource.account-ds.password=admin123456

    3、@DS注解到实现类或者实现类的方法上才可以

    当注解添加到类上,意味着此类里的方法都使用此数据源;

    当注解添加到方法上时,意味着此方法上使用的数据源优先级高于其他一切配置

    注:

    (1)注解添加在dao.mapper上无效

    (2)注解添加到interface Service类上无效

    (3)注解添加到interface Service方法上无效

    注:

    如果try catch 异常 则不会回滚事务

    二、AT事务模式:Spring Cloud Alibaba微服务AT分布式事务

    1、临时关闭Linux上防火墙,或者设置端口访问权限

    systemctl stop firewalld

    2、 因Nacos使用mysql持久化,需要先开启 mysql服务(手动安装或者docker启动,docker启动需要先启动docker和挂载mysql服务,不然重启docker或者mysql导致mysql数据丢失)

    3、首先启动nacos服务

    sh startup.sh -m standalone

    单机环境必须带-m standalone参数启动;不带参数启动的是集群环境 

    4、因Seata server 使用的 Nacos 注册中心,需要配置 conf/registry.conf 文件,选择nacos

    Seata Server 配置
    在 Seata Server 安装目录下的 config/registry.conf 中,将配置方式(config.type)修改为 Nacos,并对 Nacos 配置中心的相关信息进行配置

    如果使用了 注册中心 ,如 type="nacos"等要检查nacos的 应用名application、服务注册地址serverAddr、分组group、命名空间namespace、集群cluster、用户名username、密码password是否正确等

    1. config {
    2. # Seata 支持 file、nacos 、apollo、zk、consul、etcd3 等多种配置中心
    3. #配置方式修改为 nacos
    4. type = "nacos"
    5. nacos {
    6. #修改为使用的 nacos 服务器地址
    7. serverAddr = "127.0.0.1:8848"
    8. #配置中心的命名空间
    9. namespace = ""
    10. #配置中心所在的分组
    11. group = "SEATA_GROUP"
    12. #Nacos 配置中心的用户名
    13. username = "nacos"
    14. #Nacos 配置中心的密码
    15. password = "nacos"
    16. }
    17. }

    不然报错:

    no available service found in cluster 'default', please make sure registry config correct and keep your seata server running

    Seata Client 配置

    在 Seata Client(即微服务架构中的服务)中,通过 application.yml 等配置文件对 Nacos 配置中心进行配置

    1. #-----------------------------------------------------------
    2. #单机版 tc server 配置
    3. # Seata应用编号,默认为 ${spring.application.name}
    4. seata.application-id=springcloud-order-seata
    5. # Seata事务组编号,用于TC集群名,一般格式为:${spring.application.name}-group
    6. seata.tx-service-group=springcloud-order-seata-group
    7. # 注:虚拟组和分组的映射要写对,不然报错:
    8. # no available service 'null' found, please make sure registry config correct
    9. # 虚拟组和分组的映射 seata.service.vgroup-mapping.${seata.tx-service-group}=default
    10. seata.service.vgroup-mapping.springcloud-order-seata-group=default
    11. # 分组和Seata服务的映射,此处default指上面 seata.service.vgroup-mapping.springboot-seata-group 的值 default
    12. #seata.service.grouplist.default=192.168.133.129:8091
    13. # 存储模式 默认 file模式
    14. seata.config.type=file
    15. # 默认为 file
    16. #seata.registry.type=file
    17. #------------------------------------------------------------
    18. #设置使用注册中心
    19. #seata-spring-boot-starter 1.1版本少一些配置项
    20. seata.enabled=true
    21. seata.registry.type=nacos
    22. # 集群
    23. seata.registry.nacos.cluster=default
    24. # 分组
    25. seata.registry.nacos.group=SEATA_GROUP
    26. # 应用名
    27. seata.registry.nacos.application=seata-server
    28. # 服务注册地址
    29. seata.registry.nacos.server-addr=192.168.133.129:8848

    注:!!!

    Seata应用编号 seata.application-id,默认为 ${spring.application.name}

    Seata事务组编号 seata.tx-service-group,用于TC集群名,一般格式为:${spring.application.name}-group

    Seata虚拟组和分组的映射 seata.service.vgroup-mapping.${seata.tx-service-group}=default

    三者对应关系要写对,不然会报错:

    no available service 'null' found, please make sure registry config correct

    测试应用

    1、创建 4 个SpringBoot 模块

    (1)springcloud-alibaba-2-seata-distributed-commons

    1. <dependencies>
    2. <dependency>
    3. <groupId>org.projectlombokgroupId>
    4. <artifactId>lombokartifactId>
    5. <version>1.18.16version>
    6. dependency>
    7. <dependency>
    8. <groupId>org.springframework.cloudgroupId>
    9. <artifactId>spring-cloud-starter-openfeignartifactId>
    10. <version>3.0.0version>
    11. dependency>
    12. dependencies>
    1. @FeignClient(name = "springcloud-alibaba-2-seata-distributed-account")
    2. public interface FeignAccountService {
    3. /**
    4. * 扣除余额
    5. *
    6. * @param userId 用户ID
    7. * @param money 扣减金额
    8. * @throws Exception 失败时抛出异常
    9. */
    10. @PostMapping("/account/reduceBalance")
    11. void reduceBalance(@RequestParam("userId") Integer userId, @RequestParam("money") BigDecimal money);
    12. }
    13. @FeignClient(name = "springcloud-alibaba-2-seata-distributed-order")
    14. public interface FeignOrderService {
    15. /**
    16. * 创建订单
    17. *
    18. * @param userId 用户ID
    19. * @param productId 产品ID
    20. * @return 订单编号
    21. * @throws Exception 创建订单失败,抛出异常
    22. */
    23. Integer createOrder(Integer userId, Integer productId) throws Exception;
    24. }
    25. @FeignClient(name = "springcloud-alibaba-2-seata-distributed-product")
    26. public interface FeignProductService {
    27. /**
    28. * 减库存
    29. *
    30. * @param productId 商品ID
    31. * @param amount 扣减数量
    32. * @throws Exception 扣减失败时抛出异常
    33. */
    34. @PostMapping("/product/reduceStock")
    35. Product reduceStock(@RequestParam("productId") Integer productId, @RequestParam("amount") Integer amount);
    36. }

    (2)springcloud-alibaba-2-seata-distributed-order

    注意:

    1、异常需要层层往上抛,如果你在子服务将异常处理的话(比如全局异常处理GlobalExceptionHandler),seata会认为你已经手动处理了异常

    2、出现事务失效的情况下,优先检查 RootContext.getXID() ,xid是否传递且一致

    3、主服务加上@GlobalTransactional注解即可,被调用服务不用加@GlobalTransactional和@Transactional

    4、@GlobalTransactional(rollbackFor = Exception.class)最好加上rollbackFor = Exception.class,表示遇到Exception都回滚,不然遇到有些异常(如自定义异常)则不会回滚

    1. @Slf4j //lombok
    2. @RestController
    3. public class OrderController {
    4. @Autowired
    5. private OrderService orderService;
    6. @RequestMapping("/order")
    7. public Integer createOrder(@RequestParam("userId") Integer userId,
    8. @RequestParam("productId") Integer productId) throws Exception {
    9. log.info("请求下单, 用户:{}, 商品:{}", userId, productId);
    10. return orderService.createOrder(userId, productId);
    11. }
    12. }
    13. @Slf4j
    14. @Service
    15. public class OrderServiceImpl implements OrderService {
    16. @Autowired
    17. private OrdersMapper ordersMapper;
    18. @Autowired
    19. private FeignAccountService accountService;
    20. @Autowired
    21. private FeignProductService productService;
    22. @Override
    23. @GlobalTransactional //seata全局事务注解
    24. public Integer createOrder(Integer userId, Integer productId) {
    25. Integer amount = 1; // 购买数量暂时设置为 1
    26. log.info("当前 XID: {}", RootContext.getXID());
    27. //1、减库存
    28. Product product = productService.reduceStock(productId, amount);
    29. //2、减余额
    30. accountService.reduceBalance(userId, product.getPrice());
    31. //3、下订单
    32. Orders order = new Orders();
    33. order.setUserId(userId);
    34. order.setProductId(productId);
    35. order.setPayAmount(product.getPrice().multiply(new BigDecimal(amount)));
    36. order.setAddTime(new Date());
    37. ordersMapper.insertSelective(order);
    38. //造成异常,测试是否回滚
    39. int a = 10/0;
    40. log.info("下订单: {}", order.getId());
    41. // 返回订单编号
    42. return order.getId();
    43. }
    44. }
    1. <groupId>com.companygroupId>
    2. <artifactId>springcloud-alibaba-2-seata-distributed-orderartifactId>
    3. <version>1.0.0version>
    4. <name>springcloud-alibaba-2-seata-distributed-ordername>
    5. <description>Demo project for Spring Bootdescription>
    6. <properties>
    7. <java.version>1.8java.version>
    8. <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
    9. <project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
    10. <spring-boot.version>2.3.12.RELEASEspring-boot.version>
    11. <spring-cloud-alibaba.version>2.2.7.RELEASEspring-cloud-alibaba.version>
    12. properties>
    13. <dependencies>
    14. <dependency>
    15. <groupId>org.springframework.bootgroupId>
    16. <artifactId>spring-boot-starter-webartifactId>
    17. dependency>
    18. <dependency>
    19. <groupId>com.alibaba.cloudgroupId>
    20. <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
    21. dependency>
    22. <dependency>
    23. <groupId>org.springframework.bootgroupId>
    24. <artifactId>spring-boot-starter-testartifactId>
    25. <scope>testscope>
    26. <exclusions>
    27. <exclusion>
    28. <groupId>org.junit.vintagegroupId>
    29. <artifactId>junit-vintage-engineartifactId>
    30. exclusion>
    31. exclusions>
    32. dependency>
    33. <dependency>
    34. <groupId>org.projectlombokgroupId>
    35. <artifactId>lombokartifactId>
    36. dependency>
    37. <dependency>
    38. <groupId>mysqlgroupId>
    39. <artifactId>mysql-connector-javaartifactId>
    40. dependency>
    41. <dependency>
    42. <groupId>org.mybatis.spring.bootgroupId>
    43. <artifactId>mybatis-spring-boot-starterartifactId>
    44. <version>2.1.3version>
    45. dependency>
    46. <dependency>
    47. <groupId>com.alibaba.cloudgroupId>
    48. <artifactId>spring-cloud-starter-alibaba-seataartifactId>
    49. <exclusions>
    50. <exclusion>
    51. <groupId>io.seatagroupId>
    52. <artifactId>seata-spring-boot-starterartifactId>
    53. exclusion>
    54. exclusions>
    55. dependency>
    56. <dependency>
    57. <groupId>io.seatagroupId>
    58. <artifactId>seata-spring-boot-starterartifactId>
    59. <version>1.4.2version>
    60. dependency>
    61. <dependency>
    62. <groupId>com.companygroupId>
    63. <artifactId>springcloud-alibaba-2-seata-distributed-commonsartifactId>
    64. <version>1.0.0version>
    65. dependency>
    66. dependencies>
    67. <dependencyManagement>
    68. <dependencies>
    69. <dependency>
    70. <groupId>com.alibaba.cloudgroupId>
    71. <artifactId>spring-cloud-alibaba-dependenciesartifactId>
    72. <version>${spring-cloud-alibaba.version}version>
    73. <type>pomtype>
    74. <scope>importscope>
    75. dependency>
    76. <dependency>
    77. <groupId>org.springframework.cloudgroupId>
    78. <artifactId>spring-cloud-dependenciesartifactId>
    79. <version>Hoxton.SR12version>
    80. <type>pomtype>
    81. <scope>importscope>
    82. dependency>
    83. <dependency>
    84. <groupId>org.springframework.bootgroupId>
    85. <artifactId>spring-boot-dependenciesartifactId>
    86. <version>${spring-boot.version}version>
    87. <type>pomtype>
    88. <scope>importscope>
    89. dependency>
    90. dependencies>
    91. dependencyManagement>
    92. <build>
    93. <plugins>
    94. <plugin>
    95. <groupId>org.apache.maven.pluginsgroupId>
    96. <artifactId>maven-compiler-pluginartifactId>
    97. <version>3.8.1version>
    98. <configuration>
    99. <source>1.8source>
    100. <target>1.8target>
    101. <encoding>UTF-8encoding>
    102. configuration>
    103. plugin>
    104. <plugin>
    105. <groupId>org.springframework.bootgroupId>
    106. <artifactId>spring-boot-maven-pluginartifactId>
    107. plugin>
    108. plugins>
    109. <resources>
    110. <resource>
    111. <directory>src/main/javadirectory>
    112. <includes>
    113. <include>**/*.xmlinclude>
    114. includes>
    115. resource>
    116. <resource>
    117. <directory>src/main/resourcesdirectory>
    118. <includes>
    119. <include>**/*.*include>
    120. includes>
    121. resource>
    122. resources>
    123. build>

    注:!!!

    seata-spring-boot-starter 服务端和客户端版本要一致,不然报错:

    no available service 'default' found, please make sure registry config correct
    1. server.port=8081
    2. spring.application.name=springcloud-alibaba-2-seata-distributed-order
    3. spring.datasource.url=jdbc:mysql://192.168.133.129:3306/orderdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useSSL=false
    4. spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    5. spring.datasource.username=root
    6. spring.datasource.password=123456
    7. #nacos服务的注册与发现
    8. spring.cloud.nacos.discovery.server-addr=192.168.133.129:8848
    9. # 用户名、密码为默认时,测试发现不写 用户名、密码也可以
    10. spring.cloud.nacos.username=nacos
    11. spring.cloud.nacos.password=nacos
    12. #-----------------------------------------------------------
    13. #单机版 tc server 配置
    14. # Seata应用编号,默认为 ${spring.application.name}
    15. seata.application-id=springcloud-order-seata
    16. # Seata事务组编号,用于TC集群名,一般格式为:${spring.application.name}-group
    17. seata.tx-service-group=springcloud-order-seata-group
    18. # 注:虚拟组和分组的映射要写对,不然报错:
    19. # no available service 'null' found, please make sure registry config correct
    20. # 虚拟组和分组的映射 seata.service.vgroup-mapping.${seata.tx-service-group}=default
    21. seata.service.vgroup-mapping.springcloud-order-seata-group=default
    22. # 分组和Seata服务的映射,此处default指上面 seata.service.vgroup-mapping.springboot-seata-group 的值 default
    23. #seata.service.grouplist.default=192.168.133.129:8091
    24. # 存储模式 默认 file模式
    25. seata.config.type=file
    26. # 默认为 file
    27. #seata.registry.type=file
    28. #------------------------------------------------------------
    29. #设置使用注册中心
    30. #seata-spring-boot-starter 1.1版本少一些配置项
    31. seata.enabled=true
    32. seata.registry.type=nacos
    33. # 集群
    34. seata.registry.nacos.cluster=default
    35. # 分组
    36. seata.registry.nacos.group=SEATA_GROUP
    37. # 应用名
    38. seata.registry.nacos.application=seata-server
    39. # 服务注册地址
    40. seata.registry.nacos.server-addr=192.168.133.129:8848
    41. #feign超时时间设置
    42. feign.client.config.default.connect-timeout=60000
    43. feign.client.config.default.read-timeout=60000

    (3)springcloud-alibaba-2-seata-distributed-product

    1. @Slf4j
    2. @Service
    3. public class ProductServiceImpl implements ProductService {
    4. @Autowired
    5. private ProductMapper productMapper;
    6. @Override
    7. public Product reduceStock(Integer productId, Integer amount) throws Exception {
    8. log.info("当前 XID: {}", RootContext.getXID());
    9. // 检查库存
    10. Product product = productMapper.selectByPrimaryKey(productId);
    11. if (product.getStock() < amount) {
    12. throw new Exception("库存不足");
    13. }
    14. // 扣减库存
    15. int updateCount = productMapper.reduceStock(productId, amount);
    16. // 扣除成功
    17. if (updateCount == 0) {
    18. throw new Exception("库存不足");
    19. }
    20. //造成异常,测试是否回滚
    21. //int a = 10/0;
    22. // 扣除成功
    23. log.info("扣除 {} 库存成功", productId);
    24. return product;
    25. }
    26. }

    (4)springcloud-alibaba-2-seata-distributed-account

    1. @Slf4j
    2. @Service
    3. public class AccountServiceImpl implements AccountService {
    4. @Autowired
    5. private AccountMapper accountMapper;
    6. @Override
    7. public void reduceBalance(Integer userId, BigDecimal money) throws Exception {
    8. log.info("当前 XID: {}", RootContext.getXID());
    9. // 检查余额
    10. Account account = accountMapper.selectAccountByUserId(userId);
    11. if (account.getBalance().doubleValue() < money.doubleValue()) {
    12. throw new Exception("余额不足");
    13. }
    14. // 扣除余额
    15. int updateCount = accountMapper.reduceBalance(userId, money);
    16. // 扣除成功
    17. if (updateCount == 0) {
    18. throw new Exception("余额不足");
    19. }
    20. //造成异常,测试是否回滚
    21. //int a = 10/0;
    22. log.info("扣除用户 {} 余额成功", userId);
    23. }
    24. }

    2、先启动Nacos,再启动Seata-Server

    Nacos中注册服务列表如下: 

    注:

    如果try catch 异常 则不会回滚事务

    三、遇到问题

    1、启动seata server遇到问题

    报错如下:

    1. Failed to retry rollbacking [192.168.133.129:8091:702852926242021399] Unknown java.lang.RuntimeException: rm client is not connected.
    2. dbkey:jdbc:mysql://localhost:3306/orderdb,clientId:springboot-seata:192.168.133.1:64279

    因之前博主测试 AT事务模式:单体应用多数据源分布式事务,导致 /bin/sessionStore/root.data 中含有回滚数据 ,但是连接的 数据库url是错误的,将其修改掉或者直接删除

    rm -rf root.data 

    重新启动即可

    启动seata server 报错也可参考

    https://blog.csdn.net/MinggeQingchun/article/details/126172351

    2、 no available service 'null' found, please make sure registry config correct

    no available service 'default' found, please make sure registry config correct

    no available service found in cluster 'default', please make sure registry config correct and keep your seata server running

    启动项目时因为 Seata 服务注册到Nacos 出现过如下三种错误:

    (1) no available service 'null' found, please make sure registry config correct

    Seata应用编号 seata.application-id,默认为 ${spring.application.name}

    Seata事务组编号 seata.tx-service-group,用于TC集群名,一般格式为:${spring.application.name}-group

    Seata虚拟组和分组的映射 seata.service.vgroup-mapping.${seata.tx-service-group}=default

    三者对应关系要写对,不然会报错:

    no available service 'null' found, please make sure registry config correct

    (2)no available service 'default' found, please make sure registry config correct

    博主 Seata Server 使用 1.4.2 版本

    1. <dependency>
    2. <groupId>com.alibaba.cloudgroupId>
    3. <artifactId>spring-cloud-starter-alibaba-seataartifactId>
    4. <exclusions>
    5. <exclusion>
    6. <groupId>io.seatagroupId>
    7. <artifactId>seata-spring-boot-starterartifactId>
    8. exclusion>
    9. exclusions>
    10. dependency>
    11. <dependency>
    12. <groupId>io.seatagroupId>
    13. <artifactId>seata-spring-boot-starterartifactId>
    14. <version>1.4.2/version>
    15. dependency>
    16. <dependency>
    17. <groupId>com.alibaba.nacosgroupId>
    18. <artifactId>nacos-clientartifactId>
    19. <version>1.2.0及以上版本version>
    20. dependency>

    seata-spring-boot-starter 服务端和客户端版本要一致,不然报错:

    no available service 'default' found, please make sure registry config correct

    (3)no available service found in cluster 'default', please make sure registry config correct and keep your seata server running

    使用了 注册中心 ,如 type="nacos"等要检查nacos的 应用名application、服务注册地址serverAddr、分组group、命名空间namespace、集群cluster、用户名username、密码password是否正确等

    1. config {
    2. # Seata 支持 file、nacos 、apollo、zk、consul、etcd3 等多种配置中心
    3. #配置方式修改为 nacos
    4. type = "nacos"
    5. nacos {
    6. #修改为使用的 nacos 服务器地址
    7. serverAddr = "127.0.0.1:8848"
    8. #配置中心的命名空间
    9. namespace = ""
    10. #配置中心所在的分组
    11. group = "SEATA_GROUP"
    12. #Nacos 配置中心的用户名
    13. username = "nacos"
    14. #Nacos 配置中心的密码
    15. password = "nacos"
    16. }
    17. }

    不然报错:

    no available service found in cluster 'default', please make sure registry config correct and keep your seata server running

    3、 @GlobalTransactional注解 无效,事务不会滚

    具体可参考

    Seata常见问题

    通过AOP动态创建/关闭Seata分布式事务

    集成 spring-cloud-starter-alibaba-seata @GlobalTransactional 失效的问题 - 来世还做程序员 - 博客园

    openfeign+seata+zipkin通过feign请求服务栈溢出异常的问题_陌生人的魅力的博客-CSDN博客

    feign调用服务,被调用服务seata事务不开启或者xid为空_广铁小多多的博客-CSDN博客_seata xid不一致Seata分布式事务失效,不生效(事务不回滚)的常见场景_godkzz的博客-CSDN博客_seata回滚不生效

     seata事务无法回滚情况_slivloon的博客-CSDN博客_seata事务不回滚原因

  • 相关阅读:
    springboot读取resources下文件方式
    【Android知识笔记】UI体系(五)
    三万字盘点Spring/Boot的那些常用扩展点
    开源大数据管理平台选型
    AtCoder ABC324G 启发式合并
    Shell:正则表达式
    图的遍历(BFS、DFS)
    11【门面设计模式】
    Code Bloaters-代码肿胀
    redis 重启后数据丢失
  • 原文地址:https://blog.csdn.net/MinggeQingchun/article/details/126153368