0
点赞
收藏
分享

微信扫一扫

Spring Cloud Alibaba(6.3 Seata——纯 Spring Boot AT 模式 + HttpClient 远程调用)

树下的老石头 2021-09-21 阅读 68

一、前言

Apache HttpClient 来实现 HTTP 远程调用每个 Spring Boot 应用提供的 Restful API 接口。整体如下图所示:

Seata 提供了 seata-http 项目,对 Apache HttpClient 进行集成。实现原理是:

  • 服务消费者,使用 Seata 封装的 AbstractHttpExecutor 执行器,在使用HttpClient 发起 HTTP 调用时,将 Seata 全局事务 XID 通过 Header 传递。
  • 服务提供者,使用 Seata 提供的 SpringMVC TransactionPropagationIntercepter 拦截器,将 Header 中的 Seata 全局事务 XID 解析出来,设置到 Seata 上下文 中。

如此,我们便实现了多个 Spring Boot 应用的 Seata 全局事务的传播

本文的源代码可从Gitee下载.

二、创建Module

该项目包含三个 Spring Boot模块。


三、初始化数据库

使用 data.sql脚本,创建 seata_orderseata_storageseata_amount 三个库。脚本内容如下:

# Order
DROP DATABASE IF EXISTS seata_order;
CREATE DATABASE seata_order;

CREATE TABLE seata_order.orders
(
    id               INT(11) NOT NULL AUTO_INCREMENT,
    user_id          INT(11)        DEFAULT NULL,
    product_id       INT(11)        DEFAULT NULL,
    pay_amount       DECIMAL(10, 0) DEFAULT NULL,
    add_time         DATETIME       DEFAULT CURRENT_TIMESTAMP,
    last_update_time DATETIME       DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (id)
) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8;

CREATE TABLE seata_order.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,
    PRIMARY KEY (id),
    UNIQUE KEY ux_undo_log (xid, branch_id)
) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8;

# Storage
DROP DATABASE IF EXISTS seata_storage;
CREATE DATABASE seata_storage;

CREATE TABLE seata_storage.product
(
    id               INT(11) NOT NULL AUTO_INCREMENT,
    stock            INT(11)  DEFAULT NULL,
    last_update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (id)
) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8;
INSERT INTO seata_storage.product (id, stock) VALUES (1, 10); # 插入一条产品的库存

CREATE TABLE seata_storage.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,
    PRIMARY KEY (id),
    UNIQUE KEY ux_undo_log (xid, branch_id)
) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8;

# Amount
DROP DATABASE IF EXISTS seata_amount;
CREATE DATABASE seata_amount;

CREATE TABLE seata_amount.account
(
    id               INT(11) NOT NULL AUTO_INCREMENT,
    balance          DOUBLE   DEFAULT NULL,
    last_update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (id)
) ENGINE = InnoDB AUTO_INCREMENT = 1  DEFAULT CHARSET = utf8;

CREATE TABLE seata_amount.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,
    PRIMARY KEY (id),
    UNIQUE KEY ux_undo_log (xid, branch_id)
) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8;
INSERT INTO seata_amount.account (id, balance) VALUES (1, 1);

其中,每个库中的 undo_log 表,是 Seata AT 模式必须创建的表,主要用于分支事务的回滚。
另外,考虑到测试方便,我们插入了一条 id = 1account 记录,和一条 id = 1product 记录。

四、 订单服务

作为订单服务。它主要提供 /order/create 接口,实现下单逻辑。

4.1 引入依赖

创建 [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>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-parent</artifactId>
 <version>2.2.2.RELEASE</version>
 <relativePath/> <!-- lookup parent from repository -->
 </parent>
 <modelVersion>4.0.0</modelVersion>

 <artifactId>lab-52-seata-at-httpclient-demo-account-service</artifactId>

 <dependencies>
 <!-- 实现对 Spring MVC 的自动化配置 -->
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
 </dependency>

 <!-- 实现对数据库连接池的自动化配置 -->
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-jdbc</artifactId>
 </dependency>
 <dependency> <!-- 本示例,我们使用 MySQL -->
 <groupId>mysql</groupId>
 <artifactId>mysql-connector-java</artifactId>
 <version>5.1.48</version>
 </dependency>

 <!-- 实现对 MyBatis 的自动化配置 -->
 <dependency>
 <groupId>org.mybatis.spring.boot</groupId>
 <artifactId>mybatis-spring-boot-starter</artifactId>
 <version>2.1.2</version>
 </dependency>

 <!-- 实现对 Seata 的自动化配置 -->
 <dependency>
 <groupId>io.seata</groupId>
 <artifactId>seata-spring-boot-starter</artifactId>
 <version>1.1.0</version>
 </dependency>
 <!-- 实现 Seata 对 HttpClient 的集成支持  -->
 <dependency>
 <groupId>io.seata</groupId>
 <artifactId>seata-http</artifactId>
 <version>1.1.0</version>
 </dependency>

 <!-- Apache HttpClient 依赖 -->
 <dependency>
 <groupId>org.apache.httpcomponents</groupId>
 <artifactId>httpclient</artifactId>
 <version>4.5.8</version>
 </dependency>
 </dependencies>

</project>

① 引入 seata-spring-boot-starter 依赖,实现对 Seata 的自动配置。
② 引入 seata-http 依赖,实现 Seata 对 HttpClient 的集成支持。

4.2 配置文件

创建 [application.yaml]配置文件,添加相关的配置项。内容如下:

server:
 port: 8081 # 端口

spring:
 application:
 name: order-service

 datasource:
 url: jdbc:mysql://127.0.0.1:3306/seata_order?useSSL=false&useUnicode=true&characterEncoding=UTF-8
 driver-class-name: com.mysql.jdbc.Driver
 username: root
 password:

# Seata 配置项,对应 SeataProperties 类
seata:
 application-id: ${spring.application.name} # Seata 应用编号,默认为 ${spring.application.name}
 tx-service-group: ${spring.application.name}-group # Seata 事务组编号,用于 TC 集群名
 # 服务配置项,对应 ServiceProperties 类
 service:
 # 虚拟组和分组的映射
 vgroup-mapping:
 order-service-group: default
 # 分组和 Seata 服务的映射
 grouplist:
 default: 127.0.0.1:8091

spring.datasource 配置项,设置连接 seata_order 库。
seata 配置项,设置 Seata 的配置项目,对应 SeataProperties 类。

  • application-id 配置项,对应 Seata 应用编号,默认为 ${spring.application.name}。实际上,可以不进行设置。
  • tx-service-group 配置项,Seata 事务组编号,用于 TC 集群名。

seata.service 配置项,Seata 服务配置项,对应 ServiceProperties 类。它主要用于 Seata 在事务分组的特殊设计,可见《Seata 文档 —— 事务分组专题》。如果不能理解,可以见如下图:

简单来说,就是多了一层虚拟映射。这里,我们直接设置 TC Server 的地址,为 127.0.0.1:8091

4.3 OrderController

创建 [OrderController]类,提供 order/create 下单 HTTP API。代码如下:


/**
 * @ClassName: OrderController
 * @Description: 下单操作
 * @author: 郭秀志 jbcode@126.com
 * @date: 2020/6/19 15:59
 * @Copyright:
 */
@RestController
@RequestMapping("/order")
public class OrderController {

    private Logger logger = LoggerFactory.getLogger(OrderController.class);

    @Autowired
    private OrderService orderService;

    @PostMapping("/create")
    public Integer createOrder(@RequestParam("userId") Long userId,
                               @RequestParam("productId") Long productId,
                               @RequestParam("price") Integer price) throws Exception {
        logger.info("[createOrder] 收到下单请求,用户:{}, 商品:{}, 价格:{}", userId, productId, price);
        return orderService.createOrder(userId, productId, price);
    }

}

  • 该 API 中,会调用 OrderService 进行下单。

4.4 OrderService

创建 [OrderService]接口,定义了创建订单的方法。代码如下:


/**
 * @ClassName: OrderService
 * @Description: 订单 Service
 * @author: 郭秀志 jbcode@126.com
 * @date: 2020/6/19 16:00
 * @Copyright:
 */
public interface OrderService {

    /**
     * 创建订单
     *
     * @param userId    用户编号
     * @param productId 产品编号
     * @param price     价格
     * @return 订单编号
     * @throws Exception 创建订单失败,抛出异常
     */
    Integer createOrder(Long userId, Long productId, Integer price) throws Exception;

}

4.5 OrderServiceImpl

创建 [OrderServiceImpl] 类,实现创建订单的方法,全局事务的核心入口在这里,使用了@GlobalTransactional注解,其他子服务(扣减库存、扣减余额)使用的都是本地事务注解@Transactional // 开启事物。代码如下:

/**   
 * @ClassName:  OrderServiceImpl
 * @Description: 核心入口方法通过httpclient调用其他服务。
 * @author: 郭秀志 jbcode@126.com
 * @date:   2020/6/19 16:01    
 * @Copyright:  
 */
@Service
public class OrderServiceImpl implements OrderService {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private OrderDao orderDao;

    @Override
    @GlobalTransactional
    public Integer createOrder(Long userId, Long productId, Integer price) throws Exception {
        Integer amount = 1; // 购买数量,暂时设置为 1。

        logger.info("[createOrder] 当前 XID: {}", RootContext.getXID());

        // 扣减库存
        this.reduceStock(productId, amount);

        // 扣减余额
        this.reduceBalance(userId, price);

        // 保存订单
        OrderDO order = new OrderDO().setUserId(userId).setProductId(productId).setPayAmount(amount * price);
        orderDao.saveOrder(order);
        logger.info("[createOrder] 保存订单: {}", order.getId());

        // 返回订单编号
        return order.getId();
    }

    private void reduceStock(Long productId, Integer amount) throws IOException {
        // 参数拼接
        JSONObject params = new JSONObject().fluentPut("productId", String.valueOf(productId))
                .fluentPut("amount", String.valueOf(amount));
        // 执行调用
        HttpResponse response = DefaultHttpExecutor.getInstance().executePost("http://127.0.0.1:8082", "/product/reduce-stock",
                params, HttpResponse.class);
        // 解析结果
        Boolean success = Boolean.valueOf(EntityUtils.toString(response.getEntity()));
        if (!success) {
            throw new RuntimeException("扣除库存失败");
        }
    }

    private void reduceBalance(Long userId, Integer price) throws IOException {
        // 参数拼接
        JSONObject params = new JSONObject().fluentPut("userId", String.valueOf(userId))
                .fluentPut("price", String.valueOf(price));
        // 执行调用
        HttpResponse response = DefaultHttpExecutor.getInstance().executePost("http://127.0.0.1:8083", "/account/reduce-balance",
                params, HttpResponse.class);
        // 解析结果
        Boolean success = Boolean.valueOf(EntityUtils.toString(response.getEntity()));
        if (!success) {
            throw new RuntimeException("扣除余额失败");
        }
    }

}

<1> 处,在类上,添加 Seata @GlobalTransactional 注解,声明全局事务
<2> 处,调用 #reduceStock(productId, amount) 方法,通过 Apache HttpClient 远程 HTTP 调用商品服务,进行扣除库存。
其中,DefaultHttpExecutor 是 Seata 封装,在使用个 HttpClient 发起 HTTP 调用时,将 Seata 全局事务 XID 通过 Header 传递。不过有两点要注意:

  • 在使用 POST 请求时,DefaultHttpExecutor 暂时只支持 application/json 请求参数格式。所以,如果想要 application/x-www-form-urlencoded 等格式,需要自己重新封装~
  • 针对返回结果的转换,DefaultHttpExecutor 暂时没有实现完成,代码如下图所示:

另外,商品服务提供的 /product/reduce-stock 接口,通过返回 truefalse 来表示扣除库存是否成功。因此,我们在 false扣除失败时,抛出 RuntimeException 异常,从而实现全局事务的回滚。
<3> 处,调用 #reduceBalance(userId, price) 方法,通过 Apache HttpClient 远程 HTTP 调用账户服务,进行扣除余额。整体逻辑和 <2> 一致。
<4> 处,在全部调用成功后,调用 OrderDao 保存订单。

4.6 OrderDao

创建 [OrderDao]接口,定义保存订单的操作。代码如下:


@Mapper
@Repository
public interface OrderDao {

    /**
     * 插入订单记录
     *
     * @param order 订单
     * @return 影响记录数量
     */
    @Insert("INSERT INTO orders (user_id, product_id, pay_amount) VALUES (#{userId}, #{productId}, #{payAmount})")
    @Options(useGeneratedKeys = true, keyColumn = "id", keyProperty = "id")
    int saveOrder(OrderDO order);

}

其中,[OrderDO]实体类,对应 orders 表。代码如下:

package cn.iocoder.springboot.lab53.orderservice.entity;

/**
 * 订单实体
 */
public class OrderDO {

    /** 订单编号 **/
    private Integer id;

    /** 用户编号 **/
    private Long userId;

    /** 产品编号 **/
    private Long productId;

    /** 支付金额 **/
    private Integer payAmount;

    public Integer getId() {
        return id;
    }

    public OrderDO setId(Integer id) {
        this.id = id;
        return this;
    }

    public Long getUserId() {
        return userId;
    }

    public OrderDO setUserId(Long userId) {
        this.userId = userId;
        return this;
    }

    public Long getProductId() {
        return productId;
    }

    public OrderDO setProductId(Long productId) {
        this.productId = productId;
        return this;
    }

    public Integer getPayAmount() {
        return payAmount;
    }

    public OrderDO setPayAmount(Integer payAmount) {
        this.payAmount = payAmount;
        return this;
    }

}

其他2个服务的代码略,可参考Order模块的结构。

五、测试

下面,我们将测试两种情况:

  1. 分布式事务正常提交
  2. 分布式事务异常回滚

5.1 步骤

  1. 启动Nacos、Seata。
  2. Debug 执行 OrderServiceApplication 启动订单服务。
  3. ProductServiceApplication 启动商品服务。
  4. 执行 AccountServiceApplication 启动账户服务。

5.2 正常提交下单请求

使用 Postman 模拟调用 http://127.0.0.1:8081/order/create 创建订单的接口,如下图所示:


此时,在控制台打印日志如下图所示:


2020-06-19 15:46:34.052  INFO 10628 --- [nio-8081-exec-3] c.i.s.l.o.controller.OrderController     : [createOrder] 收到下单请求,用户:1, 商品:1, 价格:1
2020-06-19 15:46:34.137  INFO 10628 --- [nio-8081-exec-3] i.seata.tm.api.DefaultGlobalTransaction  : Begin new global transaction [192.168.1.104:8091:2014744463]
2020-06-19 15:46:37.957  INFO 10628 --- [nio-8081-exec-3] c.i.s.l.o.service.OrderServiceImpl       : [createOrder] 当前 XID: 192.168.1.104:8091:2014744463
2020-06-19 15:46:47.681  INFO 10628 --- [nio-8081-exec-3] c.i.s.l.o.service.OrderServiceImpl       : [createOrder] 保存订单: 4
2020-06-19 15:47:00.055  INFO 10628 --- [lector_TMROLE_1] i.s.c.r.netty.AbstractRpcRemotingClient  : channel [id: 0x8ddec010, L:/127.0.0.1:50867 - R:/127.0.0.1:8091] read idle.
2020-06-19 15:47:00.056  INFO 10628 --- [lector_TMROLE_1] i.s.core.rpc.netty.NettyPoolableFactory  : will destroy channel:[id: 0x8ddec010, L:/127.0.0.1:50867 - R:/127.0.0.1:8091]
2020-06-19 15:47:00.056  INFO 10628 --- [lector_TMROLE_1] i.s.core.rpc.netty.AbstractRpcRemoting   : ChannelHandlerContext(AbstractRpcRemotingClient$ClientHandler#0, [id: 0x8ddec010, L:/127.0.0.1:50867 - R:/127.0.0.1:8091]) will closed
2020-06-19 15:47:00.056  INFO 10628 --- [lector_TMROLE_1] i.s.core.rpc.netty.AbstractRpcRemoting   : ChannelHandlerContext(AbstractRpcRemotingClient$ClientHandler#0, [id: 0x8ddec010, L:/127.0.0.1:50867 ! R:/127.0.0.1:8091]) will closed
2020-06-19 15:47:00.056  INFO 10628 --- [lector_TMROLE_1] i.s.c.r.netty.NettyClientChannelManager  : return to pool, rm channel:[id: 0x8ddec010, L:/127.0.0.1:50867 ! R:/127.0.0.1:8091]
2020-06-19 15:47:00.056  INFO 10628 --- [lector_TMROLE_1] i.s.core.rpc.netty.NettyPoolableFactory  : channel valid false,channel:[id: 0x8ddec010, L:/127.0.0.1:50867 ! R:/127.0.0.1:8091]
2020-06-19 15:47:00.056  INFO 10628 --- [lector_TMROLE_1] i.s.core.rpc.netty.NettyPoolableFactory  : will destroy channel:[id: 0x8ddec010, L:/127.0.0.1:50867 ! R:/127.0.0.1:8091]
2020-06-19 15:47:00.056  INFO 10628 --- [lector_TMROLE_1] i.s.core.rpc.netty.AbstractRpcRemoting   : ChannelHandlerContext(AbstractRpcRemotingClient$ClientHandler#0, [id: 0x8ddec010, L:/127.0.0.1:50867 ! R:/127.0.0.1:8091]) will closed
2020-06-19 15:47:00.056  INFO 10628 --- [lector_TMROLE_1] i.s.core.rpc.netty.AbstractRpcRemoting   : ChannelHandlerContext(AbstractRpcRemotingClient$ClientHandler#0, [id: 0x8ddec010, L:/127.0.0.1:50867 ! R:/127.0.0.1:8091]) will closed
2020-06-19 15:47:00.056  INFO 10628 --- [lector_TMROLE_1] i.s.c.r.netty.AbstractRpcRemotingClient  : channel inactive: [id: 0x8ddec010, L:/127.0.0.1:50867 ! R:/127.0.0.1:8091]
2020-06-19 15:47:00.056  INFO 10628 --- [lector_TMROLE_1] i.s.core.rpc.netty.NettyPoolableFactory  : channel valid false,channel:[id: 0x8ddec010, L:/127.0.0.1:50867 ! R:/127.0.0.1:8091]
2020-06-19 15:47:00.056  INFO 10628 --- [lector_TMROLE_1] i.s.core.rpc.netty.NettyPoolableFactory  : will destroy channel:[id: 0x8ddec010, L:/127.0.0.1:50867 ! R:/127.0.0.1:8091]
2020-06-19 15:47:00.057  INFO 10628 --- [lector_TMROLE_1] i.s.core.rpc.netty.AbstractRpcRemoting   : ChannelHandlerContext(AbstractRpcRemotingClient$ClientHandler#0, [id: 0x8ddec010, L:/127.0.0.1:50867 ! R:/127.0.0.1:8091]) will closed
2020-06-19 15:47:00.057  INFO 10628 --- [lector_TMROLE_1] i.s.core.rpc.netty.AbstractRpcRemoting   : ChannelHandlerContext(AbstractRpcRemotingClient$ClientHandler#0, [id: 0x8ddec010, L:/127.0.0.1:50867 ! R:/127.0.0.1:8091]) will closed
2020-06-19 15:47:00.057  INFO 10628 --- [lector_TMROLE_1] i.s.c.r.netty.AbstractRpcRemotingClient  : channel [id: 0x0feaf046, L:/127.0.0.1:50865 - R:/127.0.0.1:8091] read idle.
2020-06-19 15:47:00.057  INFO 10628 --- [lector_TMROLE_1] i.s.core.rpc.netty.NettyPoolableFactory  : will destroy channel:[id: 0x0feaf046, L:/127.0.0.1:50865 - R:/127.0.0.1:8091]
2020-06-19 15:47:00.057  INFO 10628 --- [lector_TMROLE_1] i.s.core.rpc.netty.AbstractRpcRemoting   : ChannelHandlerContext(AbstractRpcRemotingClient$ClientHandler#0, [id: 0x0feaf046, L:/127.0.0.1:50865 - R:/127.0.0.1:8091]) will closed
2020-06-19 15:47:00.057  INFO 10628 --- [lector_TMROLE_1] i.s.core.rpc.netty.AbstractRpcRemoting   : ChannelHandlerContext(AbstractRpcRemotingClient$ClientHandler#0, [id: 0x0feaf046, L:/127.0.0.1:50865 ! R:/127.0.0.1:8091]) will closed
2020-06-19 15:47:00.057  INFO 10628 --- [lector_TMROLE_1] i.s.core.rpc.netty.NettyPoolableFactory  : channel valid false,channel:[id: 0x0feaf046, L:/127.0.0.1:50865 ! R:/127.0.0.1:8091]
2020-06-19 15:47:00.057  INFO 10628 --- [lector_TMROLE_1] i.s.core.rpc.netty.NettyPoolableFactory  : will destroy channel:[id: 0x0feaf046, L:/127.0.0.1:50865 ! R:/127.0.0.1:8091]
2020-06-19 15:47:00.057  INFO 10628 --- [lector_TMROLE_1] i.s.core.rpc.netty.AbstractRpcRemoting   : ChannelHandlerContext(AbstractRpcRemotingClient$ClientHandler#0, [id: 0x0feaf046, L:/127.0.0.1:50865 ! R:/127.0.0.1:8091]) will closed
2020-06-19 15:47:00.057  INFO 10628 --- [lector_TMROLE_1] i.s.core.rpc.netty.AbstractRpcRemoting   : ChannelHandlerContext(AbstractRpcRemotingClient$ClientHandler#0, [id: 0x0feaf046, L:/127.0.0.1:50865 ! R:/127.0.0.1:8091]) will closed
2020-06-19 15:47:00.057  INFO 10628 --- [lector_TMROLE_1] i.s.c.r.netty.AbstractRpcRemotingClient  : channel [id: 0xe544158f, L:/127.0.0.1:50866 - R:/127.0.0.1:8091] read idle.
2020-06-19 15:47:00.057  INFO 10628 --- [lector_TMROLE_1] i.s.core.rpc.netty.NettyPoolableFactory  : will destroy channel:[id: 0xe544158f, L:/127.0.0.1:50866 - R:/127.0.0.1:8091]
2020-06-19 15:47:00.057  INFO 10628 --- [lector_TMROLE_1] i.s.core.rpc.netty.AbstractRpcRemoting   : ChannelHandlerContext(AbstractRpcRemotingClient$ClientHandler#0, [id: 0xe544158f, L:/127.0.0.1:50866 - R:/127.0.0.1:8091]) will closed
2020-06-19 15:47:00.057  INFO 10628 --- [lector_TMROLE_1] i.s.core.rpc.netty.AbstractRpcRemoting   : ChannelHandlerContext(AbstractRpcRemotingClient$ClientHandler#0, [id: 0xe544158f, L:/127.0.0.1:50866 ! R:/127.0.0.1:8091]) will closed
2020-06-19 15:47:00.057  INFO 10628 --- [lector_TMROLE_1] i.s.core.rpc.netty.NettyPoolableFactory  : channel valid false,channel:[id: 0xe544158f, L:/127.0.0.1:50866 ! R:/127.0.0.1:8091]
2020-06-19 15:47:00.057  INFO 10628 --- [lector_TMROLE_1] i.s.core.rpc.netty.NettyPoolableFactory  : will destroy channel:[id: 0xe544158f, L:/127.0.0.1:50866 ! R:/127.0.0.1:8091]
2020-06-19 15:47:00.057  INFO 10628 --- [lector_TMROLE_1] i.s.core.rpc.netty.AbstractRpcRemoting   : ChannelHandlerContext(AbstractRpcRemotingClient$ClientHandler#0, [id: 0xe544158f, L:/127.0.0.1:50866 ! R:/127.0.0.1:8091]) will closed
2020-06-19 15:47:00.057  INFO 10628 --- [lector_TMROLE_1] i.s.core.rpc.netty.AbstractRpcRemoting   : ChannelHandlerContext(AbstractRpcRemotingClient$ClientHandler#0, [id: 0xe544158f, L:/127.0.0.1:50866 ! R:/127.0.0.1:8091]) will closed
2020-06-19 15:47:00.057  INFO 10628 --- [lector_TMROLE_1] i.s.c.r.netty.AbstractRpcRemotingClient  : channel inactive: [id: 0x0feaf046, L:/127.0.0.1:50865 ! R:/127.0.0.1:8091]
2020-06-19 15:47:00.057  INFO 10628 --- [lector_TMROLE_1] i.s.core.rpc.netty.NettyPoolableFactory  : channel valid false,channel:[id: 0x0feaf046, L:/127.0.0.1:50865 ! R:/127.0.0.1:8091]
2020-06-19 15:47:00.057  INFO 10628 --- [lector_TMROLE_1] i.s.core.rpc.netty.NettyPoolableFactory  : will destroy channel:[id: 0x0feaf046, L:/127.0.0.1:50865 ! R:/127.0.0.1:8091]
2020-06-19 15:47:00.057  INFO 10628 --- [lector_TMROLE_1] i.s.core.rpc.netty.AbstractRpcRemoting   : ChannelHandlerContext(AbstractRpcRemotingClient$ClientHandler#0, [id: 0x0feaf046, L:/127.0.0.1:50865 ! R:/127.0.0.1:8091]) will closed
2020-06-19 15:47:00.057  INFO 10628 --- [lector_TMROLE_1] i.s.core.rpc.netty.AbstractRpcRemoting   : ChannelHandlerContext(AbstractRpcRemotingClient$ClientHandler#0, [id: 0x0feaf046, L:/127.0.0.1:50865 ! R:/127.0.0.1:8091]) will closed
2020-06-19 15:47:00.058  INFO 10628 --- [lector_TMROLE_1] i.s.c.r.netty.AbstractRpcRemotingClient  : channel inactive: [id: 0xe544158f, L:/127.0.0.1:50866 ! R:/127.0.0.1:8091]
2020-06-19 15:47:00.058  INFO 10628 --- [lector_TMROLE_1] i.s.core.rpc.netty.NettyPoolableFactory  : channel valid false,channel:[id: 0xe544158f, L:/127.0.0.1:50866 ! R:/127.0.0.1:8091]
2020-06-19 15:47:00.058  INFO 10628 --- [lector_TMROLE_1] i.s.core.rpc.netty.NettyPoolableFactory  : will destroy channel:[id: 0xe544158f, L:/127.0.0.1:50866 ! R:/127.0.0.1:8091]
2020-06-19 15:47:00.058  INFO 10628 --- [lector_TMROLE_1] i.s.core.rpc.netty.AbstractRpcRemoting   : ChannelHandlerContext(AbstractRpcRemotingClient$ClientHandler#0, [id: 0xe544158f, L:/127.0.0.1:50866 ! R:/127.0.0.1:8091]) will closed
2020-06-19 15:47:00.058  INFO 10628 --- [lector_TMROLE_1] i.s.core.rpc.netty.AbstractRpcRemoting   : ChannelHandlerContext(AbstractRpcRemotingClient$ClientHandler#0, [id: 0xe544158f, L:/127.0.0.1:50866 ! R:/127.0.0.1:8091]) will closed
2020-06-19 15:47:00.232  INFO 10628 --- [lector_RMROLE_1] i.s.c.r.netty.AbstractRpcRemotingClient  : channel inactive: [id: 0xe226f5fd, L:/127.0.0.1:50859 ! R:/127.0.0.1:8091]
2020-06-19 15:47:00.233  INFO 10628 --- [lector_RMROLE_1] i.s.c.r.netty.NettyClientChannelManager  : return to pool, rm channel:[id: 0xe226f5fd, L:/127.0.0.1:50859 ! R:/127.0.0.1:8091]
2020-06-19 15:47:00.233  INFO 10628 --- [lector_RMROLE_1] i.s.core.rpc.netty.NettyPoolableFactory  : channel valid false,channel:[id: 0xe226f5fd, L:/127.0.0.1:50859 ! R:/127.0.0.1:8091]
2020-06-19 15:47:00.233  INFO 10628 --- [lector_RMROLE_1] i.s.core.rpc.netty.NettyPoolableFactory  : will destroy channel:[id: 0xe226f5fd, L:/127.0.0.1:50859 ! R:/127.0.0.1:8091]
2020-06-19 15:47:00.233  INFO 10628 --- [lector_RMROLE_1] i.s.core.rpc.netty.AbstractRpcRemoting   : ChannelHandlerContext(AbstractRpcRemotingClient$ClientHandler#0, [id: 0xe226f5fd, L:/127.0.0.1:50859 ! R:/127.0.0.1:8091]) will closed
2020-06-19 15:47:00.234  INFO 10628 --- [lector_RMROLE_1] i.s.core.rpc.netty.AbstractRpcRemoting   : ChannelHandlerContext(AbstractRpcRemotingClient$ClientHandler#0, [id: 0xe226f5fd, L:/127.0.0.1:50859 ! R:/127.0.0.1:8091]) will closed
2020-06-19 15:47:03.890  INFO 10628 --- [imeoutChecker_1] i.s.c.r.netty.NettyClientChannelManager  : will connect to 127.0.0.1:8091
2020-06-19 15:47:03.890  INFO 10628 --- [imeoutChecker_1] i.s.core.rpc.netty.NettyPoolableFactory  : NettyPool create channel to transactionRole:TMROLE,address:127.0.0.1:8091,msg:< RegisterTMRequest{applicationId='order-service', transactionServiceGroup='order-service-group'} >
2020-06-19 15:47:03.898  INFO 10628 --- [imeoutChecker_1] i.s.core.rpc.netty.NettyPoolableFactory  : register success, cost 5 ms, version:1.2.0,role:TMROLE,channel:[id: 0xaac9d379, L:/127.0.0.1:50892 - R:/127.0.0.1:8091]
2020-06-19 15:47:03.902  INFO 10628 --- [imeoutChecker_2] i.s.c.r.netty.NettyClientChannelManager  : will connect to 127.0.0.1:8091
2020-06-19 15:47:03.902  INFO 10628 --- [imeoutChecker_2] io.seata.core.rpc.netty.RmRpcClient      : RM will register :jdbc:mysql://101.133.227.13:3306/seata_order
2020-06-19 15:47:03.903  INFO 10628 --- [imeoutChecker_2] i.s.core.rpc.netty.NettyPoolableFactory  : NettyPool create channel to transactionRole:RMROLE,address:127.0.0.1:8091,msg:< RegisterRMRequest{resourceIds='jdbc:mysql://101.133.227.13:3306/seata_order', applicationId='order-service', transactionServiceGroup='order-service-group'} >
2020-06-19 15:47:03.913  INFO 10628 --- [imeoutChecker_2] io.seata.core.rpc.netty.RmRpcClient      : register RM success. server version:1.2.0,channel:[id: 0x592a6d40, L:/127.0.0.1:50893 - R:/127.0.0.1:8091]
2020-06-19 15:47:03.914  INFO 10628 --- [imeoutChecker_2] i.s.core.rpc.netty.NettyPoolableFactory  : register success, cost 8 ms, version:1.2.0,role:RMROLE,channel:[id: 0x592a6d40, L:/127.0.0.1:50893 - R:/127.0.0.1:8091]
2020-06-19 15:47:30.056 ERROR 10628 --- [nio-8081-exec-3] i.s.core.rpc.netty.AbstractRpcRemoting   : wait response error:cost 30001 ms,ip:127.0.0.1:8091,request:xid=192.168.1.104:8091:2014744463,extraData=null
2020-06-19 15:47:30.057 ERROR 10628 --- [nio-8081-exec-3] i.seata.tm.api.DefaultGlobalTransaction  : Failed to report global commit [192.168.1.104:8091:2014744463],Retry Countdown: 5, reason: RPC timeout
2020-06-19 15:47:30.454  INFO 10628 --- [nio-8081-exec-3] i.seata.tm.api.DefaultGlobalTransaction  : [192.168.1.104:8091:2014744463] commit status: Committed
2020-06-19 15:47:31.667  INFO 10628 --- [tch_RMROLE_1_16] i.s.core.rpc.netty.RmMessageListener     : onMessage:xid=192.168.1.104:8091:2014744463,branchId=2014744480,branchType=AT,resourceId=jdbc:mysql://101.133.227.13:3306/seata_order,applicationData=null
2020-06-19 15:47:31.668  INFO 10628 --- [tch_RMROLE_1_16] io.seata.rm.AbstractRMHandler            : Branch committing: 192.168.1.104:8091:2014744463 2014744480 jdbc:mysql://101.133.227.13:3306/seata_order null
2020-06-19 15:47:31.668  INFO 10628 --- [tch_RMROLE_1_16] io.seata.rm.AbstractRMHandler            : Branch commit result: PhaseTwo_Committed

ProductService(产品服务)控制台信息:


2020-06-19 15:44:55.501  INFO 1756 --- [nio-8082-exec-3] c.i.s.l.p.controller.ProductController   : [reduceStock] 收到减少库存请求, 商品:1, 价格:1
2020-06-19 15:44:55.614  INFO 1756 --- [nio-8082-exec-3] c.i.s.l.p.service.ProductServiceImpl     : [reduceStock] 当前 XID: 192.168.1.104:8091:2014744375
2020-06-19 15:44:55.615  INFO 1756 --- [nio-8082-exec-3] c.i.s.l.p.service.ProductServiceImpl     : [checkStock] 检查 1 库存
2020-06-19 15:44:55.684  INFO 1756 --- [nio-8082-exec-3] c.i.s.l.p.service.ProductServiceImpl     : [reduceStock] 开始扣减 1 库存
2020-06-19 15:44:55.939  INFO 1756 --- [nio-8082-exec-3] c.i.s.l.p.service.ProductServiceImpl     : [reduceStock] 扣除 1 库存成功
2020-06-19 15:45:55.713  INFO 1756 --- [tch_RMROLE_1_16] i.s.core.rpc.netty.RmMessageListener     : onMessage:xid=192.168.1.104:8091:2014744375,branchId=2014744387,branchType=AT,resourceId=jdbc:mysql://101.133.227.13:3306/seata_product,applicationData=null
2020-06-19 15:45:55.714  INFO 1756 --- [tch_RMROLE_1_16] io.seata.rm.AbstractRMHandler            : Branch Rollbacking: 192.168.1.104:8091:2014744375 2014744387 jdbc:mysql://101.133.227.13:3306/seata_product
2020-06-19 15:45:56.104  INFO 1756 --- [tch_RMROLE_1_16] i.s.r.d.undo.AbstractUndoLogManager      : xid 192.168.1.104:8091:2014744375 branch 2014744387, undo_log deleted with GlobalFinished
2020-06-19 15:45:56.160  INFO 1756 --- [tch_RMROLE_1_16] io.seata.rm.AbstractRMHandler            : Branch Rollbacked result: PhaseTwo_Rollbacked
2020-06-19 15:46:38.479  INFO 1756 --- [nio-8082-exec-5] c.i.s.l.p.controller.ProductController   : [reduceStock] 收到减少库存请求, 商品:1, 价格:1
2020-06-19 15:46:38.577  INFO 1756 --- [nio-8082-exec-5] c.i.s.l.p.service.ProductServiceImpl     : [reduceStock] 当前 XID: 192.168.1.104:8091:2014744463
2020-06-19 15:46:38.577  INFO 1756 --- [nio-8082-exec-5] c.i.s.l.p.service.ProductServiceImpl     : [checkStock] 检查 1 库存
2020-06-19 15:46:38.630  INFO 1756 --- [nio-8082-exec-5] c.i.s.l.p.service.ProductServiceImpl     : [reduceStock] 开始扣减 1 库存
2020-06-19 15:46:38.868  INFO 1756 --- [nio-8082-exec-5] c.i.s.l.p.service.ProductServiceImpl     : [reduceStock] 扣除 1 库存成功
2020-06-19 15:47:31.422  INFO 1756 --- [tch_RMROLE_1_16] i.s.core.rpc.netty.RmMessageListener     : onMessage:xid=192.168.1.104:8091:2014744463,branchId=2014744470,branchType=AT,resourceId=jdbc:mysql://101.133.227.13:3306/seata_product,applicationData=null
2020-06-19 15:47:31.425  INFO 1756 --- [tch_RMROLE_1_16] io.seata.rm.AbstractRMHandler            : Branch committing: 192.168.1.104:8091:2014744463 2014744470 jdbc:mysql://101.133.227.13:3306/seata_product null
2020-06-19 15:47:31.427  INFO 1756 --- [tch_RMROLE_1_16] io.seata.rm.AbstractRMHandler            : Branch commit result: PhaseTwo_Committed

5.4 异常回滚下单请求

在 OrderServiceImpl 的 createOrder(...)方法打上断点如下图,方便我们看到 product 表的 stock 被减少:

现在的product 表的 stock8 个。
使用 Postman 模拟调用 http://127.0.0.1:8081/order/create 创建订单的接口,如下图所示:

扣减了库存,并回滚

2020-06-19 09:45:43.334  INFO 11664 --- [nio-8081-exec-1] i.seata.tm.api.DefaultGlobalTransaction  : Begin new global transaction [192.168.1.104:8091:2014720090]
2020-06-19 09:45:43.338  INFO 11664 --- [nio-8081-exec-1] c.i.s.l.s.service.impl.OrderServiceImpl  : [createOrder] 当前 XID: 192.168.1.104:8091:2014720090
2020-06-19 09:45:43.406  INFO 11664 --- [nio-8081-exec-1] c.i.s.l.s.s.impl.ProductServiceImpl      : [reduceStock] 当前 XID: 192.168.1.104:8091:2014720090
2020-06-19 09:45:43.406  INFO 11664 --- [nio-8081-exec-1] c.i.s.l.s.s.impl.ProductServiceImpl      : [checkStock] 检查 1 库存
2020-06-19 09:45:43.420  INFO 11664 --- [nio-8081-exec-1] i.s.common.loader.EnhancedServiceLoader  : load SQLRecognizerFactory[druid] extension by class[io.seata.sqlparser.druid.DruidDelegatingSQLRecognizerFactory]
2020-06-19 09:45:43.450  INFO 11664 --- [nio-8081-exec-1] i.s.common.loader.EnhancedServiceLoader  : load SQLOperateRecognizerHolder[mysql] extension by class[io.seata.sqlparser.druid.mysql.MySQLOperateRecognizerHolder]
2020-06-19 09:45:43.642  INFO 11664 --- [nio-8081-exec-1] c.i.s.l.s.s.impl.ProductServiceImpl      : [reduceStock] 开始扣减 1 库存
2020-06-19 09:45:43.689  INFO 11664 --- [nio-8081-exec-1] i.s.common.loader.EnhancedServiceLoader  : load KeywordChecker[mysql] extension by class[io.seata.rm.datasource.undo.mysql.keyword.MySQLKeywordChecker]
2020-06-19 09:45:43.690  INFO 11664 --- [nio-8081-exec-1] i.s.common.loader.EnhancedServiceLoader  : load TableMetaCache[mysql] extension by class[io.seata.rm.datasource.sql.struct.cache.MysqlTableMetaCache]
2020-06-19 09:45:44.661  INFO 11664 --- [nio-8081-exec-1] c.i.s.l.s.s.impl.ProductServiceImpl      : [reduceStock] 扣除 1 库存成功
2020-06-19 09:45:46.182  INFO 11664 --- [nio-8081-exec-1] i.seata.tm.api.DefaultGlobalTransaction  : [192.168.1.104:8091:2014720090] rollback status: Rollbacked

库存没有减少,但是更新时间变化了

举报

相关推荐

0 条评论