SpringCloud-2.0-周阳(24. 分布式事务 - Seata)

Posted ABin-阿斌

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringCloud-2.0-周阳(24. 分布式事务 - Seata)相关的知识,希望对你有一定的参考价值。

上一篇 :23. 熔断降级 - Sentinel

下一篇 :25. 简述 Seata 的原理

  • 声明:原文作者:csdn:yuan_404

文章目录

1 . 分布式事务的问题

  • 在分布式之前

是一台电脑上包含所有的东西 —— 所有的数据、程序所有的内容 ……

  • 慢慢向分布式演变

从 1 对 1 (一个程序对应一个数据库)
到 1 对 N (分库,一个程序对应多个数据库)
再 N 对 N (分布式微服务,多个微服务对应多个数据库)

  • 分布式之后

举例:

  • 单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源,业务操作需要调用三个服务来完成。
  • 此时每个服务内部的数据一致性由本地事务来保证,但是全局的数据一致性问题没法保证
  • 一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题

2 . Seata 简介

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

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

  • 一个 ID + 三个组件

  • Transaction ID XID :全局唯一的事务ID
  • Transaction Coordinator(TC)事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚;
  • Transaction Manager™控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议;
  • Resource Manager(RM)控制分支事务,负责分支注册,状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚;
  • 分布式事务处理过程

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

3 . Seata-Server 安装

  1. 下载地址 :https://github.com/seata/seata/releases

  2. 将 seata-server 解压到指定目录

  3. 修改 conf 目录下的 file.conf 文件

    先对配置文件进行备份

  4. mysql 中新建一个数据库 :seata

  5. 初始化数据库

    运行 conf 目录下的 db_store.sql 文件

    如果没有,请看,SQL 脚本文件地址(选择自己的数据库) :https://github.com/seata/seata/tree/develop/script/server/db
    复制下来去 MySQL 执行

  6. 修改 conf 目录下的 registry.conf 文件

  7. 启动 Nacos

  8. 启动 seata-server

    双击 seata\\bin\\seata-server.bat

4 . 数据库环境搭建

  • 下面就按照该框架图进行设计

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

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

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

  1. 启动 Nacos 、Seata

  2. 创建业务数据库

    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=7 DEFAULT CHARSET=utf8;
     
    SELECT * FROM t_order;
    
    
  4. 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=2 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;
    
    
  5. 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=2 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;
    
    

  6. 建立回滚日志表

    找到 Seat 中回滚日志建表 SQL 脚本 :\\seata\\conf\\db_undo_log.sql

    三个数据库都需要执行该脚本

    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;
    
    

5 . 代码环境搭建

5.1 订单模块

  • 项目目录:

  1. 新建模块 :seata-order-service-2001

  2. 修改 POM

    <dependencies>
        <!--nacos-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--seata-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>seata-all</artifactId>
                    <groupId>io.seata</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
            <version>0.9.0</version>
        </dependency>
        <!--feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--web-actuator-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--mysql-druid-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.37</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
    
  3. 编写 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.jdbc.Driver
        url: jdbc:mysql://localhost:3306/seata_order?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
        username: root
        password: 123456
    
    feign:
      hystrix:
        enabled: false
    
    logging:
      level:
        io:
          seata: info
    
    mybatis:
      mapper-locations: classpath:mapper/*.xml
      type-aliases-package: com.demo.springcloud.pojo
    
  4. 编写 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.jdbc.Driver"
        url = "jdbc:mysql://127.0.0.1:3306/seata"
        user = "root"
        password = "123456"
        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
      
    
    
  5. 编写 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"
      
    
    
  6. 实体类