zoukankan      html  css  js  c++  java
  • 【SpringCloud】Spring Cloud Alibaba 之 Seata 分布式事务中间件(三十六)

    什么是分布式事务问题?

    单体应用

      单体应用中,一个业务操作需要调用三个模块完成,此时数据的一致性由本地事务来保证。

    微服务应用

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

    小结

      在微服务架构中由于全局数据一致性没法保证产生的问题就是分布式事务问题。简单来说,一次业务操作需要操作多个数据源或需要进行远程调用,就会产生分布式事务问题。

    Seata 是什么?

      Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

      官网:http://seata.io/

    Seata 组成

    Transaction ID(XID)

      全局唯一的事务id

    三组件

      Transaction Coordinator(TC):事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚

      Transaction Manager(TM):控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议

      Resource Manager(RM):控制分支事务,负责分支注册、状态汇报,并接受事务协调的指令,驱动分支(本地)事务的提交和回滚

    Seata 分布式事务处理过程

      过程图:

      

      说明:

      1、TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID;

      2、XID 在微服务调用链路的上下文中传播;

      3、RM 向 TC 注册分支事务,将其纳入 XID 对应的全局事务的管辖;

      4、TM 向 TC 发起针对 XID 的全局提交或回滚决议;

      5、TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。

    Seata 部署

      Seata分TC、TM和RM三个角色,TC(Server端)为单独服务端部署,TM和RM(Client端)由业务系统集成。

      项目结构图:

        

    Seata服务端(TC)部署

      1、下载服务端压缩包,地址:https://github.com/seata/seata/releases

        本例下载的是 seata-server-1.2.0.tar.gz,并解压

      2、修改事务日志存储模式为 db 及数据库连接信息,即修改 conf目录中 flie.conf 文件,如下:

     1 ## transaction log store, only used in seata-server
     2 store {
     3   ## store mode: file、db
     4   mode = "db"
     5 
     6   ## file store property
     7   file {
     8     ## store location dir
     9     dir = "sessionStore"
    10     # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
    11     maxBranchSessionSize = 16384
    12     # globe session size , if exceeded throws exceptions
    13     maxGlobalSessionSize = 512
    14     # file buffer size , if exceeded allocate new buffer
    15     fileWriteBufferCacheSize = 16384
    16     # when recover batch read size
    17     sessionReloadReadSize = 100
    18     # async, sync
    19     flushDiskMode = async
    20   }
    21 
    22   ## database store property
    23   db {
    24     ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
    25     datasource = "druid"
    26     ## mysql/oracle/postgresql/h2/oceanbase etc.
    27     dbType = "mysql"
    28     driverClassName = "com.mysql.cj.jdbc.Driver"
    29     url = "jdbc:mysql://localhost:3306/seata"
    30     user = "admin"
    31     password = "123456"
    32     minConn = 5
    33     maxConn = 30
    34     globalTable = "global_table"
    35     branchTable = "branch_table"
    36     lockTable = "lock_table"
    37     queryLimit = 100
    38     maxWait = 5000
    39   }
    40 }

        由于我们使用了db模式存储事务日志,所以我们需要创建一个seat数据库,建表sql在seat项目的github找到,

        地址:https://github.com/seata/seata/tree/1.2.0/script/server/db 目录 mysql.sql 中

     1 -- -------------------------------- The script used when storeMode is 'db' --------------------------------
     2 -- the table to store GlobalSession data
     3 CREATE TABLE IF NOT EXISTS `global_table`
     4 (
     5     `xid`                       VARCHAR(128) NOT NULL,
     6     `transaction_id`            BIGINT,
     7     `status`                    TINYINT      NOT NULL,
     8     `application_id`            VARCHAR(32),
     9     `transaction_service_group` VARCHAR(32),
    10     `transaction_name`          VARCHAR(128),
    11     `timeout`                   INT,
    12     `begin_time`                BIGINT,
    13     `application_data`          VARCHAR(2000),
    14     `gmt_create`                DATETIME,
    15     `gmt_modified`              DATETIME,
    16     PRIMARY KEY (`xid`),
    17     KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),
    18     KEY `idx_transaction_id` (`transaction_id`)
    19 ) ENGINE = InnoDB
    20   DEFAULT CHARSET = utf8;
    21 
    22 -- the table to store BranchSession data
    23 CREATE TABLE IF NOT EXISTS `branch_table`
    24 (
    25     `branch_id`         BIGINT       NOT NULL,
    26     `xid`               VARCHAR(128) NOT NULL,
    27     `transaction_id`    BIGINT,
    28     `resource_group_id` VARCHAR(32),
    29     `resource_id`       VARCHAR(256),
    30     `branch_type`       VARCHAR(8),
    31     `status`            TINYINT,
    32     `client_id`         VARCHAR(64),
    33     `application_data`  VARCHAR(2000),
    34     `gmt_create`        DATETIME(6),
    35     `gmt_modified`      DATETIME(6),
    36     PRIMARY KEY (`branch_id`),
    37     KEY `idx_xid` (`xid`)
    38 ) ENGINE = InnoDB
    39   DEFAULT CHARSET = utf8;
    40 
    41 -- the table to store lock data
    42 CREATE TABLE IF NOT EXISTS `lock_table`
    43 (
    44     `row_key`        VARCHAR(128) NOT NULL,
    45     `xid`            VARCHAR(96),
    46     `transaction_id` BIGINT,
    47     `branch_id`      BIGINT       NOT NULL,
    48     `resource_id`    VARCHAR(256),
    49     `table_name`     VARCHAR(32),
    50     `pk`             VARCHAR(36),
    51     `gmt_create`     DATETIME,
    52     `gmt_modified`   DATETIME,
    53     PRIMARY KEY (`row_key`),
    54     KEY `idx_branch_id` (`branch_id`)
    55 ) ENGINE = InnoDB
    56   DEFAULT CHARSET = utf8;
    mysql.sql

        

      3、修改注册中心,使用nacos作为注册中心,即修改 conf目录中 registry.conf 文件,如下:

     1 registry {
     2   # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
     3   type = "nacos"
     4 
     5   nacos {
     6     application = "seata-server"
     7     serverAddr = "localhost:8848"
     8     namespace = ""
     9     cluster = "default"
    10     username = ""
    11     password = ""
    12   }
    13 }

      4、启动Nacos服务(参考:【SpringCloud】Spring Cloud Alibaba 之 Nacos注册中心(二十七))、在启动Seata服务

        Seata服务启动命令:sh ./bin/seata-server.sh    

        

    Seata客户端(TM和RM)部署

    业务数据库准备

      订单库order

     1 CREATE DATABASE seata_order;
     2 
     3 USE seata_order;
     4 
     5 CREATE TABLE `order` (
     6   `id` bigint(11) NOT NULL AUTO_INCREMENT,
     7   `user_id` bigint(11) DEFAULT NULL COMMENT '用户id',
     8   `product_id` bigint(11) DEFAULT NULL COMMENT '产品id',
     9   `count` int(11) DEFAULT NULL COMMENT '数量',
    10   `money` decimal(11,0) DEFAULT NULL COMMENT '金额',
    11   `status` int(1) DEFAULT NULL COMMENT '订单状态:0:创建中;1:已完结',
    12   PRIMARY KEY (`id`)
    13 ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

      库存库storage

     1 USE seata_storage;
     2 
     3 CREATE TABLE `storage` (
     4                          `id` bigint(11) NOT NULL AUTO_INCREMENT,
     5                          `product_id` bigint(11) DEFAULT NULL COMMENT '产品id',
     6                          `total` int(11) DEFAULT NULL COMMENT '总库存',
     7                          `used` int(11) DEFAULT NULL COMMENT '已用库存',
     8                          `residue` int(11) DEFAULT NULL COMMENT '剩余库存',
     9                          PRIMARY KEY (`id`)
    10 ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
    11 
    12 INSERT INTO `seata_storage`.`storage` (`id`, `product_id`, `total`, `used`, `residue`) VALUES ('1', '1', '100', '0', '100');

      账户库

     1 CREATE DATABASE seata_account;
     2 
     3 USE seata_account;
     4 
     5 
     6 CREATE TABLE `account` (
     7   `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
     8   `user_id` bigint(11) DEFAULT NULL COMMENT '用户id',
     9   `total` decimal(10,0) DEFAULT NULL COMMENT '总额度',
    10   `used` decimal(10,0) DEFAULT NULL COMMENT '已用余额',
    11   `residue` decimal(10,0) DEFAULT '0' COMMENT '剩余可用额度',
    12   PRIMARY KEY (`id`)
    13 ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
    14 
    15 INSERT INTO `seata_account`.`account` (`id`, `user_id`, `total`, `used`, `residue`) VALUES ('1', '1', '1000', '0', '1000');

      在三个库中都插入undo_log表,sql地址:https://github.com/seata/seata/blob/develop/script/client/at/db/mysql.sql

     1 -- for AT mode you must to init this sql for you business database. the seata server not need it.
     2 CREATE TABLE IF NOT EXISTS `undo_log`
     3 (
     4     `branch_id`     BIGINT(20)   NOT NULL COMMENT 'branch transaction id',
     5     `xid`           VARCHAR(100) NOT NULL COMMENT 'global transaction id',
     6     `context`       VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
     7     `rollback_info` LONGBLOB     NOT NULL COMMENT 'rollback info',
     8     `log_status`    INT(11)      NOT NULL COMMENT '0:normal status,1:defense status',
     9     `log_created`   DATETIME(6)  NOT NULL COMMENT 'create datetime',
    10     `log_modified`  DATETIME(6)  NOT NULL COMMENT 'modify datetime',
    11     UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
    12 ) ENGINE = InnoDB
    13   AUTO_INCREMENT = 1
    14   DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';

      三个数据库

        

    Order订单服务

      1、新建订单模块(springcloud-seata-order9011)

        

      2、编辑pom文件,完整pom如下:

     1 <?xml version="1.0" encoding="UTF-8"?>
     2 <project xmlns="http://maven.apache.org/POM/4.0.0"
     3          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     4          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     5     <parent>
     6         <artifactId>test-springcloud</artifactId>
     7         <groupId>com.test</groupId>
     8         <version>1.0-SNAPSHOT</version>
     9     </parent>
    10     <modelVersion>4.0.0</modelVersion>
    11 
    12     <artifactId>springcloud-seata-order9011</artifactId>
    13 
    14     <dependencies>
    15 
    16         <!-- seata-->
    17         <dependency>
    18             <groupId>com.alibaba.cloud</groupId>
    19             <artifactId>spring-cloud-alibaba-seata</artifactId>
    20             <exclusions>
    21                 <exclusion>
    22                     <groupId>io.seata</groupId>
    23                     <artifactId>seata-all</artifactId>
    24                 </exclusion>
    25             </exclusions>
    26         </dependency>
    27         <dependency>
    28             <groupId>io.seata</groupId>
    29             <artifactId>seata-all</artifactId>
    30             <version>1.2.0</version>
    31         </dependency>
    32 
    33         <!-- alibaba nacos discovery -->
    34         <dependency>
    35             <groupId>com.alibaba.cloud</groupId>
    36             <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    37         </dependency>
    38 
    39         <dependency>
    40             <groupId>org.springframework.cloud</groupId>
    41             <artifactId>spring-cloud-starter-openfeign</artifactId>
    42         </dependency>
    43 
    44         <!-- spring boot -->
    45         <dependency>
    46             <groupId>org.springframework.boot</groupId>
    47             <artifactId>spring-boot-starter-web</artifactId>
    48         </dependency>
    49         <dependency>
    50             <groupId>org.springframework.boot</groupId>
    51             <artifactId>spring-boot-starter-actuator</artifactId>
    52         </dependency>
    53         <dependency>
    54             <groupId>org.springframework.boot</groupId>
    55             <artifactId>spring-boot-devtools</artifactId>
    56             <scope>runtime</scope>
    57             <optional>true</optional>
    58         </dependency>
    59 
    60         <dependency>
    61             <groupId>org.springframework.boot</groupId>
    62             <artifactId>spring-boot-starter-jdbc</artifactId>
    63         </dependency>
    64 
    65         <dependency>
    66             <groupId>org.mybatis.spring.boot</groupId>
    67             <artifactId>mybatis-spring-boot-starter</artifactId>
    68         </dependency>
    69 
    70         <!-- mysql -->
    71         <dependency>
    72             <groupId>mysql</groupId>
    73             <artifactId>mysql-connector-java</artifactId>
    74         </dependency>
    75 
    76         <dependency>
    77             <groupId>org.projectlombok</groupId>
    78             <artifactId>lombok</artifactId>
    79             <optional>true</optional>
    80         </dependency>
    81         <dependency>
    82             <groupId>org.springframework.boot</groupId>
    83             <artifactId>spring-boot-starter-test</artifactId>
    84             <scope>test</scope>
    85         </dependency>
    86 
    87     </dependencies>
    88 
    89 </project>
    pom.xml

        注意seata-all版本是:1.2.0,与服务端版本一致

      3、编辑application.yml属性文件

     1 # 端口
     2 server:
     3   port: 9011
     4 
     5 spring:
     6   application:
     7     name: seata-order-service
     8   #   数据源基本配置
     9   cloud:
    10     nacos:
    11       discovery:
    12         server-addr: localhost:8848
    13     alibaba:
    14       seata:
    15         # 此处的名称一定要与 vgroup-mapping配置的参数保持一致
    16         tx-service-group: my_order_tx_group
    17   datasource:
    18     driver-class-name: com.mysql.cj.jdbc.Driver
    19     url: jdbc:mysql://localhost:3306/seata_order?allowPublicKeyRetrieval=true&useSSL=true
    20     username: admin
    21     password: 123456
    22     hikari:
    23       connection-test-query: SELECT 1 FROM DUAL
    24       minimum-idle: 1
    25       maximum-pool-size: 10
    26       pool-name: ${spring.application.name}-CP
    27       idle-timeout: 10000
    28       cachePrepStmts: true
    29       prepStmtCacheSize: 250
    30       prepStmtCacheSqlLimit: 2048
    31       leakDetectionThreshold: 40000
    32 
    33 ribbon:
    34   ReadTimeout: 600000
    35   ConnectTimeout: 600000
    36   MaxAutoRetries: 0
    37   MaxAutoRetriesNextServer: 1
    38 
    39 # 饿加载开启 Feign 预加载, 防止第一次请求超时
    40   eager-load:
    41     enabled: true
    42     clients: seata-storage-service, storage-account-server
    43 
    44 mybatis:
    45   mapperLocations: classpath:mapper/*Mapper.xml
    46   # 所有entity别名类所在的包
    47   type-aliases-pachage: com.test.springcloud.entities
    48 
    49 logging:
    50   level:
    51     #    root: debug
    52     com.test.springcloud: debug

      4、在resource目录中添加registry.conf配置文件,可服务服务端中的registry.conf文件如下:

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

      5、在resource目录中添加file.conf配置文件,内容如下;

     1 transport {
     2   # tcp udt unix-domain-socket
     3   type = "TCP"
     4   #NIO NATIVE
     5   server = "NIO"
     6   #enable heartbeat
     7   heartbeat = true
     8   # the client batch send request enable
     9   enableClientBatchSendRequest = true
    10   #thread factory for netty
    11   threadFactory {
    12     bossThreadPrefix = "NettyBoss"
    13     workerThreadPrefix = "NettyServerNIOWorker"
    14     serverExecutorThread-prefix = "NettyServerBizHandler"
    15     shareBossWorker = false
    16     clientSelectorThreadPrefix = "NettyClientSelector"
    17     clientSelectorThreadSize = 1
    18     clientWorkerThreadPrefix = "NettyClientWorkerThread"
    19     # netty boss thread size,will not be used for UDT
    20     bossThreadSize = 1
    21     #auto default pin or 8
    22     workerThreadSize = "default"
    23   }
    24   shutdown {
    25     # when destroy server, wait seconds
    26     wait = 3
    27   }
    28   serialization = "seata"
    29   compressor = "none"
    30 }
    31 
    32 service {
    33   #transaction service group mapping
    34   vgroupMapping.my_order_tx_group = "default"
    35   #only support when registry.type=file, please don't set multiple addresses
    36   default.grouplist = "127.0.0.1:8091"
    37   #degrade, current not support
    38   enableDegrade = false
    39   #disable seata
    40   disableGlobalTransaction = false
    41 }
    42 
    43 client {
    44   rm {
    45     asyncCommitBufferLimit = 10000
    46     lock {
    47       retryInterval = 10
    48       retryTimes = 30
    49       retryPolicyBranchRollbackOnConflict = true
    50     }
    51     reportRetryCount = 5
    52     tableMetaCheckEnable = false
    53     reportSuccessEnable = false
    54   }
    55   tm {
    56     commitRetryCount = 5
    57     rollbackRetryCount = 5
    58   }
    59   undo {
    60     dataValidation = true
    61     logSerialization = "jackson"
    62     logTable = "undo_log"
    63   }
    64   log {
    65     exceptionRate = 100
    66   }
    67 }
    View Code

        注意其中的:vgroupMapping.my_order_tx_group = "default"配置

      6、编辑主启动类

     1 @EnableFeignClients
     2 // 自动代理数据源
     3 @EnableAutoDataSourceProxy
     4 @EnableDiscoveryClient
     5 @SpringBootApplication
     6 public class SeataOrder9011 {
     7     public static void main(String[] args) {
     8         SpringApplication.run(SeataOrder9011.class, args);
     9     }
    10 }

      7、编辑业务实现类

        7.1、controller如下:

     1 @Slf4j
     2 @RestController
     3 public class OrderController {
     4     @Autowired
     5     private OrderService orderService;
     6 
     7     @GetMapping("/order/create")
     8     public CommonResult create(Order order) {
     9         orderService.create(order);
    10         return new CommonResult(1, "success");
    11     }
    12 }
    View Code

        7.2、service接口:

    1 public interface OrderService {
    2 
    3     // 新建订单
    4     public void create(Order order);
    5 
    6 }

        7.3、service实现类

          主要业务是:创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态

          @GlobalTransactional注解用以开启全局事务

     1 @Service
     2 @Slf4j
     3 public class OrderServiceImpl implements OrderService {
     4 
     5     @Autowired
     6     private OrderDao orderDao;
     7 
     8     @Autowired
     9     private StorageService storageService;
    10 
    11     @Autowired
    12     private AccountService accountService;
    13 
    14     /**
    15      * 创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态
    16      */
    17     @GlobalTransactional(name = "my_test_tx_group",rollbackFor = Exception.class)
    18     public void create(Order order) {
    19         log.info("------->下单开始");
    20         //本应用创建订单
    21         orderDao.insert(order);
    22 
    23         //远程调用库存服务扣减库存
    24         log.info("------->order-service中扣减库存开始");
    25         storageService.decrease(order.getProductId(),order.getCount());
    26         log.info("------->order-service中扣减库存结束:{}",order.getId());
    27 
    28         //远程调用账户服务扣减余额
    29         log.info("------->order-service中扣减余额开始");
    30         accountService.decrease(order.getUserId(),order.getMoney());
    31         log.info("------->order-service中扣减余额结束");
    32 
    33         //修改订单状态为已完成
    34         log.info("------->order-service中修改订单状态开始");
    35         orderDao.update(order.getId(), order.getUserId(),0);
    36         log.info("------->order-service中修改订单状态结束");
    37 
    38         log.info("------->下单结束");
    39     }
    40 
    41 }

        7.5、StorageService的FeignClient

    1 @FeignClient(value = "seata-storage-service")
    2 public interface StorageService {
    3 
    4     @PostMapping(value = "/storage/decrease")
    5     CommonResult decrease(@RequestParam("productId") Long productId,@RequestParam("count") Integer count);
    6 }
    View Code 

        7.6、AccountService的FeignClient

    1 @FeignClient(value = "seata-account-service")
    2 public interface AccountService {
    3 
    4     @PostMapping(value = "/account/decrease")
    5     void decrease(@RequestParam("userId") Long userId,@RequestParam("money") BigDecimal money);
    6 
    7 }
    View Code

        7.7、OrderDao

    1 @Mapper
    2 public interface OrderDao {
    3     // 新建订单
    4     public int insert(Order order);
    5 
    6     // 更新订单 从0修改为1
    7     public Order update(@Param("id") Long id, @Param("userId") Long userId, @Param("status") Integer status);
    8 }
    View Code

        7.8、实体类CommonResult

     1 @Data
     2 @AllArgsConstructor
     3 @NoArgsConstructor
     4 public class CommonResult<T> {
     5 
     6     private int code;
     7     private String msg;
     8     private T data;
     9 
    10     public CommonResult(int code, String msg) {
    11         this.code = code;
    12         this.msg = msg;
    13     }
    14 }
    View Code

        7.9、实体类Order

     1 @Data
     2 @AllArgsConstructor
     3 @NoArgsConstructor
     4 public class Order {
     5     private Long id;
     6     private Long userId;
     7     private Long productId;
     8     private Integer count;
     9     private BigDecimal money;
    10     private Integer status;
    11 
    12 }
    View Code

        7.10、在resource/mapper添加映射文件OrderMapper.xml

     1 <?xml version="1.0" encoding="UTF-8" ?>
     2 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
     3 
     4 <mapper namespace="com.test.springcloud.dao.OrderDao">
     5 
     6     <resultMap id="BaseResultMap" type="com.test.springcloud.entities.Order" >
     7         <id property="id" jdbcType="BIGINT" column="id" />
     8         <result property="userId" jdbcType="BIGINT" column="user_id" />
     9         <result property="productId" jdbcType="BIGINT" column="product_id" />
    10         <result property="count" jdbcType="INTEGER" column="count" />
    11         <result property="money" jdbcType="DECIMAL" column="money" />
    12         <result property="status" jdbcType="INTEGER" column="status" />
    13     </resultMap>
    14 
    15     <insert id="insert" parameterType="com.test.springcloud.entities.Order" useGeneratedKeys="true"
    16             keyProperty="id">
    17         INSERT INTO `order` (id, user_id, product_id, count, money, status )
    18         values(null, #{userId}, #{productId}, #{count}, #{money}, 0)
    19     </insert>
    20 
    21     <select id="update" >
    22         UPDATE `order`  set status = 1
    23         WHERE id = #{id} and status = #{status} and user_id = #{userId}
    24     </select>
    25 </mapper>
    View Code

    Storage库存服务

      1、新建订单模块(springcloud-seata-storage9012)

      2、编辑pom文件,同上

      3、编辑application.yml属性文件,同上

     1 # 端口
     2 server:
     3   port: 9012
     4 
     5 spring:
     6   application:
     7     name: seata-storage-service
     8   #   数据源基本配置
     9   cloud:
    10     nacos:
    11       discovery:
    12         server-addr: localhost:8848
    13     alibaba:
    14       seata:
    15         tx-service-group: my_storage_tx_group
    16   datasource:
    17     driver-class-name: com.mysql.cj.jdbc.Driver
    18     url: jdbc:mysql://localhost 3306/seata_storage?allowPublicKeyRetrieval=true&useSSL=true
    19     username: admin
    20     password: 123456
    21     hikari:
    22       connection-test-query: SELECT 1 FROM DUAL
    23       minimum-idle: 1
    24       maximum-pool-size: 10
    25       pool-name: ${spring.application.name}-CP
    26       idle-timeout: 10000
    27       cachePrepStmts: true
    28       prepStmtCacheSize: 250
    29       prepStmtCacheSqlLimit: 2048
    30       leakDetectionThreshold: 40000
    31 
    32 feign.hystrix.enabled: true
    33 hystrix:
    34   command:
    35     default:
    36       circuitBreaker:
    37         sleepWindowInMilliseconds: 30000
    38         requestVolumeThreshold: 10
    39       execution:
    40         isolation:
    41           strategy: SEMAPHORE
    42           thread:
    43             timeoutInMilliseconds: 100000
    44 
    45 
    46 
    47 mybatis:
    48   mapperLocations: classpath:mapper/*Mapper.xml
    49   # 所有entity别名类所在的包
    50   type-aliases-pachage: com.test.springcloud.entities
    51 
    52 logging:
    53   level:
    54 #    root: debug
    55     com.test.springcloud: debug
    View Code

      4、在resource目录中添加registry.conf配置文件,同上

      5、在resource目录中添加file.conf配置文件,同上

     1 transport {
     2   # tcp udt unix-domain-socket
     3   type = "TCP"
     4   #NIO NATIVE
     5   server = "NIO"
     6   #enable heartbeat
     7   heartbeat = true
     8   # the client batch send request enable
     9   enableClientBatchSendRequest = true
    10   #thread factory for netty
    11   threadFactory {
    12     bossThreadPrefix = "NettyBoss"
    13     workerThreadPrefix = "NettyServerNIOWorker"
    14     serverExecutorThread-prefix = "NettyServerBizHandler"
    15     shareBossWorker = false
    16     clientSelectorThreadPrefix = "NettyClientSelector"
    17     clientSelectorThreadSize = 1
    18     clientWorkerThreadPrefix = "NettyClientWorkerThread"
    19     # netty boss thread size,will not be used for UDT
    20     bossThreadSize = 1
    21     #auto default pin or 8
    22     workerThreadSize = "default"
    23   }
    24   shutdown {
    25     # when destroy server, wait seconds
    26     wait = 3
    27   }
    28   serialization = "seata"
    29   compressor = "none"
    30 }
    31 
    32 service {
    33   #transaction service group mapping
    34   vgroupMapping.my_storage_tx_group = "default"
    35   #only support when registry.type=file, please don't set multiple addresses
    36   default.grouplist = "127.0.0.1:8091"
    37   #degrade, current not support
    38   enableDegrade = false
    39   #disable seata
    40   disableGlobalTransaction = false
    41 }
    42 
    43 client {
    44   rm {
    45     asyncCommitBufferLimit = 10000
    46     lock {
    47       retryInterval = 10
    48       retryTimes = 30
    49       retryPolicyBranchRollbackOnConflict = true
    50     }
    51     reportRetryCount = 5
    52     tableMetaCheckEnable = false
    53     reportSuccessEnable = false
    54   }
    55   tm {
    56     commitRetryCount = 5
    57     rollbackRetryCount = 5
    58   }
    59   undo {
    60     dataValidation = true
    61     logSerialization = "jackson"
    62     logTable = "undo_log"
    63   }
    64   log {
    65     exceptionRate = 100
    66   }
    67 }
    View Code

        注意其中的:vgroupMapping.my_storage_tx_group = "default"

      7、编辑业务实现类

        与上雷同,mapper.xml文件如下:

     1 <?xml version="1.0" encoding="UTF-8" ?>
     2 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
     3 
     4 <mapper namespace="com.test.springcloud.dao.StorageDao">
     5 
     6     <resultMap id="BaseResultMap" type="com.test.springcloud.entities.Storage" >
     7         <id property="id" jdbcType="BIGINT" column="id" />
     8         <result property="productId" jdbcType="BIGINT" column="product_id" />
     9         <result property="total" jdbcType="INTEGER" column="total" />
    10         <result property="used" jdbcType="INTEGER" column="used" />
    11         <result property="residue" jdbcType="INTEGER" column="residue" />
    12     </resultMap>
    13 
    14     <select id="decrease" >
    15         UPDATE storage
    16             set used = used + #{count},
    17             residue = residue - #{count}
    18         WHERE product_id = #{productId}
    19     </select>
    20 </mapper>
    View Code

    Account账户服务

      1、新建订单模块(springcloud-seata-storage9012)

      2、编辑pom文件,同上

      3、编辑application.yml属性文件,同上

      4、在resource目录中添加registry.conf配置文件,同上

      5、在resource目录中添加file.conf配置文件,同上

        注意其中的:vgroupMapping.my_account_tx_group = "default"

      7、编辑业务实现类

        与上雷同,mapper.xml文件如下:

     1 <?xml version="1.0" encoding="UTF-8" ?>
     2 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
     3 
     4 <mapper namespace="com.test.springcloud.dao.AccountDao">
     5 
     6     <resultMap id="BaseResultMap" type="com.test.springcloud.entities.Account" >
     7         <id property="id" jdbcType="BIGINT" column="id" />
     8         <result property="userId" jdbcType="BIGINT" column="user_id" />
     9         <result property="total" jdbcType="DECIMAL" column="total" />
    10         <result property="used" jdbcType="DECIMAL" column="used" />
    11         <result property="residue" jdbcType="DECIMAL" column="residue" />
    12     </resultMap>
    13 
    14     <select id="decrease" >
    15         UPDATE account
    16             set used = used + #{money},
    17             residue = residue - #{money}
    18         WHERE user_id = #{userId}
    19     </select>
    20 </mapper>
    View Code

    验证分布式事务

      1、启动Nacos,然后启动Seata服务端

      2、分别启动order,storage,account服务

      3、查看Seata服务端控制台输出内容:

        三个服务分别注册了 RM 和 TM,都用通道连接

        

      4、浏览器访问地址:http://localhost:9011/order/create?userId=1&productId=1&count=10&money=100,创建订单,开始业务

      5、查看数据库,order,storage,account,三个数据库数据的变化

         order表

        

        storage表

        

        account表

        

         三张表数据正常,符合正确的业务逻辑

      6、在Account服务中,增加一个异常,模式业务失败

     1 @Service
     2 @Slf4j
     3 public class AccountServiceImpl implements AccountService {
     4     @Autowired
     5     private AccountDao storageDao;
     6 
     7     public void decrease(Long userId, BigDecimal money) {
     8         log.info("------->account-service中扣减账户余额开始");
     9         // 模拟业务异常,全局事务回滚
    10         int n = 10/0;
    11         storageDao.decrease(userId, money);
    12         log.info("------->account-service中扣减账户余额结束");
    13     }
    14

      7、重新启动Account服务,且访问地址:http://localhost:9011/order/create?userId=1&productId=1&count=10&money=100,创建订单,开始业务

      8、请求报错,数据无变化,符合正常逻辑,验证Seata分布式事务管理已生效

      9、还可以去掉Order服务中的@GlobalTransactional注解,然后重新启动Order服务

      10、访问地址:http://localhost:9011/order/create?userId=1&productId=1&count=10&money=100,创建订单,开始业务

        当服务报错时,查看数据库,三个数据库的数据异常,不符合正常逻辑

  • 相关阅读:
    nop 配置阿里cdn 联通4g 页面显示不全 查看源代码发现被截断
    HTTP 错误 500.21 模块 IIS Web Core
    nopcommerce4.0 安装步骤
    2017-12-08高级.net 面试小结
    图片上传
    每个部门绩效成绩第二名 sql server 查询 ( 替代 not in )
    Vmware虚拟机与主机连接的三种模式及使用
    PE,VG,PV,LV概念与结构关系
    windows忘记 oracle的用户名和密码
    虚拟机CentOS打开终端设置快捷键
  • 原文地址:https://www.cnblogs.com/h--d/p/12994365.html
Copyright © 2011-2022 走看看