美文网首页
Seata分布式事务AT模式初体验

Seata分布式事务AT模式初体验

作者: Ravitn | 来源:发表于2021-01-19 16:39 被阅读0次

    原文:Seata分布式事务AT模式初体验

    引言

    随着应用体量的增加,微服务作为上帝之手为超级单体打开了屏障;同时使用整个应用服务显得更加清晰。服务寄托于数据,如何解决分布式事务的ACID,成为分布式事务亟待解决的问题。比较有名的分布式事务规范有XA(2PC),TCC(3PC), SAGA, 基于BASE理论的本地事务表重试达到最终一致性解决方案;基于MQ的2PC+补偿机制(事务回查)解决方案。Seata 作为一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

    目录

    我们来模拟用户购买商品的业务逻辑。整个业务逻辑由3个微服务提供支持:

    • 仓储服务:对给定的商品扣除仓储数量。
    • 订单服务:根据采购需求创建订单。
    • 帐户服务:从用户帐户中扣除余额。

    具体模型查看seata快速开始doc。

    两个应用

    comosus-boot: 订单服务;
    comosus-ravitn:仓储服务;帐户服务;
    服务间通过dubbo进行通信。

    配置是基于spring-cloud-starter-alibaba-seata, 在application.properties可以配置seata的相关事务配置

    角色

    • TC服务端;
    • TM:comosus-boot
    • RM(Client端):comosus-boot;comosus-ravitn

    建库建表

    comosus-ravitn数据库

    comosus-ravitn数据库用于存储,商品库存,和账户金额管理;建表SQL包含undo_log表如下:

    /*
    Navicat MySQL Data Transfer
    
    Source Server         : localhost
    Source Server Version : 50639
    Source Host           : localhost:3306
    Source Database       : comosus-ravitn
    
    Target Server Type    : MYSQL
    Target Server Version : 50639
    File Encoding         : 65001
    
    Date: 2021-01-18 10:39:09
    */
    
    SET FOREIGN_KEY_CHECKS=0;
    
    -- ----------------------------
    -- Table structure for ravitn_account
    -- ----------------------------
    DROP TABLE IF EXISTS `ravitn_account`;
    CREATE TABLE `ravitn_account` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `user_id` varchar(64) DEFAULT NULL,
      `money` decimal(32,5) DEFAULT '0.00000',
      `is_delete` tinyint(4) DEFAULT '0' COMMENT '1:删除;0:正常',
      `remark` varchar(256) DEFAULT NULL COMMENT '备注',
      `version` bigint(20) DEFAULT NULL COMMENT '更新版本',
      `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
      `create_time` datetime NOT NULL COMMENT '创建时间',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
    
    -- ----------------------------
    -- Records of ravitn_account
    -- ----------------------------
    INSERT INTO `ravitn_account` VALUES ('1', 'CU00000001', '1000.00000', '0', null, '1', '2021-01-05 14:37:45', '2021-01-05 14:37:42');
    
    -- ----------------------------
    -- Table structure for ravitn_storage
    -- ----------------------------
    DROP TABLE IF EXISTS `ravitn_storage`;
    CREATE TABLE `ravitn_storage` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `commodity_code` varchar(32) DEFAULT NULL COMMENT '商品编号',
      `count` bigint(20) DEFAULT '0' COMMENT '库存数量',
      `is_delete` tinyint(4) DEFAULT '0' COMMENT '1:删除;0:正常',
      `remark` varchar(256) DEFAULT NULL COMMENT '备注',
      `version` bigint(20) DEFAULT NULL COMMENT '更新版本',
      `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
      `create_time` datetime NOT NULL COMMENT '创建时间',
      PRIMARY KEY (`id`),
      UNIQUE KEY `commodity_code` (`commodity_code`)
    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
    
    -- ----------------------------
    -- Records of ravitn_storage
    -- ----------------------------
    INSERT INTO `ravitn_storage` VALUES ('1', 'CGD0001', '1000', '0', null, '1', '2021-01-05 14:38:38', '2021-01-05 14:38:36');
    
    -- ----------------------------
    -- Table structure for undo_log
    -- ----------------------------
    DROP TABLE IF EXISTS `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 DEFAULT CHARSET=utf8;
    
    -- ----------------------------
    -- Records of undo_log
    -- ----------------------------
    
    

    comosus-boot数据库

    comosus-boot数据库用户存储订单数据,包括undo日志表;具体sql如下:

    /*
    Navicat MySQL Data Transfer
    
    Source Server         : localhost
    Source Server Version : 50639
    Source Host           : localhost:3306
    Source Database       : master0
    
    Target Server Type    : MYSQL
    Target Server Version : 50639
    File Encoding         : 65001
    
    Date: 2021-01-04 16:37:57
    */
    
    SET FOREIGN_KEY_CHECKS=0;
    
    -- ----------------------------
    -- Table structure for seate_order
    -- ----------------------------
    DROP TABLE IF EXISTS `seate_order`;
    CREATE TABLE `seate_order` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `user_id` varchar(64) DEFAULT NULL,
    `commodity_code` varchar(32) DEFAULT NULL COMMENT '商品编号',
    `count` int(11) DEFAULT '0',
    `money` decimal(32,5) DEFAULT '0.00000',
    `version` bigint(20) DEFAULT NULL COMMENT '更新版本',
    `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    `create_time` datetime NOT NULL COMMENT '创建时间',
    `is_delete` tinyint(1) DEFAULT '0' COMMENT '[0:normal: 正常;1:deleted: 删除]',
    `remark` varchar(32) DEFAULT NULL,
    PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    -- ----------------------------
    -- Table structure for undo_log
    -- ----------------------------
    DROP TABLE IF EXISTS `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`)
    
    

    TC数据库

    TC提供了本地文件,db和redis三种方式存储事务日志,我们选择db方式,以便观察对应的事务过程。具体sql如下:

    /*
    Navicat MySQL Data Transfer
    
    Source Server         : localhost
    Source Server Version : 50639
    Source Host           : localhost:3306
    Source Database       : seata
    
    Target Server Type    : MYSQL
    Target Server Version : 50639
    File Encoding         : 65001
    
    Date: 2021-01-12 19:35:23
    */
    
    SET FOREIGN_KEY_CHECKS=0;
    
    -- ----------------------------
    -- Table structure for branch_table
    -- ----------------------------
    DROP TABLE IF EXISTS `branch_table`;
    CREATE TABLE `branch_table` (
      `branch_id` bigint(20) NOT NULL,
      `xid` varchar(128) NOT NULL,
      `transaction_id` bigint(20) DEFAULT NULL,
      `resource_group_id` varchar(32) DEFAULT NULL,
      `resource_id` varchar(256) DEFAULT NULL,
      `lock_key` varchar(128) DEFAULT NULL,
      `branch_type` varchar(8) DEFAULT NULL,
      `status` tinyint(4) DEFAULT NULL,
      `client_id` varchar(64) DEFAULT NULL,
      `application_data` varchar(2000) DEFAULT NULL,
      `gmt_create` datetime DEFAULT NULL,
      `gmt_modified` datetime DEFAULT NULL,
      PRIMARY KEY (`branch_id`),
      KEY `idx_xid` (`xid`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
    
    -- ----------------------------
    -- Table structure for global_table
    -- ----------------------------
    DROP TABLE IF EXISTS `global_table`;
    CREATE TABLE `global_table` (
      `xid` varchar(128) NOT NULL,
      `transaction_id` bigint(20) DEFAULT NULL,
      `status` tinyint(4) NOT NULL,
      `application_id` varchar(32) DEFAULT NULL,
      `transaction_service_group` varchar(32) DEFAULT NULL,
      `transaction_name` varchar(128) DEFAULT NULL,
      `timeout` int(11) DEFAULT NULL,
      `begin_time` bigint(20) DEFAULT NULL,
      `application_data` varchar(2000) DEFAULT NULL,
      `gmt_create` datetime DEFAULT NULL,
      `gmt_modified` datetime DEFAULT NULL,
      PRIMARY KEY (`xid`),
      KEY `idx_gmt_modified_status` (`gmt_modified`,`status`),
      KEY `idx_transaction_id` (`transaction_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
    
    -- ----------------------------
    -- Table structure for lock_table
    -- ----------------------------
    DROP TABLE IF EXISTS `lock_table`;
    CREATE TABLE `lock_table` (
      `row_key` varchar(128) NOT NULL,
      `xid` varchar(96) DEFAULT NULL,
      `transaction_id` mediumtext,
      `branch_id` mediumtext,
      `resource_id` varchar(256) DEFAULT NULL,
      `table_name` varchar(32) DEFAULT NULL,
      `pk` varchar(36) DEFAULT NULL,
      `gmt_create` datetime DEFAULT NULL,
      `gmt_modified` datetime DEFAULT NULL,
      PRIMARY KEY (`row_key`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
    
    -- ----------------------------
    -- Table structure for undo_log
    -- ----------------------------
    DROP TABLE IF EXISTS `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 DEFAULT CHARSET=utf8;
    
    

    建库建表已经准备好了,我们来实现业务。

    业务实现

    引用需要引入seata的相关包, 几版本信息如下:

      <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <springfox.swagger2.version>2.9.2</springfox.swagger2.version>
            <spring.boot.version>2.1.1.RELEASE</spring.boot.version>
            <maven.compiler.source>1.8</maven.compiler.source>
            <maven.compiler.target>1.8</maven.compiler.target>
            <metrics.version>2.0.1</metrics.version>
            <alibaba.boot.dubbo.springboot.starter>0.2.1.RELEASE</alibaba.boot.dubbo.springboot.starter>
            <sharding-sphere.version>4.1.0</sharding-sphere.version>
            <seata-spring-boot-starter.version>1.4.1</seata-spring-boot-starter.version>
            <dubbo.version>2.6.5</dubbo.version>
            <comosus-ravitn-api.version>1.0.0-SNAPSHOT</comosus-ravitn-api.version>
        </properties>
    

    使用的dubbo版本为2.6.5, dubbo-boot-starter的版本为0.2.1.RELEASE。 Seata版本为为1.4.1。spring-cloud-starter-alibaba-seata为2.2.1.RELEASE。

    <!-- seata start-->
            <dependency>
                <groupId>io.seata</groupId>
                <artifactId>seata-spring-boot-starter</artifactId>
                <version>${seata-spring-boot-starter.version}</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
                <version>2.2.1.RELEASE</version>
                <exclusions>
                    <exclusion>
                        <groupId>io.seata</groupId>
                        <artifactId>seata-spring-boot-starter</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
    
            <!--seata end-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
                <version>2.1.1.RELEASE</version>
                <exclusions>
                    <exclusion>
                        <groupId>org.bouncycastle</groupId>
                        <artifactId>bcpkix-jdk15on</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
    

    注意spring-cloud-starter-alibaba-seata需要依赖于hystrix。

    先来定义服务

    定义服务

    在comosus-ravitn中定义仓储服务;帐户服务;

    仓储服务

    package com.story.home.ravitn.api.domain.storage;
    
    /**
     * @ClassName: StorageRemoteService
     * @Description:
     * @Author: VT
     * @Date: 2021-01-05 10:56
     */
    public interface StorageRemoteService {
        /**
         * 扣除存储数量
         */
        Boolean deduct(String commodityCode, int count);
    }
    
    

    帐户服务

    package com.story.home.ravitn.api.domain.account.service;
    
    
    import java.math.BigDecimal;
    
    /**
     * @ClassName: AccountRemoteService
     * @Description:
     * @Author: Donaldhan
     * @Date: 2018-09-02 15:06
     */
    public interface AccountRemoteService {
        /**
         * 从用户账户中借出
         */
        Boolean debit(String userId, BigDecimal money);
    }
    
    

    具体业务的实现,我们就不说,基于Mybatis的CRUD Mapper 操作。

    我们在账户业务中模拟账户不存在的事务异常,具体如下:

    package com.story.home.ravitn.account.service.biz.impl;
    
    import com.story.home.ravitn.account.entity.Account;
    import com.story.home.ravitn.account.service.base.AccountDbService;
    import com.story.home.ravitn.account.service.biz.AccountBizService;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Service;
    import org.springframework.util.ObjectUtils;
    
    import javax.annotation.Resource;
    import java.math.BigDecimal;
    
    /**
     * @ClassName: AccountBizServiceImpl
     * @Description:
     * @Author: VT
     * @Date: 2021-01-04 16:31
     */
    @Slf4j
    @Service
    public class AccountBizServiceImpl implements AccountBizService {
        @Resource
        AccountDbService accountDbService;
    
        @Override
        public Boolean debit(String userId, BigDecimal money) {
            Account account = accountDbService.findAccountByUserId(userId);
            if(ObjectUtils.isEmpty(account)){
                throw new IllegalStateException("account is null userId:"+userId);
            }
            account.setMoney(account.getMoney().subtract(money));
            return accountDbService.updateAccount(account);
        }
    }
    
    

    订单服务

    在comosus-boot提供订单服务,这里我们忽略,直接做业务了

    业务实现

    package org.home.seata.order.service.facade.impl;
    
    import com.alibaba.dubbo.config.annotation.Reference;
    import com.story.home.ravitn.api.domain.account.service.AccountRemoteService;
    import com.story.home.ravitn.api.domain.storage.StorageRemoteService;
    import io.seata.spring.annotation.GlobalTransactional;
    import lombok.extern.slf4j.Slf4j;
    import org.home.seata.order.service.biz.OrderBizService;
    import org.home.seata.order.service.facade.OrderFacadeService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.math.BigDecimal;
    
    /**
     * @ClassName: OrderFacadeServiceImpl
     * @Description:
     * @Author: VT
     * @Date: 2021-01-05 14:06
     */
    @Slf4j
    @Service
    public class OrderFacadeServiceImpl implements OrderFacadeService {
        @Autowired
        OrderBizService orderBizService;
        @Reference(validation = "true",check = false, version = "1.0.0")
        StorageRemoteService storageRemoteService;
        @Reference(validation = "true",check = false, version = "1.0.0")
        AccountRemoteService accountRemoteService;
    
        @GlobalTransactional(rollbackFor = Exception.class,  timeoutMills = 30000, name = "purchase-tx")
        @Override
        public Boolean purchase(String userId, String commodityCode, int orderCount) {
            try {
                Boolean result =  storageRemoteService.deduct(commodityCode,orderCount);
                if(!result){
                    log.error("OrderFacadeService purchase storage deduct  error");
                }
                BigDecimal money =  new BigDecimal("16.8");
                result = orderBizService.create(userId,commodityCode,orderCount,money);
                if(!result){
                    log.error("OrderFacadeService purchase order create  error");
                }
                result =  accountRemoteService.debit(userId,money);
                if(!result){
                    log.error("OrderFacadeService purchase  account debit error");
                }
                return result;
            } catch (Exception e) {
                log.error("OrderFacadeService purchase error");
                throw new IllegalStateException(e);
            }
        }
    }
    
    

    order 相关业务操作,基于Mappder的Insert操作,不在赘述。

    我们使用接口测试业务

    业务测试接口

    package org.home.web.seata;
    
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiOperation;
    import lombok.Data;
    import lombok.extern.slf4j.Slf4j;
    import org.home.seata.order.service.facade.OrderFacadeService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Profile;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * @ClassName: TestSeateController
     * @Description:
     * @Author: VT
     * @Date: 2021-01-05 11:02
     */
    @Slf4j
    @Profile("seata")
    @RestController
    @RequestMapping("/api/test/seata")
    @Api(tags = {"seata"}, description = "seata test")
    public class TestSeataController {
        @Autowired
        OrderFacadeService orderFacadeService;
        /**
         * @return
         */
        @PostMapping("/purchase")
        @ApiOperation("下单")
        public String purchase(@RequestBody PurchaseForm purchaseForm)  {
            orderFacadeService.purchase(purchaseForm.getUserId(),purchaseForm.getCommodityCode(), purchaseForm.getOrderCount());
            return "ok";
        }
        @Data
        static class PurchaseForm {
            private String userId;
            private String commodityCode;
            private int orderCount;
        }
    
    }
    

    业务实现完,我们来配置应用

    服务配置

    仓储账户服务配置

    # seata
    
    seata.enabled=true
    # SeataProperties
    seata.application-id=comosus-ravitn
    seata.tx-service-group=comosus-ravitn-tx
    # SpringCloudAlibabaConfiguration
    #spring.cloud.alibaba.seata.application-id=comosus-ravitn
    #spring.cloud.alibaba.seata.tx-service-group=comosus-ravitn-tx
    seata.data-source-proxy-mode=AT
    seata.enable-auto-data-source-proxy=true
    seata.use-jdk-proxy=false
    
    ## seata service
    seata.service.disable-global-transaction=false
    seata.service.enable-degrade=false
    seata.service.vgroup-mapping.comosus-ravitn-tx=tc-cluster
    #only support when registry.type=file, please don't set multiple addresses
    seata.service.grouplist.tc-cluster=127.0.0.1:8091
    
    ## registry zk
    #seata.registry.type=file
    seata.registry.zk.server-addr=127.0.0.1:2181
    seata.registry.zk.session-timeout=6000
    seata.registry.zk.connect-timeout=2000
    #seata.registry.zk.username=
    #seata.registry.zk.password=
    seata.registry.load-balance=RoundRobinLoadBalance
    seata.registry.load-balance-virtual-nodes=10
    
    
    ## seata config
    
    ### file config
    #seata.config.type=file
    #seata.config.type=springCloudConfig
    #seata.config.file.name=file.conf
    
    ### nacos config
    #seata.config.type=nacos
    #seata.config.nacos.group=comosus-ravitn-seata-group
    #seata.config.nacos.server-addr=localhost
    #seata.config.nacos.username=
    #seata.config.nacos.password=
    #seata.config.nacos.namespace=
    
    
    
    ## seata client
    ### tm
    seata.client.tm.commit-retry-count=5
    seata.client.tm.rollback-retry-count=5
    seata.client.tm.default-global-transaction-timeout=60000
    seata.client.tm.degrade-check-allow-times=10
    seata.client.tm.degrade-check=false
    seata.client.tm.degrade-check-period=2000
    
    ### rm
    seata.client.rm.lock.retry-times=30
    seata.client.rm.async-commit-buffer-limit=10000
    seata.client.rm.lock.retry-interval=10
    seata.client.rm.lock.retry-policy-branch-rollback-on-conflict=true
    seata.client.rm.table-meta-check-enable=false
    seata.client.rm.report-retry-count=5
    seata.client.rm.report-success-enable=false
    seata.client.rm.saga-json-parser=fastjson
    seata.client.rm.saga-branch-register-enable=false
    
    ### undo log
    seata.client.undo.log-table=undo_log
    seata.client.undo.data-validation=true
    seata.client.undo.log-serialization=fastjson
    seata.client.undo.only-care-update-columns=true
    
    
    ## seata transport
    seata.transport.server=nio
    seata.transport.type=tcp
    seata.transport.heartbeat=true
    seata.transport.serialization=seata
    seata.transport.enable-client-batch-send-request=true
    seata.transport.shutdown.wait=3
    # zip
    seata.transport.compressor=none
    seata.transport.thread-factory.boss-thread-prefix=comosus-ravitn
    seata.transport.thread-factory.boss-thread-size=1
    seata.transport.thread-factory.server-executor-thread-prefix=NettyServerBizHandler
    seata.transport.thread-factory.share-boss-worker=false
    seata.transport.thread-factory.worker-thread-prefix=NettyServerNIOWorker
    seata.transport.thread-factory.worker-thread-size=Default
    seata.transport.thread-factory.client-worker-thread-prefix=NettyClientWorkerThread
    seata.transport.thread-factory.client-selector-thread-prefix=NettyClientSelector
    seata.transport.thread-factory.client-selector-thread-size=1
    

    注意seata.service.disable-global-transaction=false配置,默认为false,如果为true,则全局事务无效。

    订单服务配置

    # seata
    
    seata.enabled=true
    # SeataProperties
    seata.application-id=comosus-boot
    seata.tx-service-group=comosus-boot-tx
    # SpringCloudAlibabaConfiguration
    #spring.cloud.alibaba.seata.application-id=comosus-boot
    #spring.cloud.alibaba.seata.tx-service-group=comosus-boot-tx
    seata.data-source-proxy-mode=AT
    seata.enable-auto-data-source-proxy=true
    seata.use-jdk-proxy=false
    
    ## seata service
    seata.service.disable-global-transaction=false
    seata.service.enable-degrade=false
    seata.service.vgroup-mapping.comosus-boot-tx=tc-cluster
    #only support when registry.type=file, please don't set multiple addresses
    seata.service.grouplist.tc-cluster=127.0.0.1:8091
    
    ## registry zk
    #seata.registry.type=file
    seata.registry.zk.server-addr=127.0.0.1:2181
    seata.registry.zk.session-timeout=6000
    seata.registry.zk.connect-timeout=2000
    #seata.registry.zk.username=
    #seata.registry.zk.password=
    seata.registry.load-balance=RoundRobinLoadBalance
    seata.registry.load-balance-virtual-nodes=10
    
    
    ## seata config
    
    ### file config
    #seata.config.type=file
    #seata.config.type=springCloudConfig
    #seata.config.file.name=file.conf
    
    ### nacos config
    #seata.config.type=nacos
    #seata.config.nacos.group=comosus-boot-seata-group
    #seata.config.nacos.server-addr=localhost
    #seata.config.nacos.username=
    #seata.config.nacos.password=
    #seata.config.nacos.namespace=
    
    
    
    ## seata client
    ### tm
    seata.client.tm.commit-retry-count=5
    seata.client.tm.rollback-retry-count=5
    seata.client.tm.default-global-transaction-timeout=60000
    seata.client.tm.degrade-check-allow-times=10
    seata.client.tm.degrade-check=false
    seata.client.tm.degrade-check-period=2000
    
    ### rm
    seata.client.rm.lock.retry-times=30
    seata.client.rm.async-commit-buffer-limit=10000
    seata.client.rm.lock.retry-interval=10
    seata.client.rm.lock.retry-policy-branch-rollback-on-conflict=true
    seata.client.rm.table-meta-check-enable=false
    seata.client.rm.report-retry-count=5
    seata.client.rm.report-success-enable=false
    seata.client.rm.saga-json-parser=fastjson
    seata.client.rm.saga-branch-register-enable=false
    
    ### undo log
    seata.client.undo.log-table=undo_log
    seata.client.undo.data-validation=true
    seata.client.undo.log-serialization=fastjson
    seata.client.undo.only-care-update-columns=true
    
    
    ## seata transport
    seata.transport.server=nio
    seata.transport.type=tcp
    seata.transport.heartbeat=true
    seata.transport.serialization=seata
    seata.transport.enable-client-batch-send-request=true
    seata.transport.shutdown.wait=3
    # zip
    seata.transport.compressor=none
    seata.transport.thread-factory.boss-thread-prefix=comosus-boot
    seata.transport.thread-factory.boss-thread-size=1
    seata.transport.thread-factory.server-executor-thread-prefix=NettyServerBizHandler
    seata.transport.thread-factory.share-boss-worker=false
    seata.transport.thread-factory.worker-thread-prefix=NettyServerNIOWorker
    seata.transport.thread-factory.worker-thread-size=Default
    seata.transport.thread-factory.client-worker-thread-prefix=NettyClientWorkerThread
    seata.transport.thread-factory.client-selector-thread-prefix=NettyClientSelector
    seata.transport.thread-factory.client-selector-thread-size=1
    

    TC配置

    配置server, 从seata的github仓库下载相应的tc server;配置文件在conf目录下的file.conf,我们使用的是db方式具体配置如下:

    ## transaction log store, only used in seata-server
    store {
      ## store mode: file、db、redis
      mode = "db"
    
      ## 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
      }
    
      ## database store property
      db {
        ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
        datasource = "druid"
        ## mysql/oracle/postgresql/h2/oceanbase etc.
        dbType = "mysql"
        driverClassName = "com.mysql.jdbc.Driver"
        url = "jdbc:mysql://127.0.0.1:3306/seata"
        user = "root"
        password = "123456"
        minConn = 5
        maxConn = 100
        globalTable = "global_table"
        branchTable = "branch_table"
        lockTable = "lock_table"
        queryLimit = 100
        maxWait = 5000
      }
    
      ## redis store property
      redis {
        host = "127.0.0.1"
        port = "6379"
        password = ""
        database = "0"
        minConn = 1
        maxConn = 10
        maxTotal = 100
        queryLimit = 100
      }
    
    }
    lock {
      ## the lock store mode: local、remote
      mode = "remote"
    
      local {
        ## store locks in user's database
      }
    
      remote {
        ## store locks in the seata's server
      }
    }
    recovery {
      #schedule committing retry period in milliseconds
      committing-retry-period = 1000
      #schedule asyn committing retry period in milliseconds
      asyn-committing-retry-period = 1000
      #schedule rollbacking retry period in milliseconds
      rollbacking-retry-period = 1000
      #schedule timeout retry period in milliseconds
      timeout-retry-period = 1000
    }
    transaction {
      undo.data.validation = true
      undo.log.serialization = "jackson"
      undo.log.save.days = 7
      #schedule delete expired undo_log in milliseconds
      undo.log.delete.period = 86400000
      undo.log.table = "undo_log"
    }
    
    ## metrics settings
    metrics {
      enabled = false
      registry-type = "compact"
      # multi exporters use comma divided
      exporter-list = "prometheus"
      exporter-prometheus-port = 9898
    }
    
    support {
      ## spring
      spring {
        # auto proxy the DataSource bean
        datasource.autoproxy = false
      }
    }
    

    接下来我们来验证事务。

    事务验证

    先启动TC服务

     seata-server.bat -p 8091 -h 127.0.0.1 -m db
    

    然后分别启动
    两个应用
    comosus-boot: 订单服务;
    comosus-ravitn:仓储服务;帐户服务;

    TC控制台日志如下:

    20:23:09.996  INFO --- [rverHandlerThread_1_1_500] i.s.c.r.processor.server.RegRmProcessor  : RM register success,message:RegisterRMRequest{resourceIds='jdbc:mysql://localhost:3306/comosus-boot', applicationId='comosus-boot', transactionServiceGroup='comosus-boot-tx'},channel:[id: 0x2241d2d0, L:/127.0.0.1:8091 - R:/127.0.0.1:56709],client version:1.4.1
    20:23:10.060  INFO --- [rverHandlerThread_1_2_500] i.s.c.r.processor.server.RegRmProcessor  : RM register success,message:RegisterRMRequest{resourceIds='jdbc:mysql://localhost:3306/comosus-boot', applicationId='comosus-boot', transactionServiceGroup='comosus-boot-tx'},channel:[id: 0x2241d2d0, L:/127.0.0.1:8091 - R:/127.0.0.1:56709],client version:1.4.1
    20:23:28.886  INFO --- [rverHandlerThread_1_3_500] i.s.c.r.processor.server.RegRmProcessor  : RM register success,message:RegisterRMRequest{resourceIds='jdbc:mysql://localhost:3306/comosus-ravitn', applicationId='comosus-ravitn', transactionServiceGroup='comosus-ravitn-tx'},channel:[id: 0x9f8692e2, L:/127.0.0.1:8091 - R:/127.0.0.1:56780],client version:1.4.1
    20:23:28.954  INFO --- [rverHandlerThread_1_4_500] i.s.c.r.processor.server.RegRmProcessor  : RM register success,message:RegisterRMRequest{resourceIds='jdbc:mysql://localhost:3306/comosus-ravitn', applicationId='comosus-ravitn', transactionServiceGroup='comosus-ravitn-tx'},channel:[id: 0x9f8692e2, L:/127.0.0.1:8091 - R:/127.0.0.1:56780],client version:1.4.1
    20:24:01.842  INFO --- [ettyServerNIOWorker_1_3_8] i.s.c.r.processor.server.RegTmProcessor  : TM register success,message:RegisterTMRequest{applicationId='comosus-boot', transactionServiceGroup='comosus-boot-tx'},channel:[id: 0x4b144e0e, L:/127.0.0.1:8091 - R:/127.0.0.1:56840],client version:1.4.1
    20:24:20.302  INFO --- [ettyServerNIOWorker_1_4_8] i.s.c.r.processor.server.RegTmProcessor  : TM register success,message:RegisterTMRequest{applicationId='comosus-ravitn', transactionServiceGroup='comosus-ravitn-tx'},channel:[id: 0x199abf63, L:/127.0.0.1:8091 - R:/127.0.0.1:56861],client version:1.4.1
    

    从上面日志可以看出,comosus-ravitn(TM、RM),comosus-boot(TM、RM)注册到TC服务中心。

    接下来我们验证正常的业务,为了便于观察我们这个过程,我们在业务中添加断点,具体为

    result =  accountRemoteService.debit(userId,money);
    

    正常事务

    先来看一下初始数据(comosus-ravitn:仓储服务;帐户服务):

    mysql> select * from ravitn_account;
    +----+------------+------------+-----------+--------+---------+---------------------+---------------------+
    | id | user_id    | money      | is_delete | remark | version | update_time         | create_time         |
    +----+------------+------------+-----------+--------+---------+---------------------+---------------------+
    |  1 | CU00000001 | 1000.00000 |         0 | NULL   |       1 | 2021-01-05 14:37:45 | 2021-01-05 14:37:42 |
    +----+------------+------------+-----------+--------+---------+---------------------+---------------------+
    1 row in set
    
    mysql> select * from ravitn_storage;
    +----+----------------+-------+-----------+--------+---------+---------------------+---------------------+
    | id | commodity_code | count | is_delete | remark | version | update_time         | create_time         |
    +----+----------------+-------+-----------+--------+---------+---------------------+---------------------+
    |  1 | CGD0001        |  1000 |         0 | NULL   |       1 | 2021-01-05 14:38:38 | 2021-01-05 14:38:36 |
    +----+----------------+-------+-----------+--------+---------+---------------------+---------------------+
    1 row in set
    
    mysql> 
    

    开启正常事务,调用接口参数如下:

    {
      "commodityCode": "CGD0001",
      "orderCount": 1,
      "userId": "CU00000001"
    }
    

    进入断点,观察一下相关表信息:

    comosus-boot: 订单服务;
    comosus-ravitn:仓储服务;帐户服务;
    TC:seata-sever

    1. comosus-ravitn
    mysql> select * from undo_log;
    +----+-------------------+---------------------------------------+---------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+---------------------+---------------------+------+
    | id | branch_id         | xid                                   | context             | rollback_info                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    | log_status | log_created         | log_modified        | ext  |
    +----+-------------------+---------------------------------------+---------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+---------------------+---------------------+------+
    |  1 | 94531698438029312 | 10.242.214.156:8091:94531694608629760 | serializer=fastjson | {"@type":"io.seata.rm.datasource.undo.BranchUndoLog","branchId":94531698438029312,"sqlUndoLogs":[{"afterImage":{"rows":[{"fields":[{"keyType":"PRIMARY_KEY","name":"id","type":-5,"value":1L},{"keyType":"NULL","name":"commodity_code","type":12,"value":"CGD0001"},{"keyType":"NULL","name":"count","type":-5,"value":999L},{"keyType":"NULL","name":"version","type":-5,"value":2L}]}],"tableName":"ravitn_storage"},"beforeImage":{"rows":[{"fields":[{"keyType":"PRIMARY_KEY","name":"id","type":-5,"value":1L},{"keyType":"NULL","name":"commodity_code","type":12,"value":"CGD0001"},{"keyType":"NULL","name":"count","type":-5,"value":1000L},{"keyType":"NULL","name":"version","type":-5,"value":1L}]}],"tableName":"ravitn_storage"},"sqlType":"UPDATE","tableName":"ravitn_storage"}],"xid":"10.242.214.156:8091:94531694608629760"} |          0 | 2021-01-18 20:35:13 | 2021-01-18 20:35:13 | NULL |
    +----+-------------------+---------------------------------------+---------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+---------------------+---------------------+------+
    1 row in set
    
    1. comosus-boot
    +----+-------------------+---------------------------------------+---------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+---------------------+---------------------+------+
    |  1 | 94531700921057281 | 10.242.214.156:8091:94531694608629760 | serializer=fastjson | {"@type":"io.seata.rm.datasource.undo.BranchUndoLog","branchId":94531700921057281,"sqlUndoLogs":[{"afterImage":{"rows":[{"fields":[{"keyType":"PRIMARY_KEY","name":"id","type":4,"value":1},{"keyType":"NULL","name":"user_id","type":12,"value":"CU00000001"},{"keyType":"NULL","name":"commodity_code","type":12,"value":"CGD0001"},{"keyType":"NULL","name":"count","type":4,"value":1},{"keyType":"NULL","name":"money","type":3,"value":16.80000},{"keyType":"NULL","name":"version","type":-5,"value":1L},{"keyType":"NULL","name":"update_time","type":93,"value":"2021-01-18 20:35:13"},{"keyType":"NULL","name":"create_time","type":93,"value":"2021-01-18 20:35:13"},{"keyType":"NULL","name":"is_delete","type":-7,"value":false},{"keyType":"NULL","name":"remark","type":12}]}],"tableName":"seate_order"},"beforeImage":{"@type":"io.seata.rm.datasource.sql.struct.TableRecords$EmptyTableRecords","rows":[],"tableName":"seate_order"},"sqlType":"INSERT","tableName":"seate_order"}],"xid":"10.242.214.156:8091:94531694608629760"} |          0 | 2021-01-18 20:35:14 | 2021-01-18 20:35:14 | NULL |
    +----+-------------------+---------------------------------------+---------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+---------------------+---------------------+------+
    1 row in set
    
    mysql> 
    
    1. seata-sever
    mysql> select * from global_table;
    +---------------------------------------+-------------------+--------+----------------+---------------------------+------------------+---------+---------------+------------------+---------------------+---------------------+
    | xid                                   | transaction_id    | status | application_id | transaction_service_group | transaction_name | timeout | begin_time    | application_data | gmt_create          | gmt_modified        |
    +---------------------------------------+-------------------+--------+----------------+---------------------------+------------------+---------+---------------+------------------+---------------------+---------------------+
    | 10.242.214.156:8091:94531694608629760 | 94531694608629760 |      6 | comosus-boot   | comosus-boot-tx           | purchase-tx      |   30000 | 1610973312308 | NULL             | 2021-01-18 20:35:12 | 2021-01-18 20:35:42 |
    +---------------------------------------+-------------------+--------+----------------+---------------------------+------------------+---------+---------------+------------------+---------------------+---------------------+
    1 row in set
    
    mysql> select * from branch table;
    1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'table' at line 1
    mysql> select * from branch_table;
    +-------------------+---------------------------------------+-------------------+-------------------+--------------------------------------------+----------+-------------+--------+--------------------------------+------------------+---------------------+---------------------+
    | branch_id         | xid                                   | transaction_id    | resource_group_id | resource_id                                | lock_key | branch_type | status | client_id                      | application_data | gmt_create          | gmt_modified        |
    +-------------------+---------------------------------------+-------------------+-------------------+--------------------------------------------+----------+-------------+--------+--------------------------------+------------------+---------------------+---------------------+
    | 94531698438029312 | 10.242.214.156:8091:94531694608629760 | 94531694608629760 | NULL              | jdbc:mysql://localhost:3306/comosus-ravitn | NULL     | AT          |      0 | comosus-ravitn:127.0.0.1:56780 | NULL             | 2021-01-18 20:35:13 | 2021-01-18 20:35:13 |
    | 94531700921057281 | 10.242.214.156:8091:94531694608629760 | 94531694608629760 | NULL              | jdbc:mysql://localhost:3306/comosus-boot   | NULL     | AT          |      0 | comosus-boot:127.0.0.1:56709   | NULL             | 2021-01-18 20:35:14 | 2021-01-18 20:35:14 |
    +-------------------+---------------------------------------+-------------------+-------------------+--------------------------------------------+----------+-------------+--------+--------------------------------+------------------+---------------------+---------------------+
    2 rows in set
    
    mysql> select * from lock_table;
    +-----------------------------------------------------------------+---------------------------------------+-------------------+-------------------+--------------------------------------------+----------------+----+---------------------+---------------------+
    | row_key                                                         | xid                                   | transaction_id    | branch_id         | resource_id                                | table_name     | pk | gmt_create          | gmt_modified        |
    +-----------------------------------------------------------------+---------------------------------------+-------------------+-------------------+--------------------------------------------+----------------+----+---------------------+---------------------+
    | jdbc:mysql://localhost:3306/comosus-boot^^^seate_order^^^1      | 10.242.214.156:8091:94531694608629760 | 94531694608629760 | 94531700921057281 | jdbc:mysql://localhost:3306/comosus-boot   | seate_order    | 1  | 2021-01-18 20:35:13 | 2021-01-18 20:35:13 |
    | jdbc:mysql://localhost:3306/comosus-ravitn^^^ravitn_storage^^^1 | 10.242.214.156:8091:94531694608629760 | 94531694608629760 | 94531698438029312 | jdbc:mysql://localhost:3306/comosus-ravitn | ravitn_storage | 1  | 2021-01-18 20:35:13 | 2021-01-18 20:35:13 |
    +-----------------------------------------------------------------+---------------------------------------+-------------------+-------------------+--------------------------------------------+----------------+----+---------------------+---------------------+
    2 rows in set
    
    

    从上面可以看出,有comosus-boot开启了一个全局事务,TM为comosus-boot, 两个事务分支,分别为jdbc:mysql://localhost:3306/comosus-ravitn和 jdbc:mysql://localhost:3306/comosus-boot,以及相关的表行锁。comosus-boot和comosus-ravitn分别记录相应的undo_log.

    在没有断点的情况,正常情况,上面的全局事务信息,和事务分支,table锁及undo-log都是中间过程,如果全局事务完成,则将会删除相关的中间终态。

    正常的完成事务后,相关数据为

    仓单和账户数据如下:

    mysql> select * from ravitn_storage;
    +----+----------------+-------+-----------+--------+---------+---------------------+---------------------+
    | id | commodity_code | count | is_delete | remark | version | update_time         | create_time         |
    +----+----------------+-------+-----------+--------+---------+---------------------+---------------------+
    |  1 | CGD0001        |   999 |         0 | NULL   |       2 | 2021-01-18 20:40:09 | 2021-01-05 14:38:36 |
    +----+----------------+-------+-----------+--------+---------+---------------------+---------------------+
    1 row in set
    
    mysql> select * from ravitn_account;
    +----+------------+-----------+-----------+--------+---------+---------------------+---------------------+
    | id | user_id    | money     | is_delete | remark | version | update_time         | create_time         |
    +----+------------+-----------+-----------+--------+---------+---------------------+---------------------+
    |  1 | CU00000001 | 983.20000 |         0 | NULL   |       2 | 2021-01-18 20:40:09 | 2021-01-05 14:37:42 |
    +----+------------+-----------+-----------+--------+---------+---------------------+---------------------+
    1 row in set
    
    mysql> 
    

    订单数据如下:

    mysql> select * from seate_order;
    +----+------------+----------------+-------+----------+---------+---------------------+---------------------+-----------+--------+
    | id | user_id    | commodity_code | count | money    | version | update_time         | create_time         | is_delete | remark |
    +----+------------+----------------+-------+----------+---------+---------------------+---------------------+-----------+--------+
    |  2 | CU00000001 | CGD0001        |     1 | 16.80000 |       1 | 2021-01-18 20:40:09 | 2021-01-18 20:40:09 |         0 | NULL   |
    +----+------------+----------------+-------+----------+---------+---------------------+---------------------+-----------+--------+
    1 row in set
    
    mysql> 
    

    异常事务

    全局事务我们,我们需要引入断点,观察整个过程, 准备数据如下:

    {
      "commodityCode": "CGD0001",
      "orderCount": 1,
      "userId": "CU00000002"
    }
    

    进入断点,观察一下相关表信息:

    comosus-boot: 订单服务;
    comosus-ravitn:仓储服务;帐户服务;
    TC:seata-sever

    订单数据

    mysql> select * from seate_order;
    +----+------------+----------------+-------+----------+---------+---------------------+---------------------+-----------+--------+
    | id | user_id    | commodity_code | count | money    | version | update_time         | create_time         | is_delete | remark |
    +----+------------+----------------+-------+----------+---------+---------------------+---------------------+-----------+--------+
    |  2 | CU00000001 | CGD0001        |     1 | 16.80000 |       1 | 2021-01-18 20:40:09 | 2021-01-18 20:40:09 |         0 | NULL   |
    |  3 | CU00000002 | CGD0001        |     1 | 16.80000 |       1 | 2021-01-18 20:55:55 | 2021-01-18 20:55:55 |         0 | NULL   |
    +----+------------+----------------+-------+----------+---------+---------------------+---------------------+-----------+--------+
    2 rows in set
    
    mysql> 
    

    库存数据

    mysql> select * from ravitn_storage;
    +----+----------------+-------+-----------+--------+---------+---------------------+---------------------+
    | id | commodity_code | count | is_delete | remark | version | update_time         | create_time         |
    +----+----------------+-------+-----------+--------+---------+---------------------+---------------------+
    |  1 | CGD0001        |   998 |         0 | NULL   |       3 | 2021-01-18 20:55:54 | 2021-01-05 14:38:36 |
    +----+----------------+-------+-----------+--------+---------+---------------------+---------------------+
    1 row in set
    
    

    异常发生,事务回滚后

    库存数据

    mysql> select * from seate_order;
    +----+------------+----------------+-------+----------+---------+---------------------+---------------------+-----------+--------+
    | id | user_id    | commodity_code | count | money    | version | update_time         | create_time         | is_delete | remark |
    +----+------------+----------------+-------+----------+---------+---------------------+---------------------+-----------+--------+
    |  2 | CU00000001 | CGD0001        |     1 | 16.80000 |       1 | 2021-01-18 20:40:09 | 2021-01-18 20:40:09 |         0 | NULL   |
    +----+------------+----------------+-------+----------+---------+---------------------+---------------------+-----------+--------+
    1 row in set
    
    mysql> select * from ravitn_storage;
    +----+----------------+-------+-----------+--------+---------+---------------------+---------------------+
    | id | commodity_code | count | is_delete | remark | version | update_time         | create_time         |
    +----+----------------+-------+-----------+--------+---------+---------------------+---------------------+
    |  1 | CGD0001        |   999 |         0 | NULL   |       2 | 2021-01-18 20:57:02 | 2021-01-05 14:38:36 |
    +----+----------------+-------+-----------+--------+---------+---------------------+---------------------+
    1 row in set
    

    从上面可以看出,在账户扣款失败之前,已经减库存,并生成订单,在异常发生,回滚事务后,则根据undo_log回滚相关数据。

    在这个过程中, 全局事务信息,和事务分支,table锁及undo-log都存在相应的事务相关数据,如果全局事务完成包括异常,则将会删除相关的中间状。

    我们把断点放到if语句来看一下分支表和lock表

    result =  accountRemoteService.debit(userId,money);
    if(!result){
           log.error("OrderFacadeService purchase  account debit error");
    }
    

    具体数据如下:

    mysql> select * from global_table;
    +---------------------------------------+-------------------+--------+----------------+---------------------------+------------------+---------+---------------+------------------+---------------------+---------------------+
    | xid                                   | transaction_id    | status | application_id | transaction_service_group | transaction_name | timeout | begin_time    | application_data | gmt_create          | gmt_modified        |
    +---------------------------------------+-------------------+--------+----------------+---------------------------+------------------+---------+---------------+------------------+---------------------+---------------------+
    | 10.242.214.156:8091:94744863792807936 | 94744863792807936 |      6 | comosus-boot   | comosus-boot-tx           | purchase-tx      |   30000 | 1611024135802 | NULL             | 2021-01-19 10:42:15 | 2021-01-19 10:42:46 |
    +---------------------------------------+-------------------+--------+----------------+---------------------------+------------------+---------+---------------+------------------+---------------------+---------------------+
    1 row in set
    
    mysql> select * from branch_table;
    +-------------------+---------------------------------------+-------------------+-------------------+--------------------------------------------+----------+-------------+--------+--------------------------------+------------------+---------------------+---------------------+
    | branch_id         | xid                                   | transaction_id    | resource_group_id | resource_id                                | lock_key | branch_type | status | client_id                      | application_data | gmt_create          | gmt_modified        |
    +-------------------+---------------------------------------+-------------------+-------------------+--------------------------------------------+----------+-------------+--------+--------------------------------+------------------+---------------------+---------------------+
    | 94744867513155584 | 10.242.214.156:8091:94744863792807936 | 94744863792807936 | NULL              | jdbc:mysql://localhost:3306/comosus-ravitn | NULL     | AT          |      0 | comosus-ravitn:127.0.0.1:62241 | NULL             | 2021-01-19 10:42:17 | 2021-01-19 10:42:17 |
    | 94744870000377857 | 10.242.214.156:8091:94744863792807936 | 94744863792807936 | NULL              | jdbc:mysql://localhost:3306/comosus-boot   | NULL     | AT          |      0 | comosus-boot:127.0.0.1:62233   | NULL             | 2021-01-19 10:42:17 | 2021-01-19 10:42:17 |
    | 94744870734381057 | 10.242.214.156:8091:94744863792807936 | 94744863792807936 | NULL              | jdbc:mysql://localhost:3306/comosus-ravitn | NULL     | AT          |      0 | comosus-ravitn:127.0.0.1:62241 | NULL             | 2021-01-19 10:42:17 | 2021-01-19 10:42:17 |
    +-------------------+---------------------------------------+-------------------+-------------------+--------------------------------------------+----------+-------------+--------+--------------------------------+------------------+---------------------+---------------------+
    3 rows in set
    
    mysql> select * from lock_table;
    +-----------------------------------------------------------------+---------------------------------------+-------------------+-------------------+--------------------------------------------+----------------+----+---------------------+---------------------+
    | row_key                                                         | xid                                   | transaction_id    | branch_id         | resource_id                                | table_name     | pk | gmt_create          | gmt_modified        |
    +-----------------------------------------------------------------+---------------------------------------+-------------------+-------------------+--------------------------------------------+----------------+----+---------------------+---------------------+
    | jdbc:mysql://localhost:3306/comosus-boot^^^seate_order^^^3      | 10.242.214.156:8091:94744863792807936 | 94744863792807936 | 94744870000377857 | jdbc:mysql://localhost:3306/comosus-boot   | seate_order    | 3  | 2021-01-19 10:42:17 | 2021-01-19 10:42:17 |
    | jdbc:mysql://localhost:3306/comosus-ravitn^^^ravitn_account^^^1 | 10.242.214.156:8091:94744863792807936 | 94744863792807936 | 94744870734381057 | jdbc:mysql://localhost:3306/comosus-ravitn | ravitn_account | 1  | 2021-01-19 10:42:17 | 2021-01-19 10:42:17 |
    | jdbc:mysql://localhost:3306/comosus-ravitn^^^ravitn_storage^^^1 | 10.242.214.156:8091:94744863792807936 | 94744863792807936 | 94744867513155584 | jdbc:mysql://localhost:3306/comosus-ravitn | ravitn_storage | 1  | 2021-01-19 10:42:16 | 2021-01-19 10:42:16 |
    +-----------------------------------------------------------------+---------------------------------------+-------------------+-------------------+--------------------------------------------+----------------+----+---------------------+---------------------+
    3 rows in set
    
    

    从上面可以看出, TM开启一个全局事务xid,每个服务RM对DB的操作,实际为一个事物分支;通过行记录锁控制数据的并发访问。

    再看看一下undo日志

    订单undo日志

    mysql> select * from undo_log;
    +----+-------------------+---------------------------------------+---------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+---------------------+---------------------+------+
    | id | branch_id         | xid                                   | context             | rollback_info                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         | log_status | log_created         | log_modified        | ext  |
    +----+-------------------+---------------------------------------+---------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+---------------------+---------------------+------+
    |  3 | 94746347146166273 | 10.242.214.156:8091:94746346839982080 | serializer=fastjson | {"@type":"io.seata.rm.datasource.undo.BranchUndoLog","branchId":94746347146166273,"sqlUndoLogs":[{"afterImage":{"rows":[{"fields":[{"keyType":"PRIMARY_KEY","name":"id","type":4,"value":5},{"keyType":"NULL","name":"user_id","type":12,"value":"CU00000001"},{"keyType":"NULL","name":"commodity_code","type":12,"value":"CGD0001"},{"keyType":"NULL","name":"count","type":4,"value":1},{"keyType":"NULL","name":"money","type":3,"value":16.80000},{"keyType":"NULL","name":"version","type":-5,"value":1L},{"keyType":"NULL","name":"update_time","type":93,"value":"2021-01-19 10:48:09"},{"keyType":"NULL","name":"create_time","type":93,"value":"2021-01-19 10:48:09"},{"keyType":"NULL","name":"is_delete","type":-7,"value":false},{"keyType":"NULL","name":"remark","type":12}]}],"tableName":"seate_order"},"beforeImage":{"@type":"io.seata.rm.datasource.sql.struct.TableRecords$EmptyTableRecords","rows":[],"tableName":"seate_order"},"sqlType":"INSERT","tableName":"seate_order"}],"xid":"10.242.214.156:8091:94746346839982080"} |          0 | 2021-01-19 10:48:09 | 2021-01-19 10:48:09 | NULL |
    +----+-------------------+---------------------------------------+---------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+---------------------+---------------------+------+
    1 row in set
    

    具体信息如下:

    context:serializer=fastjson
    rollback_info:

    {
    "@type":"io.seata.rm.datasource.undo.BranchUndoLog",
    "branchId":94746347146166273,"
    sqlUndoLogs":[
    {
    "afterImage":
    {"rows":[{"fields":[{"keyType":"PRIMARY_KEY","name":"id","type":4,"value":5},{"keyType":"NULL","name":"user_id","type":12,"value":"CU00000001"},{"keyType":"NULL","name":"commodity_code","type":12,"value":"CGD0001"},{"keyType":"NULL","name":"count","type":4,"value":1},{"keyType":"NULL","name":"money","type":3,"value":16.80000},{"keyType":"NULL","name":"version","type":-5,"value":1L},{"keyType":"NULL","name":"update_time","type":93,"value":"2021-01-19 10:48:09"},{"keyType":"NULL","name":"create_time","type":93,"value":"2021-01-19 10:48:09"},{"keyType":"NULL","name":"is_delete","type":-7,"value":false},{"keyType":"NULL","name":"remark","type":12}]}],
    "tableName":"seate_order"},
    "beforeImage":{"@type":"io.seata.rm.datasource.sql.struct.TableRecords$EmptyTableRecords","rows":[],"tableName":"seate_order"},"sqlType":"INSERT","tableName":"seate_order"
    }],
    "xid":"10.242.214.156:8091:94746346839982080"
    }
    

    库存undo日志

    mysql> select * from undo_log;
    +----+-------------------+---------------------------------------+---------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+---------------------+---------------------+------+
    | id | branch_id         | xid                                   | context             | rollback_info                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | log_status | log_created         | log_modified        | ext  |
    +----+-------------------+---------------------------------------+---------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+---------------------+---------------------+------+
    |  6 | 94748420306747393 | 10.242.214.156:8091:94748420164141056 | serializer=fastjson | {"@type":"io.seata.rm.datasource.undo.BranchUndoLog","branchId":94748420306747393,"sqlUndoLogs":[{"afterImage":{"rows":[{"fields":[{"keyType":"PRIMARY_KEY","name":"id","type":-5,"value":1L},{"keyType":"NULL","name":"commodity_code","type":12,"value":"CGD0001"},{"keyType":"NULL","name":"count","type":-5,"value":998L},{"keyType":"NULL","name":"version","type":-5,"value":3L}]}],"tableName":"ravitn_storage"},"beforeImage":{"rows":[{"fields":[{"keyType":"PRIMARY_KEY","name":"id","type":-5,"value":1L},{"keyType":"NULL","name":"commodity_code","type":12,"value":"CGD0001"},{"keyType":"NULL","name":"count","type":-5,"value":999L},{"keyType":"NULL","name":"version","type":-5,"value":2L}]}],"tableName":"ravitn_storage"},"sqlType":"UPDATE","tableName":"ravitn_storage"}],"xid":"10.242.214.156:8091:94748420164141056"} |          0 | 2021-01-19 10:56:24 | 2021-01-19 10:56:24 | NULL |
    +----+-------------------+---------------------------------------+---------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+---------------------+---------------------+------+
    1 row in set
    
    mysql> 
    

    具体信息如下:
    context: serializer=fastjson
    rollback_info:

    {"@type":"io.seata.rm.datasource.undo.BranchUndoLog",
    "branchId":94748420306747393,
    "sqlUndoLogs":[
    {"afterImage":
    {"rows":[
    {"fields":
    [{"keyType":"PRIMARY_KEY","name":"id","type":-5,"value":1L},{"keyType":"NULL","name":"commodity_code","type":12,"value":"CGD0001"},{"keyType":"NULL","name":"count","type":-5,"value":998L},{"keyType":"NULL","name":"version","type":-5,"value":3L}]}],
    "tableName":"ravitn_storage"}
    ,"beforeImage":
    {"rows":[
    {"fields":[
    {"keyType":"PRIMARY_KEY","name":"id","type":-5,"value":1L},{"keyType":"NULL","name":"commodity_code","type":12,"value":"CGD0001"},{"keyType":"NULL","name":"count","type":-5,"value":999L},{"keyType":"NULL","name":"version","type":-5,"value":2L}]}],
    "tableName":"ravitn_storage"}
    ,"sqlType":"UPDATE",
    "tableName":"ravitn_storage"}],
    "xid":"10.242.214.156:8091:94748420164141056"}
    

    从上,可以看出每个undo日志会记录包括全局事务id,分支id,日志类型,sql undo日志;sql undo日志由数据表行记录的操作前镜像image(表信息,行记录,行字段类型及数据,操作类型[插入、更新、删除])和操作后的镜像组成,如果事务回滚,则恢复操作前的镜像。

    总结

    TC事务中心管理所有的TM和RM。在服务(RM、TM)启动时,将会注册服务(RM、TM)到TC事务中心。发起的全局业务方作为TM, 具体业务为事务分支RM。开启一个全局事务会在
    TC注册中心生成的全局事务数据(global_table),业务方注册事物分支(branch_table),使用行锁,锁住相关的事务表行记录。并在个业务方,记录业务的undo log。如果分支事务本地都执行提交成功,则删除全局事务、事务分支,锁和undo 日志。如果有一个分支事务本地事务执行失败,则根据跟业务方的undo日志,恢复数据,并删除全局事务、事务分支,锁和undo 日志。SEATA的一个全局事务,如果处理时间长的话,在全局事务执行的过程中,会存在中间业务数据。SEATA的AT事务模式,使用的补偿操作,完成整个事务数据的回滚。TM开启一个全局事务xid,每个服务RM对DB的操作,实际为一个事物分支;通过行记录锁控制数据的并发访问。每个undo日志会记录包括全局事务id,分支id,日志类型,sql undo日志;sql undo日志由数据表行记录的操作前镜像image(表信息,行记录,行字段类型及数据,操作类型[DML ,DDL])和操作后的镜像组成,如果事务回滚,则恢复操作前的镜像。

    seata cn
    seata github
    seata-samples
    分布式事务 Seata Saga 模式首秀以及三种模式详解 | Meetup#3 回顾分布式事务 Seata Saga 模式首秀以及三种模式详解 | Meetup#3 回顾
    分布式事务:Saga模式

    usage

    SpringBoot+Dubbo+MybatisPlus整合Seata分布式事务
    SEATA 快速开始
    Seata新手部署指南(1.4.0版本)

    相关文章

      网友评论

          本文标题:Seata分布式事务AT模式初体验

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