• CloudAlibaba - Seata处理分布式事务


    当下互联网发展如火如荼,绝大部分公司都进行了数据库拆分和服务化(SOA)。在这种情况下,完成某一个业务功能可能需要横跨多个服务,操作多个数据库。这就涉及到了分布式事务,用需要操作的资源位于多个资源服务器上,而应用需要保证对于多个资源服务器的数据的操作,要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同资源服务器的数据一致性。

    分布式前

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

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

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

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

    在这里插入图片描述
    一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题。

    一.Seata简介

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

    官方文档:http://seata.io/zh-cn/

    一个典型的分布式事务过程,分布式事务处理过程的 —— 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下管辖的全部分支事务完成提交或回滚请求。

    在这里插入图片描述

    如何使用?

    本地@Transactional

    全局@GlobalTransactional

    在这里插入图片描述

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

    二.Seata-Server安装

    1.下载:
    github下载地址:https://github.com/seata/seata/releases
    本次使用的是v0.9.0 版本下载地址: https://github.com/seata/seata/releases/tag/v0.9.0

    2.下载完成后解压:
    解压完成后出现以下目录:

    在这里插入图片描述

    3.修改配置文件:

    主要修改:自定义事务组名称+事务日志存储模式为db +数据库连接信息

    修改conf下面的配置文件,在修改文件前对需要修改的文件进行一个备份保存

    在这里插入图片描述

    file.conf修改:

    service模块

    service {
      #transaction service group mapping
      # fsp_tx_group是自定义的
      vgroup_mapping.my_test_tx_group = "fsp_tx_group"
      #only support when registry.type=file, please don't set multiple addresses
      default.grouplist = "127.0.0.1:8091"
      #disable seata
      disableGlobalTransaction = false
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    store模块

    ## transaction log store, only used in seata-server
    store {
      ## store mode: file、db
      ## 改成db 代表使用数据库
      mode = "db"
    
      ## file store property
      file {
        ## store location dir
        dir = "sessionStore"
      }
    
      ## database store property
      db {
        ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
        ## 配置数据源
        datasource = "dbcp"
        ## mysql/oracle/h2/oceanbase etc.
        db-type = "mysql"
        driver-class-name = "com.mysql.jdbc.Driver"
        url = "jdbc:mysql://127.0.0.1:3306/seata"
        user = "mysql用户"
        password = "mysql密码"
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    修改jar包:

    若mysql版本为8.0+,修改mysql5.0jar包为mysql8.0jar包

    在这里插入图片描述

    在seata库里建表

    -- the table to store GlobalSession data
    drop table if exists `global_table`;
    create table `global_table` (
      `xid` varchar(128)  not null,
      `transaction_id` bigint,
      `status` tinyint not null,
      `application_id` varchar(32),
      `transaction_service_group` varchar(32),
      `transaction_name` varchar(128),
      `timeout` int,
      `begin_time` bigint,
      `application_data` varchar(2000),
      `gmt_create` datetime,
      `gmt_modified` datetime,
      primary key (`xid`),
      key `idx_gmt_modified_status` (`gmt_modified`, `status`),
      key `idx_transaction_id` (`transaction_id`)
    );
    
    -- the table to store BranchSession data
    drop table if exists `branch_table`;
    create table `branch_table` (
      `branch_id` bigint not null,
      `xid` varchar(128) not null,
      `transaction_id` bigint ,
      `resource_group_id` varchar(32),
      `resource_id` varchar(256) ,
      `lock_key` varchar(128) ,
      `branch_type` varchar(8) ,
      `status` tinyint,
      `client_id` varchar(64),
      `application_data` varchar(2000),
      `gmt_create` datetime,
      `gmt_modified` datetime,
      primary key (`branch_id`),
      key `idx_xid` (`xid`)
    );
    
    -- the table to store lock data
    drop table if exists `lock_table`;
    create table `lock_table` (
      `row_key` varchar(128) not null,
      `xid` varchar(96),
      `transaction_id` long ,
      `branch_id` long,
      `resource_id` varchar(256) ,
      `table_name` varchar(32) ,
      `pk` varchar(36) ,
      `gmt_create` datetime ,
      `gmt_modified` datetime,
      primary key(`row_key`)
    );
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    修改\conf目录下的registry.conf配置文件

    registry {
      # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
      # 改用为nacos
      type = "nacos"
    
      nacos {
      	## 加端口号
        serverAddr = "localhost:8848"
        namespace = ""
        cluster = "default"
      }
      ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    测试:

    • 先启动Nacos端口号8848
    • 再启动seata-server bin\seata-server.bat

    在这里插入图片描述

    三.Seate分布式事务演示

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

    分布式事务业务说明:

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

    当用户下单时,会在订单服务中创建一个订单, 然后通过远程调用库存服务来扣减下单商品的库存,再通过远程调用账户服务来扣减用户账户里面的余额,最后在订单服务中修改订单状态为已完成。
    该操作跨越三个数据库,有两次远程调用,很明显会有分布式事务问题。

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

    1. 数据库准备

    创建业务数据库

    • seata_ order:存储订单的数据库;
    • seata_ storage:存储库存的数据库;
    • seata_ account:存储账户信息的数据库。

    在这里插入图片描述

    建库SQL:

    CREATE DATABASE seata_order;
    CREATE DATABASE seata_storage;
    CREATE DATABASE seata_account;
    
    • 1
    • 2
    • 3

    按照上述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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

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

    • 订单-库存-账户3个库下都需要建各自的回滚日志表
    • 建表SQL:
    -- for AT mode you must to init this sql for you business database. the seata server not need it.
    CREATE TABLE IF NOT EXISTS `undo_log`
    (
        `id`            BIGINT(20)   NOT NULL AUTO_INCREMENT COMMENT 'increment id',
        `branch_id`     BIGINT(20)   NOT NULL COMMENT 'branch transaction id',
        `xid`           VARCHAR(100) NOT NULL COMMENT 'global transaction id',
        `context`       VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
        `rollback_info` LONGBLOB     NOT NULL COMMENT 'rollback info',
        `log_status`    INT(11)      NOT NULL COMMENT '0:normal status,1:defense status',
        `log_created`   DATETIME     NOT NULL COMMENT 'create datetime',
        `log_modified`  DATETIME     NOT NULL COMMENT 'modify datetime',
        PRIMARY KEY (`id`),
        UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
    ) ENGINE = InnoDB
      AUTO_INCREMENT = 1
      DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这里插入图片描述

    2. 服务搭建

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

    2.1 订单服务

    seata-order-service2001
    在这里插入图片描述

    POM.xml

    
    <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>cloud2022artifactId>
            <groupId>com.jm.springcloudgroupId>
            <version>1.0-SNAPSHOTversion>
        parent>
        <modelVersion>4.0.0modelVersion>
    
        <artifactId>seata-order-service2001artifactId>
    
        <dependencies>
            <dependency>
                <groupId>com.jm.springcloudgroupId>
                <artifactId>cloud-api-commonsartifactId>
                <version>1.0-SNAPSHOTversion>
            dependency>
            
            <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-allartifactId>
                        <groupId>io.seatagroupId>
                    exclusion>
                exclusions>
            dependency>
            <dependency>
                <groupId>io.seatagroupId>
                <artifactId>seata-allartifactId>
                <version>0.9.0version>
            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>8.0.28version>
            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>
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85

    Application.yml

    server:
      port: 2001
    
    spring:
      application:
        name: seata-order-service
      cloud:
        alibaba:
          seata:
            #自定义事务组名称需要与seata-server中的对应
            tx-service-group: fsp_tx_group
        nacos:
          discovery:
            server-addr: localhost:8848
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/seata_order?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
        username: root
        password: "021107"
    
    logging:
      level:
        io:
          seata: info
    
    mybatis:
      mapperLocations: classpath:mapper/*.xml
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    resource 资源目录下创建以下两个文件

    file.conf:

    transport {
      # tcp udt unix-domain-socket
      type = "TCP"
      #NIO NATIVE
      server = "NIO"
      #enable heartbeat
      heartbeat = true
      #thread factory for netty
      thread-factory {
        boss-thread-prefix = "NettyBoss"
        worker-thread-prefix = "NettyServerNIOWorker"
        server-executor-thread-prefix = "NettyServerBizHandler"
        share-boss-worker = false
        client-selector-thread-prefix = "NettyClientSelector"
        client-selector-thread-size = 1
        client-worker-thread-prefix = "NettyClientWorkerThread"
        # netty boss thread size,will not be used for UDT
        boss-thread-size = 1
        #auto default pin or 8
        worker-thread-size = 8
      }
      shutdown {
        # when destroy server, wait seconds
        wait = 3
      }
      serialization = "seata"
      compressor = "none"
    }
    
    service {
    
      vgroup_mapping.fsp_tx_group = "default" #修改自定义事务组名称
    
      default.grouplist = "127.0.0.1:8091"
      enableDegrade = false
      disable = false
      max.commit.retry.timeout = "-1"
      max.rollback.retry.timeout = "-1"
      disableGlobalTransaction = false
    }
    
    
    client {
      async.commit.buffer.limit = 10000
      lock {
        retry.internal = 10
        retry.times = 30
      }
      report.retry.count = 5
      tm.commit.retry.count = 1
      tm.rollback.retry.count = 1
    }
    
    ## transaction log store
    store {
      ## store mode: file、db
      mode = "db"
    
      ## file store
      file {
        dir = "sessionStore"
    
        # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
        max-branch-session-size = 16384
        # globe session size , if exceeded throws exceptions
        max-global-session-size = 512
        # file buffer size , if exceeded allocate new buffer
        file-write-buffer-cache-size = 16384
        # when recover batch read size
        session.reload.read_size = 100
        # async, sync
        flush-disk-mode = async
      }
    
      ## database store
      db {
        ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
        datasource = "dbcp"
        ## mysql/oracle/h2/oceanbase etc.
        db-type = "mysql"
        driver-class-name = "com.mysql.cj.jdbc.Driver"
        url = "jdbc:mysql://127.0.0.1:3306/seata"
        user = "root"
        password = "021107"
        min-conn = 1
        max-conn = 3
        global.table = "global_table"
        branch.table = "branch_table"
        lock-table = "lock_table"
        query-limit = 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
      }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141

    registry.conf:

    registry {
      # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
      type = "nacos"
    
      nacos {
        serverAddr = "localhost:8848"
        namespace = ""
        cluster = "default"
      }
      eureka {
        serviceUrl = "http://localhost:8761/eureka"
        application = "default"
        weight = "1"
      }
      redis {
        serverAddr = "localhost:6379"
        db = "0"
      }
      zk {
        cluster = "default"
        serverAddr = "127.0.0.1:2181"
        session.timeout = 6000
        connect.timeout = 2000
      }
      consul {
        cluster = "default"
        serverAddr = "127.0.0.1:8500"
      }
      etcd3 {
        cluster = "default"
        serverAddr = "http://localhost:2379"
      }
      sofa {
        serverAddr = "127.0.0.1:9603"
        application = "default"
        region = "DEFAULT_ZONE"
        datacenter = "DefaultDataCenter"
        cluster = "default"
        group = "SEATA_GROUP"
        addressWaitTime = "3000"
      }
      file {
        name = "file.conf"
      }
    }
    
    config {
      # file、nacos 、apollo、zk、consul、etcd3
      type = "file"
    
      nacos {
        serverAddr = "localhost"
        namespace = ""
      }
      consul {
        serverAddr = "127.0.0.1:8500"
      }
      apollo {
        app.id = "seata-server"
        apollo.meta = "http://192.168.1.204:8801"
      }
      zk {
        serverAddr = "127.0.0.1:2181"
        session.timeout = 6000
        connect.timeout = 2000
      }
      etcd3 {
        serverAddr = "http://localhost:2379"
      }
      file {
        name = "file.conf"
      }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75

    主启动类:

    @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//取消数据源的自动创建
    @EnableDiscoveryClient
    @EnableFeignClients
    public class SeataOrderMainApp2001 {
         public static void main(String[] args) {
            SpringApplication.run(SeataOrderMainApp2001.class, args);
         }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    配置类:

    DataSourceProxyConfig

    package com.jm.cloudalibaba.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.core.io.support.PathMatchingResourcePatternResolver;
    
    import javax.sql.DataSource;
    
    /**
     * 使用Seata对数据源进行代理
     */
    @Configuration
    public class DataSourceProxyConfig {
    
        @Value("${mybatis.mapperLocations}")
        private String mapperLocations;
    
        @Bean
        @ConfigurationProperties(prefix = "spring.datasource")
        public DataSource druidDataSource(){
            return new DruidDataSource();
        }
    
        @Bean
        public DataSourceProxy dataSourceProxy(DataSource dataSource) {
            return new DataSourceProxy(dataSource);
        }
    
        @Bean
        public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
            SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
            sqlSessionFactoryBean.setDataSource(dataSourceProxy);
            sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
            sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
            return sqlSessionFactoryBean.getObject();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    MyBatisConfig

    package com.jm.cloudalibaba.config;
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    @MapperScan({"com.jm.cloudalibaba.dao"})
    public class MyBatisConfig {
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    实体类:

    OrderDao:

    package com.jm.cloudalibaba.domain;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    import java.math.BigDecimal;
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Order
    {
        private Long id;
    
        private Long userId;
    
        private Long productId;
    
        private Integer count;
    
        private BigDecimal money;
    
        private Integer status; //订单状态:0:创建中;1:已完结
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    mapper\OrderDaoMapper.xml

    
    DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <mapper namespace="com.jm.cloudalibaba.dao.OrderDao">
    
        <resultMap id="BaseResultMap" type="com.jm.cloudalibaba.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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    service:

    在这里插入图片描述

    接口:

    public interface OrderService {
        void create(Order order);
    }
    
    //=================================================================================
    
    @FeignClient(value = "seata-storage-service")
    public interface StorageService {
        @PostMapping(value = "/storage/decrease")
        CommonResult decrease(@RequestParam("productId") Long productId,@RequestParam("count") Integer count);
    }
    
    //=================================================================================
    
    @FeignClient(value = "seata-account-service")
    public interface AccountService {
        @PostMapping(value = "/account/decrease")
        CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    实现类:

    package com.jm.cloudalibaba.service.impl;
    
    import com.jm.cloudalibaba.dao.OrderDao;
    import com.jm.cloudalibaba.domain.Order;
    import com.jm.cloudalibaba.service.AccountService;
    import com.jm.cloudalibaba.service.OrderService;
    import com.jm.cloudalibaba.service.StorageService;
    import io.seata.spring.annotation.GlobalTransactional;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Service;
    
    import javax.annotation.Resource;
    
    @Service
    @Slf4j
    public class OrderServiceImpl implements OrderService {
        @Resource
        private OrderDao orderDao;
        @Resource
        private StorageService storageService;
        @Resource
        private AccountService accountService;
    
        /**
         * 创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态
         * 简单说:
         * 下订单->减库存->减余额->改状态
         */
        @Override
        @GlobalTransactional(name = "seata-create-order",rollbackFor = Exception.class)
        public void create(Order order) {
            //1.新建订单
            log.info("----->开始新建订单");
            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.修改订单状态:0->1(表示完成)
            log.info("----->修改订单状态开始");
            orderDao.update(order.getUserId(),0);
            log.info("----->修改订单状态结束");
    
            log.info("----->下订单结束了,O(∩_∩)O");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    controller:

    package com.jm.cloudalibaba.controller;
    
    import com.jm.cloudalibaba.domain.Order;
    import com.jm.cloudalibaba.service.OrderService;
    import com.jm.springcloud.entities.CommonResult;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.annotation.Resource;
    
    @RestController
    public class OrderController {
        @Resource
        private OrderService orderService;
    
        @GetMapping("/order/create")
        public CommonResult create(Order order){
            orderService.create(order);
            return new CommonResult(200,"订单创建成功");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    2.2 库存服务

    在这里插入图片描述

    POM.xml
    -同订单服务配置

    Application.yml
    同订单服务配置,注意 端口 和 数据库

    在resource 资源目录下创建以下两个文件
    file.conf: 同订单服务配置
    registry.conf: 同订单服务配置

    主启动类:

    @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
    @EnableDiscoveryClient
    @EnableFeignClients
    public class SeataStorageAppMain2002 {
         public static void main(String[] args) {
               SpringApplication.run(SeataStorageAppMain2002.class, args);
         }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    配置类:

    • DataSourceProxyConfig : 同订单服务配置
    • MyBatisConfig : 同订单服务配置

    实体类:

    package com.jm.cloudalibaba.domain;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Storage {
        private Long id;
        /**
         * 产品id
         */
        private Long productId;
        /**
         * 总库存
         */
        private Integer total;
        /**
         * 已用库存
         */
        private Integer used;
        /**
         * 剩余库存
         */
        private Integer residue;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    StorageDao:

    package com.jm.cloudalibaba.dao;
    
    import org.apache.ibatis.annotations.Mapper;
    import org.apache.ibatis.annotations.Param;
    
    @Mapper
    public interface StorageDao {
        /**
         * 扣减库存
         */
        void decrease(@Param("productId") Long productId, @Param("count") Integer count);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    mapper\StorageDaoMapper.xml

    
    DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <mapper namespace="com.jm.cloudalibaba.dao.StorageDao">
        
        <resultMap id="BaseResultMap" type="com.jm.cloudalibaba.domain.Storage">
            <id column="id" property="id"/>
            <result column="product_id" property="productId"/>
            <result column="total" property="total"/>
            <result column="used" property="used"/>
            <result column="residue" property="residue"/>
        resultMap>
    
        <update id="decrease">
            update t_storage
            set used = used+#{count},residue = residue-#{count}
            where product_id=#{productId}
        update>
    mapper>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    service:

    接口:

    public interface StorageService {
        void decrease(Long productId,Integer count);
    }
    
    • 1
    • 2
    • 3

    实现类:

    @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中扣减库存结束");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    controller:

    @RestController
    public class StorageController {
        @Resource
        private StorageService storageService;
    
        /**
         * 扣减库存
         */
        @PostMapping("/storage/decrease")
        public CommonResult decrease(@RequestParam("productId") Long productId,@RequestParam("count") Integer count){
            storageService.decrease(productId,count);
            return new CommonResult<>(200,"扣减库存成功!");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    2.3 账户服务

    在这里插入图片描述

    POM.xml
    -同订单服务配置

    Application.yml
    同订单服务配置,注意 端口 和 数据库

    在resource 资源目录下创建以下两个文件
    file.conf: 同订单服务配置
    registry.conf: 同订单服务配置

    主启动类:

    @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
    @EnableDiscoveryClient
    @EnableFeignClients
    public class SeataAccountMainApp2003 {
         public static void main(String[] args) {
             SpringApplication.run(SeataAccountMainApp2003.class, args);
         }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    配置类:

    • DataSourceProxyConfig : 同订单服务配置
    • MyBatisConfig : 同订单服务配置

    实体类:

    package com.jm.cloudalibaba.domain;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    import java.math.BigDecimal;
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Account {
        private Long id;
        /**
         * 用户id
         */
        private Long userId;
    
        /**
         * 总额度
         */
        private BigDecimal total;
    
        /**
         * 已用额度
         */
        private BigDecimal used;
    
        /**
         * 剩余额度
         */
        private BigDecimal residue;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    AccountDao:

    package com.jm.cloudalibaba.dao;
    
    import org.apache.ibatis.annotations.Mapper;
    import org.apache.ibatis.annotations.Param;
    
    import java.math.BigDecimal;
    
    @Mapper
    public interface AccountDao {
        void decrease(@Param("userId") Long id, @Param("money") BigDecimal money);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    mapper\AccountDaoMapper.xml

    
    DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <mapper namespace="com.jm.cloudalibaba.dao.AccountDao">
    
        <resultMap id="BaseResultMap" type="com.jm.cloudalibaba.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 used = used+#{money},residue = residue-#{money}
            where user_id=#{userId}
        update>
    mapper>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    service:

    接口:

    public interface AccountService {
        void decrease(Long id, BigDecimal money);
    }
    
    
    • 1
    • 2
    • 3
    • 4

    实现类:

    @Service
    public class AccountServiceImpl implements AccountService {
        public static final Logger LOGGER= LoggerFactory.getLogger(AccountServiceImpl.class);
    
        @Resource
        private AccountDao accountDao;
    
        @Override
        public void decrease(Long id, BigDecimal money) {
            LOGGER.info("----->account-service中扣减账户余额开始");
            //模拟超时异常,全局事务回滚
            //暂停几秒钟线程
    //        try{
    //            TimeUnit.SECONDS.sleep(20);
    //        }catch (Exception e){
    //            e.printStackTrace();
    //        }
            accountDao.decrease(id,money);
            LOGGER.info("----->account-service中扣减账户余额结束");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    controller:

    @RestController
    public class AccountController {
        @Resource
        private AccountService accountService;
    
        @PostMapping("/account/decrease")
        public CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money){
            accountService.decrease(userId,money);
            return new CommonResult(200,"扣减账户余额成功");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    3. 正常测试

    查看数据库是否发送变化?

    在这里插入图片描述

    数据库表中发送变化,调用成功

    4. 调用超时异常未使用@GlobalTransactional

    模拟服务调用超时,修改账户服务中的 AccountServiceImpl

    @Service
    public class AccountServiceImpl implements AccountService {
        public static final Logger LOGGER= LoggerFactory.getLogger(AccountServiceImpl.class);
    
        @Resource
        private AccountDao accountDao;
    
        @Override
        public void decrease(Long id, BigDecimal money) {
            LOGGER.info("----->account-service中扣减账户余额开始");
            //模拟超时异常,全局事务回滚
            //暂停几秒钟线程
            try{
                TimeUnit.SECONDS.sleep(20);
            }catch (Exception e){
                e.printStackTrace();
            }
            accountDao.decrease(id,money);
            LOGGER.info("----->account-service中扣减账户余额结束");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    调用接口:http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100,接口返回Error Page

    此时查看数据库中的数据:

    在这里插入图片描述

    故障问题:

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

    4. 调用超时异常添加@GlobalTransactional

    在订单服务中的 OrderServiceImplcreate 方法上添加 @GlobalTransactional注解

    @Service
    @Slf4j
    public class OrderServiceImpl implements OrderService {
        @Resource
        private OrderDao orderDao;
        @Resource
        private StorageService storageService;
        @Resource
        private AccountService accountService;
    
        /**
         * 创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态
         * 简单说:
         * 下订单->减库存->减余额->改状态
         */
        @Override
        @GlobalTransactional(name = "seata-create-order",rollbackFor = Exception.class)
        public void create(Order order) {
            //1.新建订单
            log.info("----->开始新建订单");
            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.修改订单状态:0->1(表示完成)
            log.info("----->修改订单状态开始");
            orderDao.update(order.getUserId(),0);
            log.info("----->修改订单状态结束");
    
            log.info("----->下订单结束了,O(∩_∩)O");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    调用接口:http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100 后,
    查看数据库数据情况:

    在这里插入图片描述

    数据并没有被修改,在添加 @GlobalTransactional 注解标签后,当 create发生异常后,在此方法内的所有调用服务的数据库操作的数据都进行了回滚操作

    四.GlobalTransactional注解详情

    public @interface GlobalTransactional {
    
        /**
    		全局事务超时毫秒(以毫秒为单位)。
         * Global transaction timeoutMills in MILLISECONDS.
         *
         * @return timeoutMills in MILLISECONDS.
         */
        int timeoutMills() default TransactionInfo.DEFAULT_TIME_OUT;
    
        /**
        	
    		全局事务实例的给定名称。
         * Given name of the global transaction instance.
         *
         * @return Given name.
         */
        String name() default "";
    
        /**
        	需要回滚的异常类型.class
         * roll back for the Class
         * @return
         */
        Class<? extends Throwable>[] rollbackFor() default {};
    
        /**
            需要回滚的异常类型名称
         *  roll back for the class name
         * @return
         */
        String[] rollbackForClassName() default {};
    
        /**
    	  需要回滚的异常类型.class
         * not roll back for the Class
         * @return
         */
        Class<? extends Throwable>[] noRollbackFor() default {};
    
        /**
          不需要回滚的异常类型名称
         * not roll back for the class name
         * @return
         */
        String[] noRollbackForClassName() default {};
    
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    五.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 访问数据库。

    整体机制

    两阶段提交协议的演变:

    • 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
    • 二阶段:
    • 提交异步化,非常快速地完成。
    • 回滚通过一阶段的回滚日志进行反向补偿。

    官方文档说明:http://seata.io/zh-cn/docs/overview/what-is-seata.html

    一阶段:

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

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

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

    在这里插入图片描述

    二阶段:

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

    在这里插入图片描述

    回滚:
    二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的 “业务SQL",还原业务数据。
    回滚方式便是用"before image"还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和"after image"。
    如果两份数据完全一致就说明没有脏写, 可以还原业务数据,如果不一致就说明有脏写, 出现脏写就需要转人工处理。

    在这里插入图片描述

    补充

    在这里插入图片描述

    六.git笔记录

    github:https://github.com/jmkk18/cloud2022.git

    gitee:https://gitee.com/jm1107/cloud2022.git

  • 相关阅读:
    Docker部署的时候从容器获取宿主机的CPU等信息
    C++ 学习(19)STL - list容器、set容器
    SAP UI5 里的 Busy Indicator 控件使用概述
    Spring Security使用总结四,此篇没有任何营养,就是将注册服务补全
    Mysql数据库(3)—架构和日志
    文章提交秒收录软件
    INTELlij IDEA编辑VUE项目
    本地vscode安装GPU版本PyTorch
    数据在内存中的存储
    元数据性能大比拼:HDFS vs S3 vs JuiceFS
  • 原文地址:https://blog.csdn.net/m0_66689823/article/details/128194012