美文网首页
Spring Cloud Alibaba系列之-Seata分布式

Spring Cloud Alibaba系列之-Seata分布式

作者: AC编程 | 来源:发表于2021-05-17 09:19 被阅读0次

    一、什么是分布式事务问题?

    1.1 单体应用

    单体应用中,一个业务操作需要调用三个模块完成,此时数据的一致性由本地事务来保证。

    单体应用
    1.2 微服务应用

    随着业务需求的变化,单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用独立的数据源,业务操作需要调用三个服务来完成。此时每个服务内部的数据一致性由本地事务来保证,但是全局的数据一致性问题没法保证。

    微服务应用
    1.3 分布式事务

    在微服务架构中由于全局数据一致性没法保证产生的问题就是分布式事务问题。简单来说,一次业务操作需要操作多个数据源或需要进行远程调用,就会产生分布式事务问题。

    二、Seata

    Seata官网文档
    Seata官网下载地址
    Seata官网demo

    2.1 Seata历史

    2019 年 1 月,阿里巴巴中间件团队发起了开源项目 Fescar(Fast & EaSy Commit And Rollback),和社区一起共建开源分布式事务解决方案。Fescar 的愿景是让分布式事务的使用像本地事务的使用一样,简单和高效,并逐步解决开发者们遇到的分布式事务方面的所有难题。

    Fescar 开源后,蚂蚁金服加入 Fescar 社区参与共建,并在 Fescar 0.4.0 版本中贡献了 TCC(Try Confirm Cancel) 模式。为了打造更中立、更开放、生态更加丰富的分布式事务开源社区,经过社区核心成员的投票,大家决定对 Fescar 进行品牌升级,并更名为 Seata,意为:Simple Extensible Autonomous Transaction Architecture,是一套一站式分布式事务解决方案。

    2.2 Seata是什么?

    Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

    2.3 Seata术语

    TC (Transaction Coordinator) - 事务协调者
    维护全局和分支事务的状态,驱动全局事务提交或回滚。

    TM (Transaction Manager) - 事务管理器
    定义全局事务的范围:开始全局事务、提交或回滚全局事务。

    RM (Resource Manager) - 资源管理器
    管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

    2.4 Seata原理和设计
    2.4.1 定义一个分布式事务

    我们可以把一个分布式事务理解成一个包含了若干分支事务的全局事务,全局事务的职责是协调其下管辖的分支事务达成一致,要么一起成功提交,要么一起失败回滚。此外,通常分支事务本身就是一个满足ACID的本地事务。这是我们对分布式事务结构的基本认识,与 XA 是一致的。

    事务
    2.4.2 协议分布式事务处理过程的三个组件
    • Transaction Coordinator (TC): 事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚;

    • Transaction Manager (TM): 控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议;

    • Resource Manager (RM): 控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。

    分布式事务处理过程的三个组件
    2.4.3 一个典型的分布式事务过程
    • TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID;
    • XID 在微服务调用链路的上下文中传播;
    • RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖;
    • TM 向 TC 发起针对 XID 的全局提交或回滚决议;
    • TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。
    分布式事务过程

    三、Seata各事务模式

    3.1 Seata AT模式
    3.1.1 前提
    • 基于支持本地 ACID 事务的关系型数据库。
    • Java 应用,通过 JDBC 访问数据库。
    3.1.2 整体机制

    两阶段提交协议的演变:

    • 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
    • 二阶段:
      提交异步化,非常快速地完成。
      回滚通过一阶段的回滚日志进行反向补偿。
    3.1.3 写隔离
    • 一阶段本地事务提交前,需要确保先拿到 全局锁 。
    • 拿不到全局锁 ,不能提交本地事务。
    • 拿全局锁的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。

    以一个示例来说明:
    两个全局事务tx1和tx2,分别对a表的m字段进行更新操作,m 的初始值1000。

    tx1先开始,开启本地事务,拿到本地锁,更新操作 m = 1000 - 100 = 900。本地事务提交前,先拿到该记录的全局锁 ,本地提交释放本地锁。 tx2后开始,开启本地事务,拿到本地锁,更新操作 m = 900 - 100 = 800。本地事务提交前,尝试拿该记录的全局锁 ,tx1 全局提交前,该记录的全局锁被tx1持有,tx2需要重试等待全局锁 。

    如果 tx1 的二阶段全局回滚,则 tx1 需要重新获取该数据的本地锁,进行反向补偿的更新操作,实现分支的回滚。

    此时,如果 tx2 仍在等待该数据的 全局锁,同时持有本地锁,则 tx1 的分支回滚会失败。分支的回滚会一直重试,直到 tx2 的 全局锁 等锁超时,放弃 全局锁 并回滚本地事务释放本地锁,tx1 的分支回滚最终成功。

    因为整个过程 全局锁 在 tx1 结束前一直是被 tx1 持有的,所以不会发生 脏写 的问题。

    3.1.4 读隔离

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

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

    SELECT FOR UPDATE 语句的执行会申请 全局锁 ,如果 全局锁 被其他事务持有,则释放本地锁(回滚 SELECT FOR UPDATE 语句的本地执行)并重试。这个过程中,查询是被 block 住的,直到 全局锁 拿到,即读取的相关数据是 已提交 的,才返回。

    出于总体性能上的考虑,Seata 目前的方案并没有对所有 SELECT 语句都进行代理,仅针对 FOR UPDATE 的 SELECT 语句。

    3.2 Seata TCC模式

    一个分布式的全局事务,整体是 两阶段提交的模型。全局事务是由若干分支事务组成的,分支事务要满足两阶段提交的模型要求,即需要每个分支事务都具备自己的:

    • 一阶段 prepare 行为
    • 二阶段 commit 或 rollback 行为
    TCC模式

    根据两阶段行为模式的不同,我们将分支事务划分为 Automatic (Branch) Transaction Mode 和 TCC (Branch) Transaction Mode.

    AT 模式基于支持本地 ACID 事务的关系型数据库:

    • 一阶段 prepare 行为:在本地事务中,一并提交业务数据更新和相应回滚日志记录。
    • 二阶段 commit 行为:马上成功结束,自动异步批量清理回滚日志。
    • 二阶段 rollback 行为:通过回滚日志,自动生成补偿操作,完成数据回滚。

    相应的,TCC (Try Confirm Cancel)模式,不依赖于底层数据资源的事务支持:

    • 一阶段 prepare 行为:调用自定义的prepare逻辑。
    • 二阶段 commit 行为:调用自定义的commit逻辑。
    • 二阶段 rollback 行为:调用自定义的rollback逻辑。

    所谓 TCC 模式,是指支持把 自定义的分支事务纳入到全局事务的管理中。

    3.3 Seata Saga模式
    3.3.1 概述

    Saga模式是SEATA提供的长事务解决方案,在Saga模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务都由业务开发实现。

    3.3.2 适用场景
    • 业务流程长、业务流程多
    • 参与者包含其它公司或遗留系统服务,无法提供 TCC 模式要求的三个接口
    3.3.3 优势
    • 一阶段提交本地事务,无锁,高性能
    • 事件驱动架构,参与者可异步执行,高吞吐
    • 补偿服务易于实现
    3.3.4 缺点

    不保证隔离性

    3.4 Seata XA模式
    3.4.1 前提
    • 支持XA 事务的数据库。
    • Java 应用,通过 JDBC 访问数据库。
    3.4.2 整体机制

    在Seata 定义的分布式事务框架内,利用事务资源(数据库、消息服务等)对 XA 协议的支持,以XA 协议的机制来管理分支事务的一种 事务模式。

    XA
    • 执行阶段:
      1、可回滚:业务SQL 操作放在XA分支中进行,由资源对XA协议的支持来保证可回滚
      2、持久化:XA分支完成后,执行XA prepare,同样,由资源对XA协议的支持来保证持久化(即,之后任何意外都不会造成无法回滚的情况)

    • 完成阶段:
      1、分支提交:执行XA分支的 commit
      2、分支回滚:执行XA分支的 rollback

    四、Seata常见问题

    4.1 怎么使用Seata框架,来保证事务的隔离性?

    因Seata一阶段本地事务已提交,为防止其他事务脏读脏写需要加强隔离。

    脏读 select语句加for update,代理方法增加@GlobalLock+@Transactional或@GlobalTransaction

    脏写 必须使用@GlobalTransaction
    注:如果你查询的业务的接口没有GlobalTransactional 包裹,也就是这个方法上压根没有分布式事务的需求,这时你可以在方法上标注@GlobalLock+@Transactional 注解,并且在查询语句上加 for update。 如果你查询的接口在事务链路上外层有GlobalTransactional注解,那么你查询的语句只要加for update就行。设计这个注解的原因是在没有这个注解之前,需要查询分布式事务读已提交的数据,但业务本身不需要分布式事务。 若使用GlobalTransactional注解就会增加一些没用的额外的rpc开销比如begin 返回xid,提交事务等。GlobalLock简化了rpc过程,使其做到更高的性能。

    4.2 为什么mybatis没有返回自增ID?

    方案1:需要修改mybatis的配置: 在@Options(useGeneratedKeys = true, keyProperty = "id")或者在xml中指定useGeneratedKeys 和 keyProperty属性

    方案2:删除undo_log表的id字段

    4.3 AT 模式和 Spring @Transactional 注解连用时需要注意什么 ?

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

    五、项目[ac-mall-cloud]实现下单功能

    功能描述:用户下单,产品库存扣减[product-service]、用户余额扣减[user-servcie]、生成订单[order-service]

    5.1 数据库准备
    5.1.1 创建数据库

    为[user-servcie]、[product-service]、[order-service]三个微服务分别建立数据库,脚本如下

    1、[user-servcie]数据库脚本

    -- 创建数据库
    create database ac_mall_cloud_user_service_db  character set utf8mb4;
    
    --  创建用户
    create user 'user_service_u '@'%' identified by 'user_service_PWD_123';
    
    -- 授权用户
    grant all privileges on ac_mall_cloud_user_service_db.* to 'user_service_u'@'%';
    
    -- 刷新
    flush privileges;
    

    2、[product-servcie]数据库脚本

    -- 创建数据库
    create database ac_mall_cloud_product_service_db  character set utf8mb4;
    
    --  创建用户
    create user 'product_service_u '@'%' identified by 'product_service_PWD_123';
    
    -- 授权用户
    grant all privileges on ac_mall_cloud_product_service_db.* to 'product_service_u'@'%';
    
    -- 刷新
    flush privileges;
    

    3、[order-servcie]数据库脚本

    -- 创建数据库
    create database ac_mall_cloud_order_service_db  character set utf8mb4;
    
    --  创建用户
    create user 'order_service_u '@'%' identified by 'order_service_PWD_123';
    
    -- 授权用户
    grant all privileges on ac_mall_cloud_order_service_db.* to 'order_service_u'@'%';
    
    -- 刷新
    flush privileges;
    
    5.1.2 创建数据表

    1、在ac_mall_cloud_user_service_db库里创建user表

    CREATE TABLE `user` (
      `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
      `user_name` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '用户名',
      `balance` decimal(10,0) DEFAULT NULL COMMENT '余额',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
    
    INSERT INTO `user` (`id`, `user_name`, `balance`) VALUES ('1', 'AC', '1000');
    

    2、在ac_mall_cloud_product_service_db库里创建product表

    CREATE TABLE `product` (
      `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
      `product_name` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '产品名',
      `stock` int(11) DEFAULT NULL COMMENT '库存',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
    
    INSERT INTO `product` (`id`, `product_name`, `stock`) VALUES ('1', 'iPhone X', '50');
    

    3、在ac_mall_cloud_order_service_db库里创建order表

    CREATE TABLE `product_order` (
      `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
      `amount` decimal(10,0) DEFAULT NULL COMMENT '订单金额',
      `user_id` bigint(11) COMMENT '用户ID',
      `user_name` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '用户名',
      `product_id` bigint(11) COMMENT '产品ID',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
    
    5.2 下单功能代码实现
    5.2.1 maven依赖

    分别在[user-servcie]、[product-service]、[order-service]的pom.xml中添加mysql、mybaits-plus依赖

    <dependency>
        <groupId>com.baomidou</groupId>
         <artifactId>mybatis-plus-boot-starter</artifactId>
    </dependency>
    
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    
    5.2.2 配置文件

    分别在[user-servcie]、[product-service]、[order-service]的application.yml中添加mysql、mybaits-plus配置,以[user-servcie]为例,[product-service]、[order-service]同理

    spring:
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://47.105.146.74:3306/ac_mall_cloud_user_service_db?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone =GMT%2B8
        username: user_service_u
        password: user_service_PWD_123
    
    mybatis-plus:
      configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    
    
    5.3 代码
    5.3.1 [user-service]代码
    @Data
    public class User {
    
        private int id;
    
        private String userName;
    
        private double balance;
    }
    
    @Repository
    @Mapper
    public interface UserDao extends BaseMapper<User> {
    
    }
    
    @Service
    public class UserServiceImpl implements IUserService {
    
        @Autowired
        UserDao userDao;
    
        public User getUser(String id) {
            System.out.println("获取用户信息");
            return userDao.selectById(id);
        }
    
        /**
         * 下单扣减余额
         * @param userId
         * @param amount
         */
        @Transactional(rollbackFor = Exception.class)
        public void deductionBalance(int userId, double amount) {
            User user = userDao.selectById(userId);
            double newBalance=user.getBalance()-amount;
            user.setBalance(newBalance);
    
            userDao.updateById(user);
    
            System.out.println("下单扣减余额成功!");
        }
    }
    
    @RestController
    @RequestMapping(ModulePrePath.API+"/users")
    public class UserApi {
    
        @Autowired
        IUserService userService;
    
        /**
         * 获取用户信息
         * @param userId
         * @return
         */
        @GetMapping("/{userId}")
        public User getUser(@PathVariable String userId){
            return userService.getUser(userId);
        }
    
        /**
         * 下单扣减余额
         * @param userId
         * @param amount
         */
        @PutMapping("/deduction_balance/{userId}/{amount}")
        void deductionBalance(@PathVariable int userId,@PathVariable double amount){
            userService.deductionBalance(userId,amount);
        }
    }
    
    user-service
    5.3.2 [product-service]代码
    @Data
    public class Product {
    
        private int id;
    
        private String productName;
    
        private int stock;
    }
    
    @Repository
    @Mapper
    public interface ProductDao extends BaseMapper<Product> {
    
    }
    
    @Service
    public class ProductServiceImpl implements IProductService {
    
        @Autowired
        ProductDao productDao;
    
        /**
         * 下单扣减库存
         * @param productId
         * @param subCount
         */
        @Transactional(rollbackFor = Exception.class)
        public void subStock(int productId, int subCount) {
            Product product = productDao.selectById(productId);
            int newStock = product.getStock()-subCount;
            product.setStock(newStock);
    
            productDao.updateById(product);
    
            System.out.println("下单扣减库存成功!");
        }
    }
    
    @RestController
    @RequestMapping(ModulePrePath.API+"/products")
    public class ProductApi {
    
        @Autowired
        IProductService productService;
    
        /**
         * 更新销量
         * @param productId
         */
        @PutMapping("/{productId}")
        public void updateSales(@PathVariable String productId){
            System.out.println("商品:"+productId+"更新销量数");
        }
    
        /**
         * 下单扣减库存
         * @param productId
         * @param subCount
         */
        @PutMapping("/sub_stock/{productId}/{subCount}")
        public void subStock(@PathVariable int productId,@PathVariable int subCount){
            productService.subStock(productId,subCount);
        }
    }
    
    product-service
    5.3.3 [order-service]代码
    @Data
    public class ProductOrder {
    
        private int id;
    
        private double amount;
    
        private int userId;
    
        private String userName;
    
        private int productId;
    }
    
    @Repository
    @Mapper
    public interface OrderDao extends BaseMapper<ProductOrder> {
    
    }
    
    @Service
    public class OrderServiceImpl implements IOrderService {
    
        @Autowired
        OrderDao orderDao;
    
        @Autowired
        RestTemplate restTemplate;
    
        @Autowired
        UserServiceClient userServiceClient;
    
        @Autowired
        ProductServiceClient productServiceClient;
    
        //final static String USER_SERVICE_URL="http://127.0.0.1:8010/users/{userId}";
    
        final static String USER_SERVICE_URL="http://user-service/users/{userId}"; //用服务名来替换IP
    
    
        @Transactional(rollbackFor = Exception.class)
        public ProductOrder makeOrder(int productId, int userId) {
    
            /**
             * RestTemplate是java创造出来的,在java能够访问到网络资源的包是java.net.URLConnenction/Socket
             * RestTemplate是对URLConnenction的封装
             * apache--HttpClient 也是对URLConnenction/HttpURLConnenction的封装
             * oKHttp 也封装了URLConnenction
             * netty/rpc/grpc/thirt/tomcat
             */
    
            // 1、根据用户ID调用用户服务接口数据,查询用户的名字
            //UserDto userDto = restTemplate.getForObject(USER_SERVICE_URL,UserDto.class,userId);
    
            //换成OpenFeign
            UserDto userDto = userServiceClient.getUser(userId);
    
            String userName=userDto.getUserName();
    
            double amount = 100;
    
            // 2、生成订单
            ProductOrder productOrder = new ProductOrder();
            productOrder.setAmount(amount);
            productOrder.setUserId(userId);
            productOrder.setUserName(userName);
            productOrder.setProductId(productId);
    
            // 3、保存数据库
            orderDao.insert(productOrder);
    
            // 4、更新产品销量
            productServiceClient.updateSales(productId);
    
            // 5、下单减库存
            productServiceClient.subStock(productId,1);
    
            // 6、下单扣减用户余额
            userServiceClient.deductionBalance(userId,amount);
    
            return productOrder;
        }
    }
    
    @FeignClient(name="product-service",fallbackFactory = ProductFeignClientFallbackFactory.class)
    public interface ProductServiceClient {
    
        /**
         * 更新产品销量
         * @param productId
         */
        @PutMapping(ModulePrePath.API+"/products/{productId}")
        void updateSales(@PathVariable("productId") int productId);
    
        /**
         * 下单扣减库存
         * @param productId
         * @param subCount
         */
        @PutMapping(ModulePrePath.API+"/products/sub_stock/{productId}/{subCount}")
        void subStock(@PathVariable("productId") int productId,@PathVariable("subCount") int subCount);
    }
    
    @FeignClient("user-service")
    public interface UserServiceClient {
    
        /**
         * 获取用户信息
         * @param userId
         * @return
         */
        @GetMapping(ModulePrePath.API+"/users/{userId}")
        UserDto getUser(@PathVariable("userId") int userId);
    
        /**
         * 下单扣减余额
         * @param userId
         * @param amount
         */
        @PutMapping(ModulePrePath.API+"/users/deduction_balance/{userId}/{amount}")
        void deductionBalance(@PathVariable("userId") int userId,@PathVariable("amount") double amount);
    }
    
    5.4下单接口测试

    依次启动[auth-service]、[gateway-service]、[user-service]、[product-service]、[order-service]

    5.4.1 处理超时问题

    调用下单接口,有很大机率会出现超时异常,如下

    java.net.SocketTimeoutException: connect timed out
    
    image.png

    默认Feign 客户端只等待1秒钟,但是服务端处理需要超过1秒钟,导致Feign 客户端不想等待了,直接返回报错。为了避免这样的情况,我们需要设置Feign客户端的超时控制。OpenFeign 内与 Ribbon 整合了,支持负载均衡,它的超时控制也由最底层的Ribbon进行控制,在[order-service]的application.yml添加配置:

    #设置feign 客户端超时时间(openFeign默认支持ribbon)
    ribbon:
      #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
      ReadTimeout: 25000
      #指的是建立连接后从服务器读取到可用资源所用的时间
      ConnectTimeout: 25000
    

    [order-service]application.yml完整配置

    server:
      port: 8020
    
    spring:
      application:
        name: order-service
    
      cloud:
        nacos:
          discovery:
            server-addr: 47.105.146.74:8848
    
        sentinel:
          transport:
            dashboard: 127.0.0.1:8888
    
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://47.105.146.74:3306/ac_mall_cloud_order_service_db?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone =GMT%2B8
        username: order_service_u
        password: order_service_PWD_123
    
    mybatis-plus:
      configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    
    feign:
      sentinel:
        #为feign整合Sentinel
        enabled: true
    
    #设置feign 客户端超时时间(openFeign默认支持ribbon)
    ribbon:
      #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
      ReadTimeout: 25000
      #指的是建立连接后从服务器读取到可用资源所用的时间
      ConnectTimeout: 25000
    
    
    5.4.2 配置完超时时间后重启项目重新访问下单接口

    1、Postman结果


    下单接口操作成功

    2、数据库更新成功


    用户表 产品表 订单表
    5.4.3 制造异常,暴露分布式事务问题

    我们在[product-service]服务的下单扣减库存接口中,人为制造一个异常,让扣减库存失败

    人为制造一个异常

    我们再来调用下单接口,发现接口返回成功了。用户余额扣减了,订单记录也生成了,但库存没有扣减(异常导致失败),该操作跨越三个数据库,有两次远程调用,这就是事务失效的原因,需要用分布式事务的方式来解决该问题。

    六、Seata完成分布式事务

    6.1 Seata的安装与配置
    6.1.1 下载

    我们先从官网下载seata-server,https://github.com/seata/seata/releases,本文采用的是 seata 1.3.0

    seata-server
    6.1.2 Seata Server需要依赖的表

    由于我们准备使用了db模式存储事务日志,所以我们需要先创建一个seat-server数据库

    -- 创建数据库
    create database seata_db  character set utf8mb4;
    
    --  创建用户
    create user 'seata_u '@'%' identified by 'seata_PWD_123';
    
    -- 授权用户
    grant all privileges on seata_db.* to 'seata_u'@'%';
    
    -- 刷新
    flush privileges;
    

    在seat-server数据库创建如下三个表,用于seata服务, 0.0.9版本才有这个文件1.0.0版本后需要手动添加。
    表的地址 https://github.com/seata/seata/blob/develop/script/server/db/mysql.sql

    -- -------------------------------- The script used when storeMode is 'db' --------------------------------
    -- the table to store GlobalSession data
    CREATE TABLE IF NOT EXISTS `global_table`
    (
        `xid`                       VARCHAR(128) NOT NULL,
        `transaction_id`            BIGINT,
        `status`                    TINYINT      NOT NULL,
        `application_id`            VARCHAR(32),
        `transaction_service_group` VARCHAR(32),
        `transaction_name`          VARCHAR(128),
        `timeout`                   INT,
        `begin_time`                BIGINT,
        `application_data`          VARCHAR(2000),
        `gmt_create`                DATETIME,
        `gmt_modified`              DATETIME,
        PRIMARY KEY (`xid`),
        KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),
        KEY `idx_transaction_id` (`transaction_id`)
    ) ENGINE = InnoDB
      DEFAULT CHARSET = utf8;
    
    -- the table to store BranchSession data
    CREATE TABLE IF NOT EXISTS `branch_table`
    (
        `branch_id`         BIGINT       NOT NULL,
        `xid`               VARCHAR(128) NOT NULL,
        `transaction_id`    BIGINT,
        `resource_group_id` VARCHAR(32),
        `resource_id`       VARCHAR(256),
        `branch_type`       VARCHAR(8),
        `status`            TINYINT,
        `client_id`         VARCHAR(64),
        `application_data`  VARCHAR(2000),
        `gmt_create`        DATETIME(6),
        `gmt_modified`      DATETIME(6),
        PRIMARY KEY (`branch_id`),
        KEY `idx_xid` (`xid`)
    ) ENGINE = InnoDB
      DEFAULT CHARSET = utf8;
    
    -- the table to store lock data
    CREATE TABLE IF NOT EXISTS `lock_table`
    (
        `row_key`        VARCHAR(128) NOT NULL,
        `xid`            VARCHAR(128),
        `transaction_id` BIGINT,
        `branch_id`      BIGINT       NOT NULL,
        `resource_id`    VARCHAR(256),
        `table_name`     VARCHAR(32),
        `pk`             VARCHAR(36),
        `gmt_create`     DATETIME,
        `gmt_modified`   DATETIME,
        PRIMARY KEY (`row_key`),
        KEY `idx_branch_id` (`branch_id`)
    ) ENGINE = InnoDB
      DEFAULT CHARSET = utf8;
    

    AT模式下每个业务数据库需要创建undo_log表,用于seata记录分支的回滚信息
    表的地址 https://github.com/seata/seata/blob/1.3.0/script/client/at/db/mysql.sql

    -- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
    CREATE TABLE `undo_log` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `branch_id` bigint(20) NOT NULL,
      `xid` varchar(100) NOT NULL,
      `context` varchar(128) NOT NULL,
      `rollback_info` longblob NOT NULL,
      `log_status` int(11) NOT NULL,
      `log_created` datetime NOT NULL,
      `log_modified` datetime NOT NULL,
      `ext` varchar(100) DEFAULT NULL,
      PRIMARY KEY (`id`),
      UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
    
    6.1.3 修改file.conf、registry.conf

    解压seata-server安装包到指定目录,修改conf目录下的file.conf、registry.conf配置文件,主要修改自定义事务组名称,事务日志存储模式为db及数据库连接信息。

    注意:registry.conf里的group="SEATA_GROUP" 最好配置为SEATA_GROUP不要配置其它,否则服务会出现注册不到的问题,提示No available service

    file.conf配置 registry.conf配置一 registry.conf配置二

    file.conf完整配置

    ## transaction log store, only used in seata-server
    store {
      ## store mode: file、db、redis
      mode = "db"
    
      ## database store property
      db {
        ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
        datasource = "hikari"
        ## mysql/oracle/postgresql/h2/oceanbase etc.
        dbType = "mysql"
        driverClassName = "com.mysql.jdbc.Driver"
        url = "jdbc:mysql://47.105.146.74:3306/seata_db"
        user = "seata_u"
        password = "seata_PWD_123"
        minConn = 5
        maxConn = 30
        globalTable = "global_table"
        branchTable = "branch_table"
        lockTable = "lock_table"
        queryLimit = 100
        maxWait = 5000
      }
    
      ## file store property
      file {
        ## store location dir
        dir = "sessionStore"
        # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
        maxBranchSessionSize = 16384
        # globe session size , if exceeded throws exceptions
        maxGlobalSessionSize = 512
        # file buffer size , if exceeded allocate new buffer
        fileWriteBufferCacheSize = 16384
        # when recover batch read size
        sessionReloadReadSize = 100
        # async, sync
        flushDiskMode = async
      }
    
      ## redis store property
      redis {
        host = "127.0.0.1"
        port = "6379"
        password = ""
        database = "0"
        minConn = 1
        maxConn = 10
        queryLimit = 100
      }
    
    }
    

    registry.conf完整配置

    registry {
      # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
      type = "nacos"
    
      nacos {
        application = "seata-server"
        serverAddr = "47.105.146.74:8848"
        group = "SEATA_GROUP"
        namespace = "public"
        cluster = "default"
        username = "nacos"
        password = "nacos"
      }
    
      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"
        sessionTimeout = 6000
        connectTimeout = 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
      type = "nacos"
    
      nacos {
        serverAddr = "47.105.146.74:8848"
        namespace = "public"
        group = "SEATA_GROUP"
        username = "nacos"
        password = "nacos"
      }
      consul {
        serverAddr = "127.0.0.1:8500"
      }
      apollo {
        appId = "seata-server"
        apolloMeta = "http://192.168.1.204:8801"
        namespace = "application"
      }
      zk {
        serverAddr = "127.0.0.1:2181"
        sessionTimeout = 6000
        connectTimeout = 2000
        username = ""
        password = ""
      }
      etcd3 {
        serverAddr = "http://localhost:2379"
      }
      file {
        name = "file.conf"
      }
    }
    
    6.1.4 启动seata-server

    点击seata-server-1.3.0\seata\bin\seata-server.bat 启动seata-server

    补充:启动bat闪退问题
    首先我们在目录用cmd打开看错误信息

    闪退 执行bat 具体错误信息

    可以根据窗口中的具体错误信息针对性地进行解决。

    注意:mysql8 的驱动要改成com.mysql.cj.jdbc.Driver 而不是com.mysql.jdbc.Driver

    6.1.5 配置nacos-config.txt

    官网seata-server-0.9.0的conf目录下有该文件,后面的版本无该文件需要手动下载执行。

    文件地址 https://github.com/seata/seata/blob/1.3.0/script/config-center/config.txt
    修改以下参数配置

    store.mode=db
    store.db.datasource=hikari
    store.db.url=jdbc:mysql://47.105.146.74:3306/seata_db?useUnicode=true
    store.db.user=seata_u
    store.db.password=seata_PWD_123
    service.vgroupMapping.my_test_tx_group=default
    

    nacos-config.txt完整配置如下

    transport.type=TCP
    transport.server=NIO
    transport.heartbeat=true
    transport.enableClientBatchSendRequest=false
    transport.threadFactory.bossThreadPrefix=NettyBoss
    transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
    transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
    transport.threadFactory.shareBossWorker=false
    transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
    transport.threadFactory.clientSelectorThreadSize=1
    transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
    transport.threadFactory.bossThreadSize=1
    transport.threadFactory.workerThreadSize=default
    transport.shutdown.wait=3
    service.vgroupMapping.my_test_tx_group=default
    service.default.grouplist=127.0.0.1:8091
    service.enableDegrade=false
    service.disableGlobalTransaction=false
    client.rm.asyncCommitBufferLimit=10000
    client.rm.lock.retryInterval=10
    client.rm.lock.retryTimes=30
    client.rm.lock.retryPolicyBranchRollbackOnConflict=true
    client.rm.reportRetryCount=5
    client.rm.tableMetaCheckEnable=false
    client.rm.sqlParserType=druid
    client.rm.reportSuccessEnable=false
    client.rm.sagaBranchRegisterEnable=false
    client.tm.commitRetryCount=5
    client.tm.rollbackRetryCount=5
    client.tm.degradeCheck=false
    client.tm.degradeCheckAllowTimes=10
    client.tm.degradeCheckPeriod=2000
    store.mode=db
    store.file.dir=file_store/data
    store.file.maxBranchSessionSize=16384
    store.file.maxGlobalSessionSize=512
    store.file.fileWriteBufferCacheSize=16384
    store.file.flushDiskMode=async
    store.file.sessionReloadReadSize=100
    store.db.datasource=hikari
    store.db.dbType=mysql
    store.db.driverClassName=com.mysql.jdbc.Driver
    store.db.url=jdbc:mysql://47.105.146.74:3306/seata_db?useUnicode=true
    store.db.user=seata_u
    store.db.password=seata_PWD_123
    store.db.minConn=5
    store.db.maxConn=30
    store.db.globalTable=global_table
    store.db.branchTable=branch_table
    store.db.queryLimit=100
    store.db.lockTable=lock_table
    store.db.maxWait=5000
    store.redis.host=127.0.0.1
    store.redis.port=6379
    store.redis.maxConn=10
    store.redis.minConn=1
    store.redis.database=0
    store.redis.password=null
    store.redis.queryLimit=100
    server.recovery.committingRetryPeriod=1000
    server.recovery.asynCommittingRetryPeriod=1000
    server.recovery.rollbackingRetryPeriod=1000
    server.recovery.timeoutRetryPeriod=1000
    server.maxCommitRetryTimeout=-1
    server.maxRollbackRetryTimeout=-1
    server.rollbackRetryTimeoutUnlockEnable=false
    client.undo.dataValidation=true
    client.undo.logSerialization=jackson
    client.undo.onlyCareUpdateColumns=true
    server.undo.logSaveDays=7
    server.undo.logDeletePeriod=86400000
    client.undo.logTable=undo_log
    client.log.exceptionRate=100
    transport.serialization=seata
    transport.compressor=none
    metrics.enabled=false
    metrics.registryType=compact
    metrics.exporterList=prometheus
    metrics.exporterPrometheusPort=9898
    

    将nacos-config.txt 放到seata-server目录下


    nacos-config.txt目录位置
    6.1.6 执行nacos-config.sh脚本

    官网seata-server-0.9.0的conf目录下有该文件,后面的版本无该文件需要手动下载执行。

    脚本地址 https://github.com/seata/seata/blob/1.3.0/script/config-center/nacos/nacos-config.sh
    将nacos-config.sh脚本 放到seata-server目录下

    nacos-config.sh脚本放置目录

    nacos-config.sh内容

    #!/usr/bin/env bash
    # Copyright 1999-2019 Seata.io Group.
    #
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at、
    #
    #      http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.
    
    while getopts ":h:p:g:t:u:w:" opt
    do
      case $opt in
      h)
        host=$OPTARG
        ;;
      p)
        port=$OPTARG
        ;;
      g)
        group=$OPTARG
        ;;
      t)
        tenant=$OPTARG
        ;;
      u)
        username=$OPTARG
        ;;
      w)
        password=$OPTARG
        ;;
      ?)
        echo " USAGE OPTION: $0 [-h host] [-p port] [-g group] [-t tenant] [-u username] [-w password] "
        exit 1
        ;;
      esac
    done
    
    if [[ -z ${host} ]]; then
        host=localhost
    fi
    if [[ -z ${port} ]]; then
        port=8848
    fi
    if [[ -z ${group} ]]; then
        group="SEATA_GROUP"
    fi
    if [[ -z ${tenant} ]]; then
        tenant=""
    fi
    if [[ -z ${username} ]]; then
        username=""
    fi
    if [[ -z ${password} ]]; then
        password=""
    fi
    
    nacosAddr=$host:$port
    contentType="content-type:application/json;charset=UTF-8"
    
    echo "set nacosAddr=$nacosAddr"
    echo "set group=$group"
    
    failCount=0
    tempLog=$(mktemp -u)
    function addConfig() {
      curl -X POST -H "${contentType}" "http://$nacosAddr/nacos/v1/cs/configs?dataId=$1&group=$group&content=$2&tenant=$tenant&username=$username&password=$password" >"${tempLog}" 2>/dev/null
      if [[ -z $(cat "${tempLog}") ]]; then
        echo " Please check the cluster status. "
        exit 1
      fi
      if [[ $(cat "${tempLog}") =~ "true" ]]; then
        echo "Set $1=$2 successfully "
      else
        echo "Set $1=$2 failure "
        (( failCount++ ))
      fi
    }
    
    count=0
    for line in $(cat $(dirname "$PWD")/config.txt | sed s/[[:space:]]//g); do
      (( count++ ))
        key=${line%%=*}
        value=${line#*=}
        addConfig "${key}" "${value}"
    done
    
    echo "========================================================================="
    echo " Complete initialization parameters,  total-count:$count ,  failure-count:$failCount "
    echo "========================================================================="
    
    if [[ ${failCount} -eq 0 ]]; then
        echo " Init nacos config finished, please start seata-server. "
    else
        echo " init nacos config fail. "
    fi
    

    如果本地是windows,使用git工具git bash执行nacos-config.sh脚本:

    sh nacos-config.sh -h 47.105.146.74 -p 8848
    
    git bash执行nacos-config.sh脚本

    执行完成后nacos会新增seata配置。

    nacos中seata配置
    6.2 spring-cloud、spring-boot、alibaba-cloud版本升级

    spring-cloud-alibaba 版本说明

    spring-cloud-alibaba 版本说明

    为了使用Seata 1.3.0,我们需要先将[ac-mall-cloud]的spring-cloud、spring-boot、alibaba-cloud版本对应升级,对应版本如下

    <properties>
            <java.version>1.8</java.version>
            <spring-cloud.version>Hoxton.SR8</spring-cloud.version>
            <mysql.version>8.0.17</mysql.version>
            <mybatis.plus.version>3.2.0</mybatis.plus.version>
            <druid.version>1.1.10</druid.version>
            <boot.version>2.3.2.RELEASE</boot.version>
            <alibaba.cloud.version>2.2.5.RELEASE</alibaba.cloud.version>
            <alibaba.seata.version>2.2.0.RELEASE</alibaba.seata.version>
            <lombok.version>1.18.10</lombok.version>
    </properties>
    
    6.3 [ac-mall-cloud]代码
    6.3.1 父级工程配置

    在父级工程 [ac-mall-cloud] pom.xml中引入Seata依赖

    <dependency>
           <groupId>com.alibaba.cloud</groupId>
           <artifactId>spring-cloud-alibaba-seata</artifactId>
           <version>${alibaba.seata.version}</version>
           <exclusions>
                 <exclusion>
                       <groupId>io.seata</groupId>
                       <artifactId>seata-all</artifactId>
                 </exclusion>
                 <exclusion>
                       <groupId>io.seata</groupId>
                       <artifactId>seata-spring-boot-starter</artifactId>
                  </exclusion>
            </exclusions>
    </dependency>
    
    <dependency>
         <groupId>io.seata</groupId>
         <artifactId>seata-spring-boot-starter</artifactId>
         <version>1.3.0</version>
    </dependency>
    

    [ac-mall-cloud] pom.xml完整配置如下

    <?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>ac-mall-cloud</artifactId>
        <version>1.0-SNAPSHOT</version>
        <modules>
            <module>user-service</module>
            <module>product-service</module>
            <module>order-service</module>
            <module>gateway-service</module>
            <module>auth-service</module>
        </modules>
    
        <packaging>pom</packaging>
    
        <properties>
            <java.version>1.8</java.version>
            <spring-cloud.version>Hoxton.SR8</spring-cloud.version>
            <mysql.version>8.0.17</mysql.version>
            <mybatis.plus.version>3.2.0</mybatis.plus.version>
            <druid.version>1.1.10</druid.version>
            <boot.version>2.3.2.RELEASE</boot.version>
            <alibaba.cloud.version>2.2.5.RELEASE</alibaba.cloud.version>
            <alibaba.seata.version>2.2.0.RELEASE</alibaba.seata.version>
            <lombok.version>1.18.10</lombok.version>
        </properties>
    
        <!-- 管理子类所有的jar包的版本,这样的目的是方便去统一升级和维护 -->
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-parent</artifactId>
                    <version>${boot.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>
    
                <dependency>
                    <groupId>com.alibaba.cloud</groupId>
                    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                    <version>${alibaba.cloud.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
    
                <dependency>
                    <groupId>com.alibaba.cloud</groupId>
                    <artifactId>spring-cloud-alibaba-seata</artifactId>
                    <version>${alibaba.seata.version}</version>
                    <exclusions>
                        <exclusion>
                            <groupId>io.seata</groupId>
                            <artifactId>seata-all</artifactId>
                        </exclusion>
                        <exclusion>
                            <groupId>io.seata</groupId>
                            <artifactId>seata-spring-boot-starter</artifactId>
                        </exclusion>
                    </exclusions>
                </dependency>
    
                <dependency>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-spring-boot-starter</artifactId>
                    <version>1.3.0</version>
                </dependency>
    
                <dependency>
                    <groupId>com.alibaba.cloud</groupId>
                    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
                    <version>${alibaba.cloud.version}</version>
                </dependency>
    
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-starter-openfeign</artifactId>
                    <version>${alibaba.cloud.version}</version>
                </dependency>
    
                <dependency>
                    <groupId>com.alibaba.cloud</groupId>
                    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
                    <version>${alibaba.cloud.version}</version>
                </dependency>
    
                <dependency>
                    <groupId>com.baomidou</groupId>
                    <artifactId>mybatis-plus-boot-starter</artifactId>
                    <version>${mybatis.plus.version}</version>
                </dependency>
    
                <dependency>
                    <groupId>com.alibaba</groupId>
                    <artifactId>druid-spring-boot-starter</artifactId>
                    <version>${druid.version}</version>
                </dependency>
    
                <dependency>
                    <groupId>mysql</groupId>
                    <artifactId>mysql-connector-java</artifactId>
                    <version>${mysql.version}</version>
                </dependency>
    
                <dependency>
                    <groupId>org.projectlombok</groupId>
                    <artifactId>lombok</artifactId>
                    <version>${lombok.version}</version>
                    <scope>provided</scope>
                </dependency>
    
            </dependencies>
    
        </dependencyManagement>
    
        <!-- 所有的子工程都会自动加入下面的依赖  -->
        <dependencies>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.10</version>
                <scope>provided</scope>
            </dependency>
        </dependencies>
    
        <!-- SpringBoot 工程编译打包的插件,放在父pom中就直接给所有子工程继承 -->
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
    
    6.3.2 [user-service]、[product-service]、[order-service]pom.xml中引入Seata依赖

    以 [user-service]pom.xml 为例,[product-service]、[order-service]pom.xml同理

    <dependency>
          <groupId>com.alibaba.cloud</groupId>
          <artifactId>spring-cloud-alibaba-seata</artifactId>
          <exclusions>
                <exclusion>
                <groupId>io.seata</groupId>
                <artifactId>seata-spring-boot-starter</artifactId>
                </exclusion>
         </exclusions>
    </dependency>
    
    <dependency>
          <groupId>io.seata</groupId>
          <artifactId>seata-spring-boot-starter</artifactId>
    </dependency>
    

    [user-service]pom.xml 完整配置如下

    <?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">
        <parent>
            <artifactId>ac-mall-cloud</artifactId>
            <groupId>org.example</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>org.ac</groupId>
        <artifactId>user-service</artifactId>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            </dependency>
    
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
            </dependency>
    
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>
    
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-seata</artifactId>
                <exclusions>
                    <exclusion>
                        <groupId>io.seata</groupId>
                        <artifactId>seata-spring-boot-starter</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
    
            <dependency>
                <groupId>io.seata</groupId>
                <artifactId>seata-spring-boot-starter</artifactId>
            </dependency>
    
        </dependencies>
    
    </project>
    
    6.3.3 [user-service]、[product-service]、[order-service]application.yml 添加 seata配置

    以 [user-service]application.yml为例,[product-service]、[order-service]application.yml同理

    seata:
      enabled: true
      application-id: user-service # 同spring.application.name 配置的项目名称
      tx-service-group: my_test_tx_group
      enable-auto-data-source-proxy: true    #开启数据库代理
      config:
        type: nacos
        nacos:
          namespace:
          server-addr: 47.105.146.74:8848
          group: SEATA_GROUP
          username: nacos
          password: nacos
      registry:
        type: nacos
        nacos:
          application: seata-server
          server-addr: 47.105.146.74:8848
          namespace: public
          username: nacos
          password: nacos
    

    [user-service]application.yml完整配置如下

    server:
      port: 8010
    
    spring:
      application:
        name: user-service
    
      cloud:
        nacos:
          discovery:
            server-addr: 47.105.146.74:8848
    
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://47.105.146.74:3306/ac_mall_cloud_user_service_db?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone =GMT%2B8
        username: user_service_u
        password: user_service_PWD_123
    
    
        #hikari数据库连接池
        hikari:
          pool-name: YH_HikariCP
          minimum-idle: 10 #最小空闲连接数量
          idle-timeout: 600000 #空闲连接存活最大时间,默认600000(10分钟)
          maximum-pool-size: 100 #连接池最大连接数,默认是10
          auto-commit: true  #此属性控制从池返回的连接的默认自动提交行为,默认值:true
          max-lifetime: 1800000 #此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认1800000即30分钟
          connection-timeout: 30000 #数据库连接超时时间,默认30秒,即30000
          connection-test-query: SELECT 1
    
    mybatis-plus:
      configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    
    seata:
      enabled: true
      application-id: user-service # 同spring.application.name 配置的项目名称
      tx-service-group: my_test_tx_group
      enable-auto-data-source-proxy: true    #开启数据库代理
      config:
        type: nacos
        nacos:
          namespace:
          server-addr: 47.105.146.74:8848
          group: SEATA_GROUP
          username: nacos
          password: nacos
      registry:
        type: nacos
        nacos:
          application: seata-server
          server-addr: 47.105.146.74:8848
          namespace: public
          username: nacos
          password: nacos
    
    
    6.3.4 先取消降级接口(巨坑)

    在[order-service]中,先将产品的降级接口注释以免影响事务回滚。在做本案例时由于没有先注释该降级接口代码,导致分布式事务一直没有得到回滚,经过定位和分析才定位到这个问题。

    注释降级服务接口
    6.3.4 加@GlobalTransactional 注解

    在[order-service]下单接口上加分布式事务@GlobalTransactional 注解

    下单接口

    下单接口完整代码如下

    package com.ac.order.service.impl;
    
    import com.ac.order.dao.OrderDao;
    import com.ac.order.dto.UserDto;
    import com.ac.order.entity.ProductOrder;
    import com.ac.order.feign.ProductServiceClient;
    import com.ac.order.feign.UserServiceClient;
    import com.ac.order.service.IOrderService;
    import io.seata.core.context.RootContext;
    import io.seata.spring.annotation.GlobalTransactional;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    import org.springframework.web.client.RestTemplate;
    
    /**
     * @author Alan Chen
     * @description
     * @date 2020/10/15
     */
    @Service
    public class OrderServiceImpl implements IOrderService {
    
        @Autowired
        OrderDao orderDao;
    
        @Autowired
        RestTemplate restTemplate;
    
        @Autowired
        UserServiceClient userServiceClient;
    
        @Autowired
        ProductServiceClient productServiceClient;
    
        //final static String USER_SERVICE_URL="http://127.0.0.1:8010/users/{userId}";
    
        final static String USER_SERVICE_URL="http://user-service/users/{userId}"; //用服务名来替换IP
    
    
        @GlobalTransactional(rollbackFor = Exception.class)
        @Transactional(rollbackFor = Exception.class)
        public ProductOrder makeOrder(int productId, int userId) {
    
            System.out.println("开始分支事务,XID = " + RootContext.getXID());
    
            /**
             * RestTemplate是java创造出来的,在java能够访问到网络资源的包是java.net.URLConnenction/Socket
             * RestTemplate是对URLConnenction的封装
             * apache--HttpClient 也是对URLConnenction/HttpURLConnenction的封装
             * oKHttp 也封装了URLConnenction
             * netty/rpc/grpc/thirt/tomcat
             */
    
            // 1、根据用户ID调用用户服务接口数据,查询用户的名字
            //UserDto userDto = restTemplate.getForObject(USER_SERVICE_URL,UserDto.class,userId);
    
            //换成OpenFeign
            UserDto userDto = userServiceClient.getUser(userId);
    
            String userName=userDto.getUserName();
    
            double amount = 100;
    
            // 2、生成订单
            ProductOrder productOrder = new ProductOrder();
            productOrder.setAmount(amount);
            productOrder.setUserId(userId);
            productOrder.setUserName(userName);
            productOrder.setProductId(productId);
    
            // 3、保存数据库
            orderDao.insert(productOrder);
    
            // 4、更新产品销量
            productServiceClient.updateSales(productId);
    
            // 5、下单减库存
            productServiceClient.subStock(productId,1);
    
            // 6、下单扣减用户余额
            userServiceClient.deductionBalance(userId,amount);
    
            return productOrder;
        }
    }
    

    即使加了 @GlobalTransactional 分布式注解,本地数据库事务注解 @Transactional(rollbackFor = Exception.class) 仍然不能省略, 否则依然会生成新的订单数据。

    6.3.5 测试分布式事务效果

    重启各服务,重新调用下单接口

    [user-service]控制台

    2021-04-19 15:37:46.950  INFO 10316 --- [nio-8010-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
    2021-04-19 15:37:46.950  INFO 10316 --- [nio-8010-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
    2021-04-19 15:37:46.955  INFO 10316 --- [nio-8010-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 5 ms
    获取用户信息
    Creating a new SqlSession
    SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7024bfae] was not registered for synchronization because synchronization is not active
    JDBC Connection [io.seata.rm.datasource.ConnectionProxy@62a2a35d] will not be managed by Spring
    ==>  Preparing: SELECT id,balance,user_name FROM user WHERE id=? 
    ==> Parameters: 1(String)
    <==    Columns: id, balance, user_name
    <==        Row: 1, 1000, AC
    <==      Total: 1
    Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7024bfae]
    2021-04-19 15:37:47.448  WARN 10316 --- [nio-8010-exec-1] c.a.c.seata.web.SeataHandlerInterceptor  : xid in change during RPC from 192.168.124.33:8091:127434137383604224 to null
    

    [product-service]控制台

    开始分支事务,XID = 192.168.124.33:8091:127434137383604224
    Creating a new SqlSession
    Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@16b2b486]
    JDBC Connection [io.seata.rm.datasource.ConnectionProxy@16f2857d] will be managed by Spring
    ==>  Preparing: SELECT id,stock,product_name FROM product WHERE id=? 
    ==> Parameters: 1(Integer)
    <==    Columns: id, stock, product_name
    <==        Row: 1, 50, iPhone X
    <==      Total: 1
    Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@16b2b486]
    Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@16b2b486] from current transaction
    ==>  Preparing: UPDATE product SET stock=?, product_name=? WHERE id=? 
    ==> Parameters: 49(Integer), iPhone X(String), 1(Integer)
    <==    Updates: 1
    Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@16b2b486]
    Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@16b2b486]
    Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@16b2b486]
    2021-04-19 15:37:49.679  WARN 21328 --- [nio-8030-exec-1] c.a.c.seata.web.SeataHandlerInterceptor  : xid in change during RPC from 192.168.124.33:8091:127434137383604224 to null
    2021-04-19 15:37:49.703 ERROR 21328 --- [nio-8030-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.ArithmeticException: / by zero] with root cause
    
    java.lang.ArithmeticException: / by zero
        at com.ac.product.service.impl.ProductServiceImpl.subStock(ProductServiceImpl.java:38) ~[classes/:na]
        at com.ac.product.service.impl.ProductServiceImpl$$FastClassBySpringCGLIB$$e5fb083d.invoke(<generated>) ~[classes/:na]
        at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:771) ~[spring-aop-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749) ~[spring-aop-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:367) ~[spring-tx-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:118) ~[spring-tx-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749) ~[spring-aop-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:691) ~[spring-aop-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at com.ac.product.service.impl.ProductServiceImpl$$EnhancerBySpringCGLIB$$6977031b.subStock(<generated>) ~[classes/:na]
        at com.ac.product.api.ProductApi.subStock(ProductApi.java:36) ~[classes/:na]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_131]
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_131]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_131]
        at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_131]
        at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) ~[spring-web-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:878) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:792) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.web.servlet.FrameworkServlet.doPut(FrameworkServlet.java:920) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:655) ~[tomcat-embed-core-9.0.37.jar:4.0.FR]
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:733) ~[tomcat-embed-core-9.0.37.jar:4.0.FR]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.37.jar:9.0.37]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541) [tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) [tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) [tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373) [tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) [tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868) [tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1589) [tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.37.jar:9.0.37]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_131]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_131]
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.37.jar:9.0.37]
        at java.lang.Thread.run(Thread.java:748) [na:1.8.0_131]
    
    2021-04-19 15:37:49.755  WARN 21328 --- [nio-8030-exec-1] c.a.c.seata.web.SeataHandlerInterceptor  : xid in change during RPC from 192.168.124.33:8091:127434137383604224 to null
    

    [order-service]控制台

    Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@13d892bf]
    JDBC Connection [io.seata.rm.datasource.ConnectionProxy@5ca7c9fe] will be managed by Spring
    ==>  Preparing: INSERT INTO product_order ( id, amount, product_id, user_name, user_id ) VALUES ( ?, ?, ?, ?, ? ) 
    2021-04-19 15:37:47.793  INFO 3656 --- [erListUpdater-0] c.netflix.config.ChainedDynamicProperty  : Flipping property: user-service.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
    ==> Parameters: 0(Integer), 100.0(Double), 1(Integer), AC(String), 1(Integer)
    <==    Updates: 1
    Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@13d892bf]
    2021-04-19 15:37:48.259  INFO 3656 --- [nio-8020-exec-1] c.netflix.config.ChainedDynamicProperty  : Flipping property: product-service.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
    2021-04-19 15:37:48.261  INFO 3656 --- [nio-8020-exec-1] c.netflix.loadbalancer.BaseLoadBalancer  : Client: product-service instantiated a LoadBalancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=product-service,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:null
    2021-04-19 15:37:48.263  INFO 3656 --- [nio-8020-exec-1] c.n.l.DynamicServerListLoadBalancer      : Using serverListUpdater PollingServerListUpdater
    2021-04-19 15:37:48.315  INFO 3656 --- [nio-8020-exec-1] c.netflix.config.ChainedDynamicProperty  : Flipping property: product-service.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
    2021-04-19 15:37:48.317  INFO 3656 --- [nio-8020-exec-1] c.n.l.DynamicServerListLoadBalancer      : DynamicServerListLoadBalancer for client product-service initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=product-service,current list of Servers=[192.168.124.33:8030],Load balancer stats=Zone stats: {unknown=[Zone:unknown;  Instance count:1;   Active connections count: 0;    Circuit breaker tripped count: 0;   Active connections per server: 0.0;]
    },Server stats: [[Server:192.168.124.33:8030;   Zone:UNKNOWN;   Total Requests:0;   Successive connection failure:0;    Total blackout seconds:0;   Last connection made:Thu Jan 01 08:00:00 CST 1970;  First connection made: Thu Jan 01 08:00:00 CST 1970;    Active Connections:0;   total failure count in last (1000) msecs:0; average resp time:0.0;  90 percentile resp time:0.0;    95 percentile resp time:0.0;    min resp time:0.0;  max resp time:0.0;  stddev resp time:0.0]
    ]}ServerList:com.alibaba.cloud.nacos.ribbon.NacosServerList@458c3ac8
    2021-04-19 15:37:49.265  INFO 3656 --- [erListUpdater-0] c.netflix.config.ChainedDynamicProperty  : Flipping property: product-service.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
    Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@13d892bf]
    Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@13d892bf]
    2021-04-19 15:37:49.931  INFO 3656 --- [nio-8020-exec-1] i.seata.tm.api.DefaultGlobalTransaction  : [192.168.124.33:8091:127434137383604224] rollback status: Rollbacked
    2021-04-19 15:37:49.962 ERROR 3656 --- [nio-8020-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is feign.FeignException$InternalServerError: [500] during [PUT] to [http://product-service/api/products/sub_stock/1/1] [ProductServiceClient#subStock(int,int)]: [{"timestamp":"2021-04-19T07:37:49.731+00:00","status":500,"error":"Internal Server Error","message":"","path":"/api/products/sub_stock/1/1"}]] with root cause
    
    feign.FeignException$InternalServerError: [500] during [PUT] to [http://product-service/api/products/sub_stock/1/1] [ProductServiceClient#subStock(int,int)]: [{"timestamp":"2021-04-19T07:37:49.731+00:00","status":500,"error":"Internal Server Error","message":"","path":"/api/products/sub_stock/1/1"}]
        at feign.FeignException.serverErrorStatus(FeignException.java:231) ~[feign-core-10.10.1.jar:na]
        at feign.FeignException.errorStatus(FeignException.java:180) ~[feign-core-10.10.1.jar:na]
        at feign.FeignException.errorStatus(FeignException.java:169) ~[feign-core-10.10.1.jar:na]
        at feign.codec.ErrorDecoder$Default.decode(ErrorDecoder.java:92) ~[feign-core-10.10.1.jar:na]
        at feign.AsyncResponseHandler.handleResponse(AsyncResponseHandler.java:96) ~[feign-core-10.10.1.jar:na]
        at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:138) ~[feign-core-10.10.1.jar:na]
        at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:89) ~[feign-core-10.10.1.jar:na]
        at com.alibaba.cloud.sentinel.feign.SentinelInvocationHandler.invoke(SentinelInvocationHandler.java:107) ~[spring-cloud-starter-alibaba-sentinel-2.2.5.RELEASE.jar:2.2.5.RELEASE]
        at com.sun.proxy.$Proxy95.subStock(Unknown Source) ~[na:na]
        at com.ac.order.service.impl.OrderServiceImpl.makeOrder(OrderServiceImpl.java:79) ~[classes/:na]
        at com.ac.order.service.impl.OrderServiceImpl$$FastClassBySpringCGLIB$$1db3649d.invoke(<generated>) ~[classes/:na]
        at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:771) ~[spring-aop-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749) ~[spring-aop-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:367) ~[spring-tx-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:118) ~[spring-tx-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749) ~[spring-aop-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at io.seata.spring.annotation.GlobalTransactionalInterceptor$1.execute(GlobalTransactionalInterceptor.java:150) ~[seata-all-1.3.0.jar:1.3.0]
        at io.seata.tm.api.TransactionalTemplate.execute(TransactionalTemplate.java:104) ~[seata-all-1.3.0.jar:1.3.0]
        at io.seata.spring.annotation.GlobalTransactionalInterceptor.handleGlobalTransaction(GlobalTransactionalInterceptor.java:147) ~[seata-all-1.3.0.jar:1.3.0]
        at io.seata.spring.annotation.GlobalTransactionalInterceptor.invoke(GlobalTransactionalInterceptor.java:122) ~[seata-all-1.3.0.jar:1.3.0]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749) ~[spring-aop-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:691) ~[spring-aop-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at com.ac.order.service.impl.OrderServiceImpl$$EnhancerBySpringCGLIB$$53288503.makeOrder(<generated>) ~[classes/:na]
        at com.ac.order.controller.OrderController.saveOrder(OrderController.java:25) ~[classes/:na]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_131]
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_131]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_131]
        at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_131]
        at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) ~[spring-web-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:878) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:792) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:626) ~[tomcat-embed-core-9.0.37.jar:4.0.FR]
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:733) ~[tomcat-embed-core-9.0.37.jar:4.0.FR]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.37.jar:9.0.37]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541) [tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) [tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) [tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373) [tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) [tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868) [tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1589) [tomcat-embed-core-9.0.37.jar:9.0.37]
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.37.jar:9.0.37]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_131]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_131]
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.37.jar:9.0.37]
        at java.lang.Thread.run(Thread.java:748) [na:1.8.0_131]
    
    

    七、附录

    项目源码地址

    相关文章

      网友评论

          本文标题:Spring Cloud Alibaba系列之-Seata分布式

          本文链接:https://www.haomeiwen.com/subject/chfqjltx.html