Spring Cloud Alibaba(6.3 Seata——純 Spring Boot AT 模式 + HttpClient 遠程調(diào)用)

一、前言

上篇文章介紹用戶購買商品的多數(shù)據(jù)源分布式事務 Spring Boot 單體應用,本文拆成分多個 Spring Boot 應用,通過 Apache HttpClient 來實現(xiàn) HTTP 遠程調(diào)用每個 Spring Boot 應用提供的 Restful API 接口。整體如下圖所示:

整體架構圖

早期的微服務架構,會采用 Nginx 對后端的服務進行負載均衡,而服務提供者使用 HttpClient 進行遠程 HTTP 調(diào)用。如下調(diào)用鏈路:

Nginx + Spring Boot

Seata 提供了 seata-http 項目,對 Apache HttpClient 進行集成。實現(xiàn)原理是:

  • 服務消費者,使用 Seata 封裝的 AbstractHttpExecutor 執(zhí)行器,在使用HttpClient 發(fā)起 HTTP 調(diào)用時,將 Seata 全局事務 XID 通過 Header 傳遞。
  • 服務提供者,使用 Seata 提供的 SpringMVC TransactionPropagationIntercepter 攔截器,將 Header 中的 Seata 全局事務 XID 解析出來,設置到 Seata 上下文 中。

如此,我們便實現(xiàn)了多個 Spring Boot 應用的 Seata 全局事務的傳播。

本文的源代碼可從Gitee下載.

二、創(chuàng)建Module

該項目包含三個 Spring Boot模塊。


項目結構

三、初始化數(shù)據(jù)庫

使用 data.sql腳本,創(chuàng)建 seata_order、seata_storageseata_amount 三個庫。腳本內(nèi)容如下:

# 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); # 插入一條產(chǎn)品的庫存

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 模式必須創(chuàng)建的表,主要用于分支事務的回滾。
另外,考慮到測試方便,我們插入了一條 id = 1account 記錄,和一條 id = 1product 記錄。

四、 訂單服務

作為訂單服務。它主要提供 /order/create 接口,實現(xiàn)下單邏輯。

4.1 引入依賴

創(chuàng)建 [pom.xml] 文件,引入相關的依賴。內(nèi)容如下:

<?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>
 <!-- 實現(xiàn)對 Spring MVC 的自動化配置 -->
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
 </dependency>

 <!-- 實現(xiàn)對數(shù)據(jù)庫連接池的自動化配置 -->
 <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>

 <!-- 實現(xiàn)對 MyBatis 的自動化配置 -->
 <dependency>
 <groupId>org.mybatis.spring.boot</groupId>
 <artifactId>mybatis-spring-boot-starter</artifactId>
 <version>2.1.2</version>
 </dependency>

 <!-- 實現(xiàn)對 Seata 的自動化配置 -->
 <dependency>
 <groupId>io.seata</groupId>
 <artifactId>seata-spring-boot-starter</artifactId>
 <version>1.1.0</version>
 </dependency>
 <!-- 實現(xiàn) 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 依賴,實現(xiàn)對 Seata 的自動配置。
② 引入 seata-http 依賴,實現(xiàn) Seata 對 HttpClient 的集成支持。

4.2 配置文件

創(chuàng)建 [application.yaml]配置文件,添加相關的配置項。內(nèi)容如下:

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 文檔 —— 事務分組專題》。如果不能理解,可以見如下圖:

image

簡單來說,就是多了一層虛擬映射。這里,我們直接設置 TC Server 的地址,為 127.0.0.1:8091。

4.3 OrderController

創(chuàng)建 [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 中,會調(diào)用 OrderService 進行下單。

4.4 OrderService

創(chuàng)建 [OrderService]接口,定義了創(chuàng)建訂單的方法。代碼如下:


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

    /**
     * 創(chuàng)建訂單
     *
     * @param userId    用戶編號
     * @param productId 產(chǎn)品編號
     * @param price     價格
     * @return 訂單編號
     * @throws Exception 創(chuàng)建訂單失敗,拋出異常
     */
    Integer createOrder(Long userId, Long productId, Integer price) throws Exception;

}

4.5 OrderServiceImpl

創(chuàng)建 [OrderServiceImpl] 類,實現(xiàn)創(chuàng)建訂單的方法,全局事務的核心入口在這里,使用了@GlobalTransactional注解,其他子服務(扣減庫存、扣減余額)使用的都是本地事務注解@Transactional // 開啟事物。代碼如下:

/**   
 * @ClassName:  OrderServiceImpl
 * @Description: 核心入口方法通過httpclient調(diào)用其他服務。
 * @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; // 購買數(shù)量,暫時設置為 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 {
        // 參數(shù)拼接
        JSONObject params = new JSONObject().fluentPut("productId", String.valueOf(productId))
                .fluentPut("amount", String.valueOf(amount));
        // 執(zhí)行調(diào)用
        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 {
        // 參數(shù)拼接
        JSONObject params = new JSONObject().fluentPut("userId", String.valueOf(userId))
                .fluentPut("price", String.valueOf(price));
        // 執(zhí)行調(diào)用
        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> 處,調(diào)用 #reduceStock(productId, amount) 方法,通過 Apache HttpClient 遠程 HTTP 調(diào)用商品服務,進行扣除庫存。
其中,DefaultHttpExecutor 是 Seata 封裝,在使用個 HttpClient 發(fā)起 HTTP 調(diào)用時,將 Seata 全局事務 XID 通過 Header 傳遞。不過有兩點要注意:

  • 在使用 POST 請求時,DefaultHttpExecutor 暫時只支持 application/json 請求參數(shù)格式。所以,如果想要 application/x-www-form-urlencoded 等格式,需要自己重新封裝~
  • 針對返回結果的轉(zhuǎn)換,DefaultHttpExecutor 暫時沒有實現(xiàn)完成,代碼如下圖所示:
    實現(xiàn)代碼

另外,商品服務提供的 /product/reduce-stock 接口,通過返回 truefalse 來表示扣除庫存是否成功。因此,我們在 false扣除失敗時,拋出 RuntimeException 異常,從而實現(xiàn)全局事務的回滾。
<3> 處,調(diào)用 #reduceBalance(userId, price) 方法,通過 Apache HttpClient 遠程 HTTP 調(diào)用賬戶服務,進行扣除余額。整體邏輯和 <2> 一致。
<4> 處,在全部調(diào)用成功后,調(diào)用 OrderDao 保存訂單。

4.6 OrderDao

創(chuàng)建 [OrderDao]接口,定義保存訂單的操作。代碼如下:


@Mapper
@Repository
public interface OrderDao {

    /**
     * 插入訂單記錄
     *
     * @param order 訂單
     * @return 影響記錄數(shù)量
     */
    @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;

    /** 產(chǎn)品編號 **/
    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. 分布式事務異?;貪L

5.1 步驟

  1. 啟動Nacos、Seata。
  2. Debug 執(zhí)行 OrderServiceApplication 啟動訂單服務。
  3. ProductServiceApplication 啟動商品服務。
  4. 執(zhí)行 AccountServiceApplication 啟動賬戶服務。

5.2 正常提交下單請求

使用 Postman 模擬調(diào)用 http://127.0.0.1:8081/order/create 創(chuàng)建訂單的接口,如下圖所示:

下單請求

此時,在控制臺打印日志如下圖所示:


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(產(chǎn)品服務)控制臺信息:


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 異?;貪L下單請求

在 OrderServiceImpl 的 createOrder(...)方法打上斷點如下圖,方便我們看到 product 表的 stock 被減少:

image

現(xiàn)在的product 表的 stock8 個。
使用 Postman 模擬調(diào)用 http://127.0.0.1:8081/order/create 創(chuàng)建訂單的接口,如下圖所示:

image

扣減了庫存,并回滾

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

庫存沒有減少,但是更新時間變化了

image
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容