• 微服务(十六)——Seata 分布式事务框架


    分布式事务问题由来

    分布式前

    • 单机单库没这个问题
    • 从1:1 -> 1:N -> N:N

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

    在这里插入图片描述

    一句话:一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题

    Seata术语

    是什么

    Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。

    官方网址

    能干嘛

    一个典型的分布式事务过程

    分布式事务处理过程的一ID+三组件模型:

    • Transaction ID XID 全局唯一的事务ID

    • 三组件概念

      • TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,驱动全局事务提交或回滚。
      • TM (Transaction Manager) - 事务管理器:定义全局事务的范围:开始全局事务、提交或回滚全局事务。
      • RM (Resource Manager) - 资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

    处理过程:

    1. TM向TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID;
    2. XID在微服务调用链路的上下文中传播;
    3. RM向TC注册分支事务,将其纳入XID对应全局事务的管辖;
    4. TM向TC发起针对XID的全局提交或回滚决议;
    5. TC调度XID下管辖的全部分支事务完成提交或回滚请求。

    在这里插入图片描述

    Seata-Server安装

    去哪下

    发布说明: https://github.com/seata/seata/releases

    怎么玩

    本地@Transactional

    全局@GlobalTransactional

    SEATA 的分布式交易解决方案

    在这里插入图片描述

    我们只需要使用一个 @GlobalTransactional 注解在业务方法上:

    Seata-Server安装

    官网地址 - http://seata.io/zh-cn/

    下载版本 - 1.5.2

    当前是20220831,最新版本为 - 1.5.2

    seata-server-1.5.2.zip解压到指定目录

    进入 cd seata

    进入 cd conf

    修改 application.yml: vim application.yml 注意替换自己的 nacos地址

    server:
      port: 7091
     
    spring:
      application:
        name: seata-server
     
    logging:
      config: classpath:logback-spring.xml
      file:
        path: ${user.home}/logs/seata
      #extend:
       # logstash-appender:
        #  destination: 127.0.0.1:4560
        #kafka-appender:
         # bootstrap-servers: 127.0.0.1:9092
          #topic: logback_to_logstash
     
    console:
      user:
        username: seata
        password: seata
     
    seata:
      config:
        # support: nacos, consul, apollo, zk, etcd3
        type: nacos
        nacos:
          server-addr: 127.0.0.1:1111
          namespace:
          group: SEATA_GROUP
          username: nacos
          password: nacos
          data-id: seataServer.properties
      registry:
        # support: nacos, eureka, redis, zk, consul, etcd3, sofa
        type: nacos
        nacos:
          application: seata-server
          server-addr: 127.0.0.1:1111
          group: SEATA_GROUP
          namespace:
          cluster: default
          username: nacos
          password: nacos
     # store:
        # support: file 、 db 、 redis
     #   mode: file
    #  server:
    #    service-port: 8091 #If not configured, the default is '${server.port} + 1000'
      security:
        secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
        tokenValidityInMilliseconds: 1800000
        ignore:
          urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
    

    在这里插入图片描述

    进入 /usr/local/project/seata/script/config-center

    修改 vim config.txt 注意

    #For details about configuration items, see https://seata.io/zh-cn/docs/user/configurations.html
    #Transport configuration, for client and server
    transport.type=TCP
    transport.server=NIO
    transport.heartbeat=true
    transport.enableTmClientBatchSendRequest=false
    transport.enableRmClientBatchSendRequest=true
    transport.enableTcServerBatchSendResponse=false
    transport.rpcRmRequestTimeout=30000
    transport.rpcTmRequestTimeout=30000
    transport.rpcTcRequestTimeout=30000
    transport.threadFactory.bossThreadPrefix=NettyBoss
    transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
    transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
    transport.threadFactory.shareBossWorker=false
    transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
    transport.threadFactory.clientSelectorThreadSize=1
    transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
    transport.threadFactory.bossThreadSize=1
    transport.threadFactory.workerThreadSize=default
    transport.shutdown.wait=3
    transport.serialization=seata
    transport.compressor=none
     
    #Transaction routing rules configuration, only for the client
    service.vgroupMapping.my_tx_group=default
    #If you use a registry, you can ignore it
    service.default.grouplist=10.211.55.5:8091
    service.enableDegrade=false
    service.disableGlobalTransaction=false
     
    #Transaction rule configuration, only for the client
    client.rm.asyncCommitBufferLimit=10000
    client.rm.lock.retryInterval=10
    client.rm.lock.retryTimes=30
    client.rm.lock.retryPolicyBranchRollbackOnConflict=true
    client.rm.reportRetryCount=5
    client.rm.tableMetaCheckEnable=true
    client.rm.tableMetaCheckerInterval=60000
    client.rm.sqlParserType=druid
    client.rm.reportSuccessEnable=false
    client.rm.sagaBranchRegisterEnable=false
    client.rm.sagaJsonParser=fastjson
    client.rm.tccActionInterceptorOrder=-2147482648
    client.tm.commitRetryCount=5
    client.tm.rollbackRetryCount=5
    client.tm.defaultGlobalTransactionTimeout=60000
    client.tm.degradeCheck=false
    client.tm.degradeCheckAllowTimes=10
    client.tm.degradeCheckPeriod=2000
    client.tm.interceptorOrder=-2147482648
    client.undo.dataValidation=true
    client.undo.logSerialization=jackson
    client.undo.onlyCareUpdateColumns=true
    server.undo.logSaveDays=7
    server.undo.logDeletePeriod=86400000
    client.undo.logTable=undo_log
    client.undo.compress.enable=true
    client.undo.compress.type=zip
    client.undo.compress.threshold=64k
    #For TCC transaction mode
    tcc.fence.logTableName=tcc_fence_log
    tcc.fence.cleanPeriod=1h
     
    #Log rule configuration, for client and server
    log.exceptionRate=100
     
    #Transaction storage configuration, only for the server. The file, DB, and redis configuration values are optional.
    #store.mode=file
    #store.lock.mode=file
    #store.session.mode=file
    #Used for password encryption
    #store.publicKey=
     
    #If `store.mode,store.lock.mode,store.session.mode` are not equal to `file`, you can remove the configuration block.
    #store.file.dir=file_store/data
    #store.file.maxBranchSessionSize=16384
    #store.file.maxGlobalSessionSize=512
    #store.file.fileWriteBufferCacheSize=16384
    #store.file.flushDiskMode=async
    #store.file.sessionReloadReadSize=100
     
    #These configurations are required if the `store mode` is `db`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `db`, you can remove the configuration block.
    store.db.datasource=druid
    store.db.dbType=mysql
    store.db.driverClassName=com.mysql.jdbc.Driver
    store.db.url=jdbc:mysql://10.211.55.5:3306/seata?useUnicode=true&rewriteBatchedStatements=true
    store.db.user=root
    store.db.password=123456
    store.db.minConn=5
    store.db.maxConn=30
    store.db.globalTable=global_table
    store.db.branchTable=branch_table
    store.db.distributedLockTable=distributed_lock
    store.db.queryLimit=100
    store.db.lockTable=lock_table
    store.db.maxWait=5000
     
    #These configurations are required if the `store mode` is `redis`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `redis`, you can remove the configuration block.
    #store.redis.mode=single
    #store.redis.single.host=127.0.0.1
    #store.redis.single.port=6379
    #store.redis.sentinel.masterName=
    #store.redis.sentinel.sentinelHosts=
    #store.redis.maxConn=10
    #store.redis.minConn=1
    #store.redis.maxTotal=100
    #store.redis.database=0
    #store.redis.password=
    #store.redis.queryLimit=100
     
    #Transaction rule configuration, only for the server
    server.recovery.committingRetryPeriod=1000
    server.recovery.asynCommittingRetryPeriod=1000
    server.recovery.rollbackingRetryPeriod=1000
    server.recovery.timeoutRetryPeriod=1000
    server.maxCommitRetryTimeout=-1
    server.maxRollbackRetryTimeout=-1
    server.rollbackRetryTimeoutUnlockEnable=false
    server.distributedLockExpireTime=10000
    server.xaerNotaRetryTimeout=60000
    server.session.branchAsyncQueueSize=5000
    server.session.enableBranchAsyncRemove=false
    server.enableParallelRequestHandle=false
     
    #Metrics configuration, only for the server
    metrics.enabled=false
    metrics.registryType=compact
    metrics.exporterList=prometheus
    metrics.exporterPrometheusPort=9898
    

    进入 /usr/local/project/seata/script/config-center/nacos

    执行 sh nacos-config.sh

    在这里插入图片描述

    登录自己的nocas地址
    在这里插入图片描述

    新建数据库表 seata

    执行脚本(脚本位置:seata/script/server/db/mysql.sql)

    -- -------------------------------- The script used when storeMode is 'db' --------------------------------
    -- the table to store GlobalSession data
    CREATE TABLE IF NOT EXISTS `global_table`
    (
        `xid`                       VARCHAR(128) NOT NULL,
        `transaction_id`            BIGINT,
        `status`                    TINYINT      NOT NULL,
        `application_id`            VARCHAR(32),
        `transaction_service_group` VARCHAR(32),
        `transaction_name`          VARCHAR(128),
        `timeout`                   INT,
        `begin_time`                BIGINT,
        `application_data`          VARCHAR(2000),
        `gmt_create`                DATETIME,
        `gmt_modified`              DATETIME,
        PRIMARY KEY (`xid`),
        KEY `idx_status_gmt_modified` (`status` , `gmt_modified`),
        KEY `idx_transaction_id` (`transaction_id`)
    ) ENGINE = InnoDB
      DEFAULT CHARSET = utf8mb4;
    
    -- the table to store BranchSession data
    CREATE TABLE IF NOT EXISTS `branch_table`
    (
        `branch_id`         BIGINT       NOT NULL,
        `xid`               VARCHAR(128) NOT NULL,
        `transaction_id`    BIGINT,
        `resource_group_id` VARCHAR(32),
        `resource_id`       VARCHAR(256),
        `branch_type`       VARCHAR(8),
        `status`            TINYINT,
        `client_id`         VARCHAR(64),
        `application_data`  VARCHAR(2000),
        `gmt_create`        DATETIME(6),
        `gmt_modified`      DATETIME(6),
        PRIMARY KEY (`branch_id`),
        KEY `idx_xid` (`xid`)
    ) ENGINE = InnoDB
      DEFAULT CHARSET = utf8mb4;
    
    -- the table to store lock data
    CREATE TABLE IF NOT EXISTS `lock_table`
    (
        `row_key`        VARCHAR(128) NOT NULL,
        `xid`            VARCHAR(128),
        `transaction_id` BIGINT,
        `branch_id`      BIGINT       NOT NULL,
        `resource_id`    VARCHAR(256),
        `table_name`     VARCHAR(32),
        `pk`             VARCHAR(36),
        `status`         TINYINT      NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking',
        `gmt_create`     DATETIME,
        `gmt_modified`   DATETIME,
        PRIMARY KEY (`row_key`),
        KEY `idx_status` (`status`),
        KEY `idx_branch_id` (`branch_id`),
        KEY `idx_xid_and_branch_id` (`xid` , `branch_id`)
    ) ENGINE = InnoDB
      DEFAULT CHARSET = utf8mb4;
    
    CREATE TABLE IF NOT EXISTS `distributed_lock`
    (
        `lock_key`       CHAR(20) NOT NULL,
        `lock_value`     VARCHAR(20) NOT NULL,
        `expire`         BIGINT,
        primary key (`lock_key`)
    ) ENGINE = InnoDB
      DEFAULT CHARSET = utf8mb4;
    
    INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0);
    INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0);
    INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0);
    INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0);
    

    进入 /usr/local/project/seata/bin

    启动 seata

    ./seata-server.sh

    在这里插入图片描述

    启动完成会注册到 nacos 中

    在这里插入图片描述

    至此Seata-Server安装完成,并服务注入nacos

    Seata业务数据库准备

    以下演示都需要先启动Nacos后启动Seata,保证两个都OK。

    分布式事务业务说明

    这里我们会创建三个服务,一个订单服务,一个库存服务,一个账户服务。

    当用户下单时,会在订单服务中创建一个订单, 然后通过远程调用库存服务来扣减下单商品的库存,再通过远程调用账户服务来扣减用户账户里面的余额,最后在订单服务中修改订单状态为已完成。

    该操作跨越三个数据库,有两次远程调用,很明显会有分布式事务问题。

    一言蔽之,下订单—>扣库存—>减账户(余额)。

    创建业务数据库

    • seata_order:存储订单的数据库;
    • seata_storage:存储库存的数据库;
    • seata_account:存储账户信息的数据库。

    建库SQL

    CREATE DATABASE seata_order;
    CREATE DATABASE seata_storage;
    CREATE DATABASE seata_account;
    

    按照上述3库分别建对应业务表

    • seata_order库下建t_order表
    CREATE TABLE t_order (
        `id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
        `user_id` BIGINT(11) DEFAULT NULL COMMENT '用户id',
        `product_id` BIGINT(11) DEFAULT NULL COMMENT '产品id',
        `count` INT(11) DEFAULT NULL COMMENT '数量',
        `money` DECIMAL(11,0) DEFAULT NULL COMMENT '金额',
        `status` INT(1) DEFAULT NULL COMMENT '订单状态: 0:创建中; 1:已完结'
    ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
    
    SELECT * FROM t_order;
    
    • seata_storage库下建t_storage表
    CREATE TABLE t_storage (
    `id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
    `product_id` BIGINT(11) DEFAULT NULL COMMENT '产品id',
    `total` INT(11) DEFAULT NULL COMMENT '总库存',
    `used` INT(11) DEFAULT NULL COMMENT '已用库存',
    `residue` INT(11) DEFAULT NULL COMMENT '剩余库存'
    ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
    
    INSERT INTO seata_storage.t_storage(`id`, `product_id`, `total`, `used`, `residue`)
    VALUES ('1', '1', '100', '0','100');
    
    SELECT * FROM t_storage;
    
    • seata_account库下建t_account表
    CREATE TABLE t_account(
    	`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'id',
    	`user_id` BIGINT(11) DEFAULT NULL COMMENT '用户id',
    	`total` DECIMAL(10,0) DEFAULT NULL COMMENT '总额度',
    	`used` DECIMAL(10,0) DEFAULT NULL COMMENT '已用余额',
    	`residue` DECIMAL(10,0) DEFAULT '0' COMMENT '剩余可用额度'
    ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
    
    INSERT INTO seata_account.t_account(`id`, `user_id`, `total`, `used`, `residue`)
    VALUES ('1', '1', '1000', '0', '1000');
    
    SELECT * FROM t_account;
    

    按照上述3库分别建对应的回滚日志表

    • 订单-库存-账户3个库下都需要建各自的回滚日志表
    • \seata-server-0.9.0\seata\conf目录下的db_ undo_ log.sql
    • 建表SQL
    -- the table to store seata xid data
    -- 0.7.0+ add context
    -- you must to init this sql for you business databese. the seata server not need it.
    -- 此脚本必须初始化在你当前的业务数据库中,用于AT 模式XID记录。与server端无关(注:业务数据库)
    -- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
    drop table `undo_log`;
    CREATE TABLE `undo_log` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `branch_id` bigint(20) NOT NULL,
      `xid` varchar(100) NOT NULL,
      `context` varchar(128) NOT NULL,
      `rollback_info` longblob NOT NULL,
      `log_status` int(11) NOT NULL,
      `log_created` datetime NOT NULL,
      `log_modified` datetime NOT NULL,
      `ext` varchar(100) DEFAULT NULL,
      PRIMARY KEY (`id`),
      UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
    

    Seata之Order-Module配置搭建

    下订单 -> 减库存 -> 扣余额 -> 改(订单)状态

    seata-order-service2001

    POM

    
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>springcloudartifactId>
            <groupId>cn.ktgroupId>
            <version>1.0-SNAPSHOTversion>
        parent>
        <modelVersion>4.0.0modelVersion>
    
        <artifactId>seata-order-service2001artifactId>
    
        <properties>
            <maven.compiler.source>8maven.compiler.source>
            <maven.compiler.target>8maven.compiler.target>
        properties>
    
        <dependencies>
            
            <dependency>
                <groupId>com.alibaba.cloudgroupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
            dependency>
            
            <dependency>
                <groupId>com.alibaba.cloudgroupId>
                <artifactId>spring-cloud-starter-alibaba-seataartifactId>
                <exclusions>
                    <exclusion>
                        <artifactId>seata-spring-boot-starterartifactId>
                        <groupId>io.seatagroupId>
                    exclusion>
                exclusions>
            dependency>
            <dependency>
                <groupId>io.seatagroupId>
                <artifactId>seata-spring-boot-starterartifactId>
                <version>1.5.2version>
            dependency>
            
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-openfeignartifactId>
            dependency>
            
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-actuatorartifactId>
            dependency>
            
            <dependency>
                <groupId>mysqlgroupId>
                <artifactId>mysql-connector-javaartifactId>
                <version>5.1.6version>
            dependency>
            <dependency>
                <groupId>com.alibabagroupId>
                <artifactId>druid-spring-boot-starterartifactId>
                <version>1.1.10version>
            dependency>
            <dependency>
                <groupId>org.mybatis.spring.bootgroupId>
                <artifactId>mybatis-spring-boot-starterartifactId>
                <version>2.0.0version>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-testartifactId>
                <scope>testscope>
            dependency>
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
                <optional>trueoptional>
            dependency>
        dependencies>
    project>
    

    配置文件

    YML

    server:
      port: 2001
    
    spring:
      application:
        name: seata-order-service
      cloud:
        nacos:
          discovery:
            server-addr: 10.211.55.5:1111
      datasource:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://10.211.55.5:3306/seata_order
        username: root
        password: 123456
    
    feign:
      hystrix:
        enabled: false
    
    logging:
      level:
        io:
          seata: info
    
    mybatis:
      mapperLocations: classpath:mapper/*.xml
    
    seata:
      tx-service-group: my_tx_group
      config:
        type: nacos
        nacos:
          server-addr: 10.211.55.5:1111 #填线上Nacos的IP:PORT
          namespace: #命名空间,使用默认的不用填
          group: SEATA_GROUP #组
          username: nacos
          password: nacos
          data-id: seataServer.properties #seataServer配置文件
      registry:
        type: nacos
        nacos:
          application: seata-server #注册服务名称
          server-addr: 10.211.55.5:1111 #填线上Nacos的IP:PORT
          group: SEATA_GROUP #组
          namespace: #命名空间,使用默认的不用填
          cluster: default
          username: nacos
          password: nacos
      store:
        mode: db
        db:
          datasource: druid
          db-type: mysql
          driver-class-name: com.mysql.jdbc.Driver
          url: jdbc:mysql://10.211.55.5:3306/seata?serverTimezone=Asia/Shanghai #填线上Mysql的IP:PORT,seata数据库名,要和前面插入seata表同一个名
          user: root #数据库账号
          password: 123456 #数据库密码
          min-conn: 5
          max-conn: 100
          global-table: global_table
          branch-table: branch_table
          lock-table: lock_table
          distributed-lock-table: distributed_lock
          query-limit: 100
          max-wait: 5000
      service:
        vgroup-mapping:
          my_tx_group: my_tx_group
        grouplist:
          default: 10.211.55.5:8091
    
    

    特别要注意配置问题

    在这里插入图片描述

    domain

    package cn.kt.springcloud.domain;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    /**
     * Created by tao.
     * Date: 2022/9/4 10:45
     * 描述:
     */
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class CommonResult<T> {
        private Integer code;
        private String message;
        private T data;
    
        public CommonResult(Integer code, String message) {
            this(code, message, null);
        }
    }
    
    package cn.kt.springcloud.domain;
    
    
    import com.fasterxml.jackson.annotation.JsonFormat;
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    import java.math.BigDecimal;
    
    /**
     * Created by tao.
     * Date: 2022/9/4 10:45
     * 描述:
     */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Order {
        private Long id;
    
        private Long userId;
    
        private Long productId;
    
        private Integer count;
    
        @JsonFormat(shape = JsonFormat.Shape.STRING)
        private BigDecimal money;
    
        private Integer status; //订单状态:0:创建中;1:已完结
    }
    

    Seata之Order-Module撸码(上)

    Dao接口及实现

    package cn.kt.springcloud.dao;
    
    import cn.kt.springcloud.domain.Order;
    import org.apache.ibatis.annotations.Mapper;
    import org.apache.ibatis.annotations.Param;
    
    /**
     * Created by tao.
     * Date: 2022/9/4 10:46
     * 描述:
     */
    
    @Mapper
    public interface OrderDao {
        //1 新建订单
        void create(Order order);
    
        //2 修改订单状态,从零改为1
        void update(@Param("userId") Long userId, @Param("status") Integer status);
    }
    
    
    DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
    
    <mapper namespace="cn.kt.springcloud.dao.OrderDao">
        <resultMap id="BaseResultMap" type="cn.kt.springcloud.domain.Order">
            <id column="id" property="id" jdbcType="BIGINT"/>
            <result column="user_id" property="userId" jdbcType="BIGINT"/>
            <result column="product_id" property="productId" jdbcType="BIGINT"/>
            <result column="count" property="count" jdbcType="INTEGER"/>
            <result column="money" property="money" jdbcType="DECIMAL"/>
            <result column="status" property="status" jdbcType="INTEGER"/>
        resultMap>
    
        <insert id="create">
            insert into t_order (id, user_id, product_id, count, money, status)
            values (null, #{userId}, #{productId}, #{count}, #{money}, 0);
        insert>
    
    
        <update id="update">
            update t_order
            set status = 1
            where user_id = #{userId}
              and status = #{status};
        update>
    mapper>
    

    Service接口及实现

    • OrderService

      • OrderServiceImpl
    • StorageService

    • AccountService

    package cn.kt.springcloud.service;
    
    import cn.kt.springcloud.domain.Order;
    
    /**
     * Created by tao.
     * Date: 2022/9/4 10:55
     * 描述:
     */
    public interface OrderService {
        void create(Order order);
    }
    
    package cn.kt.springcloud.service;
    
    import cn.kt.springcloud.domain.CommonResult;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    
    /**
     * Created by tao.
     * Date: 2022/9/4 11:02
     * 描述:
     */
    
    import java.math.BigDecimal;
    
    @FeignClient(value = "seata-storage-service")
    public interface StorageService {
        @PostMapping(value = "/storage/decrease")
        CommonResult decrease(@RequestParam("productId") Long productId, @RequestParam("count") Integer count);
    }
    
    package cn.kt.springcloud.service;
    
    import cn.kt.springcloud.domain.CommonResult;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    
    import java.math.BigDecimal;
    
    /**
     * Created by tao.
     * Date: 2022/9/4 11:01
     * 描述:
     */
    
    @FeignClient(value = "seata-account-service")
    public interface AccountService {
        @PostMapping(value = "/account/decrease")
        CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
    }
    
    package cn.kt.springcloud.service.impl;
    
    import cn.kt.springcloud.dao.OrderDao;
    import cn.kt.springcloud.domain.Order;
    import cn.kt.springcloud.service.AccountService;
    import cn.kt.springcloud.service.OrderService;
    import cn.kt.springcloud.service.StorageService;
    import io.seata.spring.annotation.GlobalTransactional;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Service;
    
    import javax.annotation.Resource;
    
    /**
     * Created by tao.
     * Date: 2022/9/4 11:00
     * 描述:
     */
    
    @Service
    @Slf4j
    public class OrderServiceImpl implements OrderService {
        @Resource
        private OrderDao orderDao;
        @Resource
        private StorageService storageService;
        @Resource
        private AccountService accountService;
    
        /**
         * 创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态
         * 简单说:下订单->扣库存->减余额->改状态
         */
        @Override
        //@GlobalTransactional(name = "my_tx_group",rollbackFor = Exception.class)
        public void create(Order order) {
            log.info("----->开始新建订单");
            //1 新建订单
            orderDao.create(order);
    
            //2 扣减库存
            log.info("----->订单微服务开始调用库存,做扣减Count");
            storageService.decrease(order.getProductId(), order.getCount());
            log.info("----->订单微服务开始调用库存,做扣减end");
    
            //3 扣减账户
            log.info("----->订单微服务开始调用账户,做扣减Money");
            accountService.decrease(order.getUserId(), order.getMoney());
            log.info("----->订单微服务开始调用账户,做扣减end");
    
            //4 修改订单状态,从零到1,1代表已经完成
            log.info("----->修改订单状态开始");
            orderDao.update(order.getUserId(), 0);
            log.info("----->修改订单状态结束");
    
            log.info("----->下订单结束了,O(∩_∩)O哈哈~");
        }
    }
    

    Seata之Order-Module撸码(下)

    Controller

    package cn.kt.springcloud.controller;
    
    import cn.kt.springcloud.domain.CommonResult;
    import cn.kt.springcloud.domain.Order;
    import cn.kt.springcloud.service.OrderService;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.annotation.Resource;
    
    /**
     * Created by tao.
     * Date: 2022/9/4 11:21
     * 描述:
     */
    
    
    @RestController
    public class OrderController {
        @Resource
        private OrderService orderService;
    
    
        @GetMapping("/order/create")
        public CommonResult create(Order order) {
            orderService.create(order);
            return new CommonResult(200, "订单创建成功");
        }
    }
    

    Config配置

    • MyBatisConfig
    • DataSourceProxyConfig
    package cn.kt.springcloud.config;
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * Created by tao.
     * Date: 2022/9/4 11:24
     * 描述:
     */
    
    @Configuration
    @MapperScan({"cn.kt.springcloud.dao"})
    public class MyBatisConfig {
    }
    
    package cn.kt.springcloud.config;
    
    import com.alibaba.druid.pool.DruidDataSource;
    import io.seata.rm.datasource.DataSourceProxy;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.mybatis.spring.SqlSessionFactoryBean;
    import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Primary;
    import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
    
    import javax.sql.DataSource;
    
    /**
     * Created by tao.
     * Date: 2022/9/4 11:24
     * 描述: 使用Seata对数据源进行代理
     */
    @Configuration
    public class DataSourceProxyConfig {
    
        @Value("${mybatis.mapperLocations}")
        private String mapperLocations;
    
        @Bean
        @ConfigurationProperties(prefix = "spring.datasource")
        public DruidDataSource druidDataSource() {
            return new DruidDataSource();
        }
    
        @Primary
        @Bean
        public DataSourceProxy dataSource(DruidDataSource druidDataSource) {
            return new DataSourceProxy(druidDataSource);
        }
    }
    

    主启动

    package cn.kt.springcloud;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    import org.springframework.context.annotation.ComponentScan;
    
    /**
     * Created by tao.
     * Date: 2022/9/4 11:25
     * 描述:
     */
    @EnableDiscoveryClient
    @EnableFeignClients
    //取消数据源的自动创建,而是使用自己定义的
    @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
    public class SeataOrderMainApp2001 {
    
        public static void main(String[] args) {
            SpringApplication.run(SeataOrderMainApp2001.class, args);
        }
    }
    

    Seata之Storage-Module说明

    与seata-order-service2001模块大致相同

    seata-storage-service2002

    POM(与seata-order-service2001模块大致相同)

    YML

    server:
      port: 2002
    
    spring:
      application:
        name: seata-storage-service
      cloud:
        nacos:
          discovery:
            server-addr: 10.211.55.5:1111
      datasource:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://10.211.55.5:3306/seata_storage
        username: root
        password: 123456
    
    feign:
      hystrix:
        enabled: false
    
    logging:
      level:
        io:
          seata: info
    
    mybatis:
      mapperLocations: classpath:mapper/*.xml
    
    seata:
      tx-service-group: my_tx_group
      config:
        type: nacos
        nacos:
          server-addr: 10.211.55.5:1111 #填线上Nacos的IP:PORT
          namespace: #命名空间,使用默认的不用填
          group: SEATA_GROUP #组
          username: nacos
          password: nacos
          data-id: seataServer.properties #seataServer配置文件
      registry:
        type: nacos
        nacos:
          application: seata-server #注册服务名称
          server-addr: 10.211.55.5:1111 #填线上Nacos的IP:PORT
          group: SEATA_GROUP #组
          namespace: #命名空间,使用默认的不用填
          cluster: default
          username: nacos
          password: nacos
      store:
        mode: db
        db:
          datasource: druid
          db-type: mysql
          driver-class-name: com.mysql.jdbc.Driver
          url: jdbc:mysql://10.211.55.5:3306/seata?serverTimezone=Asia/Shanghai #填线上Mysql的IP:PORT,seata数据库名,要和前面插入seata表同一个名
          user: root #数据库账号
          password: 123456 #数据库密码
          min-conn: 5
          max-conn: 100
          global-table: global_table
          branch-table: branch_table
          lock-table: lock_table
          distributed-lock-table: distributed_lock
          query-limit: 100
          max-wait: 5000
      service:
        vgroup-mapping:
          my_tx_group: my_tx_group
        grouplist:
          default: 10.211.55.5:8091
    

    domain

    package cn.kt.springcloud.domain;
    
    import lombok.Data;
    
    /**
     * Created by tao.
     * Date: 2022/9/17 16:21
     * 描述:
     */
    
    
    @Data
    public class Storage {
    
        private Long id;
    
        /**
         * 产品id
         */
        private Long productId;
    
        /**
         * 总库存
         */
        private Integer total;
    
        /**
         * 已用库存
         */
        private Integer used;
    
        /**
         * 剩余库存
         */
        private Integer residue;
    }
    

    CommonResult(与seata-order-service2001模块大致相同)

    Dao接口及实现

    package cn.kt.springcloud.dao;
    
    import org.apache.ibatis.annotations.Mapper;
    import org.apache.ibatis.annotations.Param;
    
    /**
     * Created by tao.
     * Date: 2022/9/17 16:23
     * 描述:
     */
    
    @Mapper
    public interface StorageDao {
    
        //扣减库存
        void decrease(@Param("productId") Long productId, @Param("count") Integer count);
    }
    
    
    DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
    
    <mapper namespace="cn.kt.springcloud.dao.StorageDao">
    
        <resultMap id="BaseResultMap" type="cn.kt.springcloud.domain.Storage">
            <id column="id" property="id" jdbcType="BIGINT"/>
            <result column="product_id" property="productId" jdbcType="BIGINT"/>
            <result column="total" property="total" jdbcType="INTEGER"/>
            <result column="used" property="used" jdbcType="INTEGER"/>
            <result column="residue" property="residue" jdbcType="INTEGER"/>
        resultMap>
    
        <update id="decrease">
            UPDATE
                t_storage
            SET used    = used + #{count},
                residue = residue - #{count}
            WHERE product_id = #{productId}
        update>
    
    mapper>
    

    Service接口及实现

    package cn.kt.springcloud.service;
    
    /**
     * Created by tao.
     * Date: 2022/9/17 16:26
     * 描述:
     */
    public interface StorageService {
        /**
         * 扣减库存
         */
        void decrease(Long productId, Integer count);
    }
    
    package cn.kt.springcloud.service.impl;
    import cn.kt.springcloud.dao.StorageDao;
    import cn.kt.springcloud.service.StorageService ;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import javax.annotation.Resource;
    
    
    /**
     * Created by tao.
     * Date: 2022/9/17 16:27
     * 描述:
     */
    
    @Service
    public class StorageServiceImpl implements StorageService {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(StorageServiceImpl.class);
    
        @Resource
        private StorageDao storageDao;
    
        /**
         * 扣减库存
         */
        @Override
        public void decrease(Long productId, Integer count) {
            LOGGER.info("------->storage-service中扣减库存开始");
            storageDao.decrease(productId,count);
            LOGGER.info("------->storage-service中扣减库存结束");
        }
    }
    

    Controller

    package cn.kt.springcloud.controller;
    
    import cn.kt.springcloud.domain.CommonResult;
    import cn.kt.springcloud.service.StorageService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * Created by tao.
     * Date: 2022/9/17 16:28
     * 描述:
     */
    
    @RestController
    public class StorageController {
    
        @Autowired
        private StorageService storageService;
    
        /**
         * 扣减库存
         */
        @PostMapping("/storage/decrease")
        public CommonResult decrease(Long productId, Integer count) {
            storageService.decrease(productId, count);
            return new CommonResult(200, "扣减库存成功!");
        }
    }
    

    Config配置(与seata-order-service2001模块大致相同)

    主启动

    package cn.kt.springcloud;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    import org.springframework.context.annotation.ComponentScan;
    
    /**
     * Created by tao.
     * Date: 2022/9/4 11:25
     * 描述:
     */
    @EnableDiscoveryClient
    @EnableFeignClients
    //取消数据源的自动创建,而是使用自己定义的
    @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
    public class SeataStorageMainApp2002 {
    
        public static void main(String[] args) {
            SpringApplication.run(SeataStorageMainApp2002.class, args);
        }
    }
    

    Seata之Account-Module说明

    与seata-order-service2001模块大致相同

    seata-account-service2003

    POM(与seata-order-service2001模块大致相同)

    YML

    server:
      port: 2003
    
    spring:
      application:
        name: seata-account-service
      cloud:
        nacos:
          discovery:
            server-addr: 10.211.55.5:1111
      datasource:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://10.211.55.5:3306/seata_account
        username: root
        password: 123456
    
    feign:
      hystrix:
        enabled: false
    
    logging:
      level:
        io:
          seata: info
    
    mybatis:
      mapperLocations: classpath:mapper/*.xml
    
    seata:
      tx-service-group: my_tx_group
      config:
        type: nacos
        nacos:
          server-addr: 10.211.55.5:1111 #填线上Nacos的IP:PORT
          namespace: #命名空间,使用默认的不用填
          group: SEATA_GROUP #组
          username: nacos
          password: nacos
          data-id: seataServer.properties #seataServer配置文件
      registry:
        type: nacos
        nacos:
          application: seata-server #注册服务名称
          server-addr: 10.211.55.5:1111 #填线上Nacos的IP:PORT
          group: SEATA_GROUP #组
          namespace: #命名空间,使用默认的不用填
          cluster: default
          username: nacos
          password: nacos
      store:
        mode: db
        db:
          datasource: druid
          db-type: mysql
          driver-class-name: com.mysql.jdbc.Driver
          url: jdbc:mysql://10.211.55.5:3306/seata?serverTimezone=Asia/Shanghai #填线上Mysql的IP:PORT,seata数据库名,要和前面插入seata表同一个名
          user: root #数据库账号
          password: 123456 #数据库密码
          min-conn: 5
          max-conn: 100
          global-table: global_table
          branch-table: branch_table
          lock-table: lock_table
          distributed-lock-table: distributed_lock
          query-limit: 100
          max-wait: 5000
      service:
        vgroup-mapping:
          my_tx_group: my_tx_group
        grouplist:
          default: 10.211.55.5:8091
    

    domain

    package cn.kt.springcloud.domain;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    import java.math.BigDecimal;
    
    /**
     * Created by tao.
     * Date: 2022/9/17 16:36
     * 描述:
     */
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Account {
    
        private Long id;
    
        /**
         * 用户id
         */
        private Long userId;
    
        /**
         * 总额度
         */
        private BigDecimal total;
    
        /**
         * 已用额度
         */
        private BigDecimal used;
    
        /**
         * 剩余额度
         */
        private BigDecimal residue;
    }
    

    CommonResult(与seata-order-service2001模块大致相同)

    Dao接口及实现

    package cn.kt.springcloud.dao;
    
    import org.apache.ibatis.annotations.Mapper;
    import org.apache.ibatis.annotations.Param;
    
    import java.math.BigDecimal;
    
    /**
     * Created by tao.
     * Date: 2022/9/17 16:41
     * 描述:
     */
    
    @Mapper
    public interface AccountDao {
    
        /**
         * 扣减账户余额
         */
        void decrease(@Param("userId") Long userId, @Param("money") BigDecimal money);
    }
    
    
    DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
    
    <mapper namespace="cn.kt.springcloud.dao.AccountDao">
    
        <resultMap id="BaseResultMap" type="cn.kt.springcloud.domain.Account">
            <id column="id" property="id" jdbcType="BIGINT"/>
            <result column="user_id" property="userId" jdbcType="BIGINT"/>
            <result column="total" property="total" jdbcType="DECIMAL"/>
            <result column="used" property="used" jdbcType="DECIMAL"/>
            <result column="residue" property="residue" jdbcType="DECIMAL"/>
        resultMap>
    
        <update id="decrease">
            UPDATE t_account
            SET residue = residue - #{money},
                used    = used + #{money}
            WHERE user_id = #{userId};
        update>
    
    mapper>
    

    Service接口及实现

    package cn.kt.springcloud.service;
    
    import org.springframework.web.bind.annotation.RequestParam;
    
    import java.math.BigDecimal;
    
    /**
     * Created by tao.
     * Date: 2022/9/17 16:44
     * 描述:
     */
    
    public interface AccountService {
    
        /**
         * 扣减账户余额
         *
         * @param userId 用户id
         * @param money  金额
         */
        void decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
    }
    
    package cn.kt.springcloud.service.impl;
    
    import cn.kt.springcloud.dao.AccountDao;
    import cn.kt.springcloud.service.AccountService;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Service;
    
    import javax.annotation.Resource;
    import java.math.BigDecimal;
    import java.util.concurrent.TimeUnit;
    
    /**
     * Created by tao.
     * Date: 2022/9/17 16:45
     * 描述:
     */
    
    @Service
    public class AccountServiceImpl implements AccountService {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(AccountServiceImpl.class);
    
    
        @Resource
        AccountDao accountDao;
    
        /**
         * 扣减账户余额
         */
        @Override
        public void decrease(Long userId, BigDecimal money) {
            LOGGER.info("------->account-service中扣减账户余额开始");
            //模拟超时异常,全局事务回滚
            //暂停几秒钟线程
            /*try {
                TimeUnit.SECONDS.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }*/
            accountDao.decrease(userId, money);
            LOGGER.info("------->account-service中扣减账户余额结束");
        }
    }
    

    Controller

    package cn.kt.springcloud.controller;
    
    import cn.kt.springcloud.domain.CommonResult;
    import cn.kt.springcloud.service.AccountService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.annotation.Resource;
    import java.math.BigDecimal;
    
    /**
     * Created by tao.
     * Date: 2022/9/17 16:46
     * 描述:
     */
    
    @RestController
    public class AccountController {
    
        @Resource
        AccountService accountService;
    
        /**
         * 扣减账户余额
         */
        @PostMapping("/account/decrease")
        public CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money) {
            accountService.decrease(userId, money);
            return new CommonResult(200, "扣减账户余额成功!");
        }
    }
    

    Config配置(与seata-order-service2001模块大致相同)

    主启动

    package cn.kt.springcloud;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    import org.springframework.context.annotation.ComponentScan;
    
    /**
     * Created by tao.
     * Date: 2022/9/4 11:25
     * 描述:
     */
    @EnableDiscoveryClient
    @EnableFeignClients
    //取消数据源的自动创建,而是使用自己定义的
    @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
    public class SeataAccountMainApp2003 {
    
        public static void main(String[] args) {
            SpringApplication.run(SeataAccountMainApp2003.class, args);
        }
    }
    

    Seata之@GlobalTransactional验证

    下订单 -> 减库存 -> 扣余额 -> 改(订单)状态

    数据库初始情况:

    正常下单 -http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100

    运行后台:

    在这里插入图片描述
    在这里插入图片描述

    数据库正常下单后状况:
    在这里插入图片描述


    超时异常,没加@GlobalTransactional

    模拟AccountServiceImpl添加超时

    package cn.kt.springcloud.service.impl;
    
    import cn.kt.springcloud.dao.AccountDao;
    import cn.kt.springcloud.service.AccountService;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Service;
    
    import javax.annotation.Resource;
    import java.math.BigDecimal;
    import java.util.concurrent.TimeUnit;
    
    /**
     * Created by tao.
     * Date: 2022/9/17 16:45
     * 描述:
     */
    @Service
    public class AccountServiceImpl implements AccountService {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(AccountServiceImpl.class);
      
        @Resource
        AccountDao accountDao;
    
        /**
         * 扣减账户余额
         */
        @Override
        public void decrease(Long userId, BigDecimal money) {
            LOGGER.info("------->account-service中扣减账户余额开始");
            //模拟超时异常,全局事务回滚
            //暂停几秒钟线程
            try {
                TimeUnit.SECONDS.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            accountDao.decrease(userId, money);
            LOGGER.info("------->account-service中扣减账户余额结束");
        }
    }
    

    另外,OpenFeign的调用默认时间是1s以内,所以最后会抛异常。

    数据库情况

    在这里插入图片描述

    故障情况

    • 当库存和账户金额扣减后,订单状态并没有设置为已经完成,没有从零改为1
    • 而且由于feign的重试机制,账户余额还有可能被多次扣减

    超时异常,加了@GlobalTransactional

    用@GlobalTransactional标注OrderServiceImpl的create()方法。

    @Service
    @Slf4j
    public class OrderServiceImpl implements OrderService {
        
        ...
    
        /**
         * 创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态
         * 简单说:下订单->扣库存->减余额->改状态
         */
        @Override
        //rollbackFor = Exception.class表示对任意异常都进行回滚
        @GlobalTransactional(name = "my_tx_group",rollbackFor = Exception.class)
        public void create(Order order)
        {
    		...
        }
    }
    

    还是模拟AccountServiceImpl添加超时,下单后数据库数据并没有任何改变,记录都添加不进来,达到出异常,数据库回滚的效果

    Seata之原理简介

    2019年1月份蚂蚁金服和阿里巴巴共同开源的分布式事务解决方案。

    Simple Extensible Autonomous Transaction Architecture,简单可扩展自治事务框架。

    2020起始,用1.0以后的版本。Alina Gingertail

    在这里插入图片描述

    分布式事务的执行流程

    • TM开启分布式事务(TM向TC注册全局事务记录) ;
    • 按业务场景,编排数据库、服务等事务内资源(RM向TC汇报资源准备状态) ;
    • TM结束分布式事务,事务一阶段结束(TM通知TC提交/回滚分布式事务) ;
    • TC汇总事务信息,决定分布式事务是提交还是回滚;
    • TC通知所有RM提交/回滚资源,事务二阶段结束。

    AT模式如何做到对业务的无侵入

    • 是什么

    前提

    • 基于支持本地 ACID 事务的关系型数据库。
    • Java 应用,通过 JDBC 访问数据库。

    整体机制

    两阶段提交协议的演变:

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

    • 二阶段:

      • 提交异步化,非常快速地完成。
      • 回滚通过一阶段的回滚日志进行反向补偿。

    link

    • 一阶段加载

    在一阶段,Seata会拦截“业务SQL”

    1. 解析SQL语义,找到“业务SQL" 要更新的业务数据,在业务数据被更新前,将其保存成"before image”
    2. 执行“业务SQL" 更新业务数据,在业务数据更新之后,
    3. 其保存成"after image”,最后生成行锁。

    以上操作全部在一个数据库事务内完成, 这样保证了一阶段操作的原子性。

    在这里插入图片描述

    • 二阶段提交

    二阶段如果顺利提交的话,因为"业务SQL"在一阶段已经提交至数据库,所以Seata框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。

    在这里插入图片描述

    • 二阶段回滚

    二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的 “业务SQL",还原业务数据。

    回滚方式便是用"before image"还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和"after image"。

    如果两份数据完全一致就说明没有脏写, 可以还原业务数据,如果不一致就说明有脏写, 出现脏写就需要转人工处理。

    在这里插入图片描述

    补充
    在这里插入图片描述

    Spring Cloud组件总结

    组件简介分类官网笔记备注
    EurekaEureka is the Netflix Service Discovery Server and Client.服务注册中心linklinkeureka中文解释:int.(因找到某物,尤指问题的答案而高兴)我发现了,我找到了
    ZookeeperZooKeeper is a centralized service for maintaining configuration information, naming, providing distributed synchronization, and providing group services.服务注册中心linklinkzookeeper中文解释:n.动物园管理员
    ConsulConsul is a service mesh solution providing a full featured control plane with service discovery, configuration, and segmentation functionality.服务注册中心linklinkconsul中文解释:n.领事
    RibbonRibbon is a client-side load balancer that gives you a lot of control over the behavior of HTTP and TCP clients.服务调用linklinkribbon中文解释:n.(用于捆绑或装饰的)带子;丝带;带状物;
    OpenFeignFeign is a declarative web service client. It makes writing web service clients easier.服务调用linklinkfeign中文意思:v.假装,装作,佯装(有某种感觉或生病、疲倦等)
    HystrixNetflix has created a library called Hystrix that implements the circuit breaker pattern.服务降级linklinkhystrix中文意思:n.豪猪属;猬草属;豪猪;豪猪亚属
    GateWaySpring Cloud Gateway aims to provide a simple, yet effective way to route to APIs and provide cross cutting concerns to them such as: security, monitoring/metrics, and resiliency.服务网关linklinkgateway中文意思:n.网关;途径;门道;手段
    ConfigSpring Cloud Config provides server-side and client-side support for externalized configuration in a distributed system.服务配置linklink-
    BusSpring Cloud Bus links nodes of a distributed system with a lightweight message broker.服务总线linklink-
    StreamSpring Cloud Stream is a framework for building message-driven microservice applications.消息队列linklink-
    SleuthSpring Cloud Sleuth implements a distributed tracing solution for Spring Cloud.服务跟踪linklinksleuth中文意思:n.侦探
    NacosNacos致力于帮助您发现、配置和管理微服务。服务注册中心、服务配置、服务总线linklinkNAme + COnfiguration + Service
    SentinelSentinel是面向分布式服务架构的流量控制组件,主要以流量为切入点,从流量控制、熔断降级、系统自适应保护等多个维度来帮助您保障微服务的稳定性。服务降级linklinksentinel中文意思:n.哨兵
    SeataSeata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。分布式事务linklink-

    学习源码

    https://gitee.com/KT1205529635/springcloud/tree/master/code/springcloud

  • 相关阅读:
    1003 - 编程求1+3+5+...+n
    Mysql 如何模糊匹配后匹配优化
    GitLab 内存、cpu资源占用过大问题解决
    自注意力(Self-Attention)与Multi-Head Attention机制详解
    系统架构设计师-计算机网络
    Poppler in path for pdf2image
    致敬经典 睛彩再现——AVS产业联盟和中国移动咪咕公司携手推动AVS3视频、音频标准
    JavaS
    Dell笔记本电脑 启动时提示解决
    C++ 字符串哈希(hush)讲解
  • 原文地址:https://blog.csdn.net/qq_42038623/article/details/126964783