• SpringCloud Alibaba Seata处理分布式事务


    一、简介

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

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

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

    处理过程

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

    二、Seata-Server安装

    这里使用的是1.4.0版本,不同版本之间安装有差异,不能完全照抄(特别是1.0之前的版本)

    1、官网下载压缩包

    解压缩 tar -zxvf seata-server-1.4.0.tar.gz,下面修改的文件都是压缩包自带的,当然GitHub官网可以看到。

    2、修改registry.conf文件

    这个文件主要是用来决定注册中心和配置中心,即seata需要注册到哪以及从哪读取配置。默认是从file.conf读取配置。

    我这里使用spring cloud alibaba的naocs注册中心

    1. vim ./seata/conf/registry.conf 
    2. 主要修改注册中心地址以及账号密码,主要修改两个地址

    注意:注册中心和配置中心我们都选择nacos

    registry {
      # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
      type = "nacos"
      loadBalance = "RandomLoadBalance"
      loadBalanceVirtualNodes = 10
    
      nacos {
        application = "seata-server"
        serverAddr = "192.168.216.132:8848"
        group = "SEATA_GROUP"
        namespace = "d52e8d91-7a2c-4f07-95de-083633fc31e3"
        cluster = "default"
        username = "nacos"
        password = "nacos"
      }
      eureka {
        serviceUrl = "http://localhost:8761/eureka"
        application = "default"
        weight = "1"
      }
      redis {
        serverAddr = "localhost:6379"
        db = 0
        password = ""
        cluster = "default"
        timeout = 0
      }
      zk {
        cluster = "default"
        serverAddr = "127.0.0.1:2181"
        sessionTimeout = 6000
        connectTimeout = 2000
        username = ""
        password = ""
      }
      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 = "nacos"
    
      nacos {
        serverAddr = "192.168.216.132:8848"
        namespace = "d52e8d91-7a2c-4f07-95de-083633fc31e3"
        group = "SEATA_GROUP"
        username = "nacos"
        password = "nacos"
      }
      consul {
        serverAddr = "127.0.0.1:8500"
      }
      apollo {
        appId = "seata-server"
        apolloMeta = "http://192.168.1.204:8801"
        namespace = "application"
        apolloAccesskeySecret = ""
      }
      zk {
        serverAddr = "127.0.0.1:2181"
        sessionTimeout = 6000
        connectTimeout = 2000
        username = ""
        password = ""
      }
      etcd3 {
        serverAddr = "http://localhost:2379"
      }
      file {
        name = "file.conf"
      }
    }

    3、修改file.conf文件

    这个文件用来写配置信息,我这里使用数据库的形式保存seata的数据,我这里使用mysql5.7版本的数据库,如果使用mysql8,需要在url后面添加时区等参数,驱动类也换成com.mysql.cj.jdbc.Driver

    vim seata/conf/file.conf

    ## transaction log store, only used in seata-server
    store {
      ## store mode: file、db、redis
      mode = "db"
    
      ## file store property
      file {
        ## store location dir
        dir = "sessionStore"
        # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
        maxBranchSessionSize = 16384
        # globe session size , if exceeded throws exceptions
        maxGlobalSessionSize = 512
        # file buffer size , if exceeded allocate new buffer
        fileWriteBufferCacheSize = 16384
        # when recover batch read size
        sessionReloadReadSize = 100
        # async, sync
        flushDiskMode = async
      }
    
      ## database store property
      db {
        ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
        datasource = "druid"
        ## mysql/oracle/postgresql/h2/oceanbase etc.
        dbType = "mysql"
        driverClassName = "com.mysql.jdbc.Driver"
        url = "jdbc:mysql://192.168.216.132:3306/seata"
        user = "root"
        password = "123456"
        minConn = 5
        maxConn = 100
        globalTable = "global_table"
        branchTable = "branch_table"
        lockTable = "lock_table"
        queryLimit = 100
        maxWait = 5000
      }
    
      ## redis store property
      redis {
        host = "127.0.0.1"
        port = "6379"
        password = ""
        database = "0"
        minConn = 1
        maxConn = 10
        maxTotal = 100
        queryLimit = 100
      }
    }

    4、建立seata需要的数据库和表

    脚本地址:https://github.com/seata/seata/blob/1.4.0/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_gmt_modified_status` (`gmt_modified`, `status`),
        KEY `idx_transaction_id` (`transaction_id`)
    ) ENGINE = InnoDB
      DEFAULT CHARSET = utf8;
    
    -- 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 = utf8;
    
    -- the table to store lock data
    CREATE TABLE IF NOT EXISTS `lock_table`
    (
        `row_key`        VARCHAR(128) NOT NULL,
        `xid`            VARCHAR(96),
        `transaction_id` BIGINT,
        `branch_id`      BIGINT       NOT NULL,
        `resource_id`    VARCHAR(256),
        `table_name`     VARCHAR(32),
        `pk`             VARCHAR(36),
        `gmt_create`     DATETIME,
        `gmt_modified`   DATETIME,
        PRIMARY KEY (`row_key`),
        KEY `idx_branch_id` (`branch_id`)
    ) ENGINE = InnoDB
      DEFAULT CHARSET = utf8;

    5、启动

    进入bin目录:sh seata-server.sh

    注意:本地启动上面的命令就可以了,但是如果是其他服务器,需要指定IP(对外可以访问),当然:默认的端口也要能对外访问

    sh seata-server.sh -p 8091 -h 192.168.216.132
    
    
    firewall-cmd --add-port=8091/tcp --permanent
    
    firewall-cmd --reload

    6、将seata配置信息刷到nacos

    nacos-config.sh 脚本地址:https://github.com/seata/seata/blob/1.4.0/script/config-center/nacos/nacos-config.sh

    #!/usr/bin/env bash
    # Copyright 1999-2019 Seata.io Group.
    #
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at、
    #
    #      http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.
    
    while getopts ":h:p:g:t:u:w:" opt
    do
      case $opt in
      h)
        host=$OPTARG
        ;;
      p)
        port=$OPTARG
        ;;
      g)
        group=$OPTARG
        ;;
      t)
        tenant=$OPTARG
        ;;
      u)
        username=$OPTARG
        ;;
      w)
        password=$OPTARG
        ;;
      ?)
        echo " USAGE OPTION: $0 [-h host] [-p port] [-g group] [-t tenant] [-u username] [-w password] "
        exit 1
        ;;
      esac
    done
    
    if [[ -z ${host} ]]; then
        host=localhost
    fi
    if [[ -z ${port} ]]; then
        port=8848
    fi
    if [[ -z ${group} ]]; then
        group="SEATA_GROUP"
    fi
    if [[ -z ${tenant} ]]; then
        tenant=""
    fi
    if [[ -z ${username} ]]; then
        username=""
    fi
    if [[ -z ${password} ]]; then
        password=""
    fi
    
    nacosAddr=$host:$port
    contentType="content-type:application/json;charset=UTF-8"
    
    echo "set nacosAddr=$nacosAddr"
    echo "set group=$group"
    
    failCount=0
    tempLog=$(mktemp -u)
    function addConfig() {
      curl -X POST -H "${contentType}" "http://$nacosAddr/nacos/v1/cs/configs?dataId=$1&group=$group&content=$2&tenant=$tenant&username=$username&password=$password" >"${tempLog}" 2>/dev/null
      if [[ -z $(cat "${tempLog}") ]]; then
        echo " Please check the cluster status. "
        exit 1
      fi
      if [[ $(cat "${tempLog}") =~ "true" ]]; then
        echo "Set $1=$2 successfully "
      else
        echo "Set $1=$2 failure "
        (( failCount++ ))
      fi
    }
    
    count=0
    for line in $(cat $(dirname "$PWD")/config.txt | sed s/[[:space:]]//g); do
      (( count++ ))
        key=${line%%=*}
        value=${line#*=}
        addConfig "${key}" "${value}"
    done
    
    echo "========================================================================="
    echo " Complete initialization parameters,  total-count:$count ,  failure-count:$failCount "
    echo "========================================================================="
    
    if [[ ${failCount} -eq 0 ]]; then
        echo " Init nacos config finished, please start seata-server. "
    else
        echo " init nacos config fail. "
    fi
    nacos-config.sh

    注意:windows系统下,从官网复制的脚本内容,如果直接放到Linux系统上,执行会报错(格式问题),使用dos2unix刷下格式

    yum install -y dos2unix
    
    dos2unix nacos-config.sh

    脚本有了,配置文件地址:https://github.com/seata/seata/blob/1.4.0/script/config-center/config.txt

    transport.type=TCP
    transport.server=NIO
    transport.heartbeat=true
    transport.enableClientBatchSendRequest=false
    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
    service.vgroupMapping.abc-tx-group=default
    service.default.grouplist=127.0.0.1:8091
    service.enableDegrade=false
    service.disableGlobalTransaction=false
    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=false
    client.rm.sqlParserType=druid
    client.rm.reportSuccessEnable=false
    client.rm.sagaBranchRegisterEnable=false
    client.tm.commitRetryCount=5
    client.tm.rollbackRetryCount=5
    client.tm.defaultGlobalTransactionTimeout=60000
    client.tm.degradeCheck=false
    client.tm.degradeCheckAllowTimes=10
    client.tm.degradeCheckPeriod=2000
    store.mode=db
    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
    store.db.datasource=druid
    store.db.dbType=mysql
    store.db.driverClassName=com.mysql.jdbc.Driver
    store.db.url=jdbc:mysql://192.168.216.132:3306/seata?useUnicode=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.queryLimit=100
    store.db.lockTable=lock_table
    store.db.maxWait=5000
    store.redis.host=127.0.0.1
    store.redis.port=6379
    store.redis.maxConn=10
    store.redis.minConn=1
    store.redis.database=0
    store.redis.password=null
    store.redis.queryLimit=100
    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
    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.log.exceptionRate=100
    transport.serialization=seata
    transport.compressor=none
    metrics.enabled=false
    metrics.registryType=compact
    metrics.exporterList=prometheus
    metrics.exporterPrometheusPort=9898

    命令参数:

    sh ${SEATAPATH}/script/config-center/nacos/nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP -t 5a3c7d6c-f497-4d68-a71a-2e5e3340b3ca -u username -w password
     
    
    参数描述:
    
    -h:主机,默认值是本地主机。
    
    -p:端口,默认值为8848。
    
    -g:配置分组,默认值为"SEATA_GROUP"。
    
    -t:租户信息,对应于Nacos的命名空间ID字段,默认值为""。
    
    -u:用户名,权限控制上的nacos 1.2.0+,默认值为""。
    
    -w:密码,在权限控制上的nacos 1.2.0+,默认值为""。

     

    三、SpringBoot整合seata1.4

    1、项目结构

    seata规定:每个业务库需要一张表

    -- 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`
    (
        `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(6)  NOT NULL COMMENT 'create datetime',
        `log_modified`  DATETIME(6)  NOT NULL COMMENT 'modify datetime',
        UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
    ) ENGINE = InnoDB
      AUTO_INCREMENT = 1
      DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';

    2、POM文件

    注意红色字体seata的引用

    <?xml version="1.0" encoding="UTF-8"?>
    <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>cloud2020</artifactId>
            <groupId>com.atguigu.springcloud</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>seata-order-service2001</artifactId>
    
        <properties>
            <maven.compiler.source>8</maven.compiler.source>
            <maven.compiler.target>8</maven.compiler.target>
        </properties>
    
        <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>
                        <groupId>io.seata</groupId>
                        <artifactId>seata-all</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>io.seata</groupId>
                <artifactId>seata-all</artifactId>
                <version>1.4.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>
            </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>
            </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>
    </project>

    3、application.yml

    每一个微服务的seata配置都是一样的(除了application-id)

    server:
      port: 2001
    
    spring:
      application:
        name: seata-order-service
      cloud:
        nacos:
          discovery:
            server-addr: 192.168.216.132:8848
      datasource:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://192.168.216.132:3306/seata_order
        username: root
        password: 123456
    
    seata:
      enabled: true
      application-id: seata-order-service
      # 事务群组(可以每个应用独立取名,也可以使用相同的名字),要与服务端nacos-config.txt中service.vgroup_mapping的后缀对应
      tx-service-group: abc-tx-group
      config:
        type: nacos
        # 需要和server在同一个注册中心下
        nacos:
          namespace: d52e8d91-7a2c-4f07-95de-083633fc31e3
          serverAddr: 192.168.216.132:8848
          # 需要server端(registry和config)、nacos配置client端(registry和config)保持一致
          group: SEATA_GROUP
          username: nacos
          password: nacos
      registry:
        type: nacos
        nacos:
          # 需要和server端保持一致,即server在nacos中的名称,默认为seata-server
          application: seata-server
          server-addr: 192.168.216.132:8848
          group: SEATA_GROUP
          namespace: d52e8d91-7a2c-4f07-95de-083633fc31e3
          username: nacos
          password: nacos
    
    feign:
      hystrix:
        enabled: false
    
    logging:
      level:
        io:
          seata: info
    
    mybatis:
      mapperLocations: classpath:mapper/*.xml

    4、file.conf

    每一个微服务的file.conf都是一样的,红色部分根据实际情况配置

    transport {
      # tcp, unix-domain-socket
      type = "TCP"
      #NIO, NATIVE
      server = "NIO"
      #enable heartbeat
      heartbeat = true
      # the client batch send request enable
      enableClientBatchSendRequest = true
      #thread factory for netty
      threadFactory {
        bossThreadPrefix = "NettyBoss"
        workerThreadPrefix = "NettyServerNIOWorker"
        serverExecutorThread-prefix = "NettyServerBizHandler"
        shareBossWorker = false
        clientSelectorThreadPrefix = "NettyClientSelector"
        clientSelectorThreadSize = 1
        clientWorkerThreadPrefix = "NettyClientWorkerThread"
        # netty boss thread size
        bossThreadSize = 1
        #auto default pin or 8
        workerThreadSize = "default"
      }
      shutdown {
        # when destroy server, wait seconds
        wait = 3
      }
      serialization = "seata"
      compressor = "none"
    }
    service {
      #transaction service group mapping
      vgroupMapping.abc-tx-group = "default"
      #only support when registry.type=file, please don't set multiple addresses
      default.grouplist = "127.0.0.1:8091"
      #degrade, current not support
      enableDegrade = false
      #disable seata
      disableGlobalTransaction = false
    }
    
    client {
      rm {
        asyncCommitBufferLimit = 10000
        lock {
          retryInterval = 10
          retryTimes = 30
          retryPolicyBranchRollbackOnConflict = true
        }
        reportRetryCount = 5
        tableMetaCheckEnable = false
        reportSuccessEnable = false
        sagaBranchRegisterEnable = false
      }
      tm {
        commitRetryCount = 5
        rollbackRetryCount = 5
        defaultGlobalTransactionTimeout = 60000
        degradeCheck = false
        degradeCheckPeriod = 2000
        degradeCheckAllowTimes = 10
      }
      undo {
        dataValidation = true
        onlyCareUpdateColumns = true
        logSerialization = "jackson"
        logTable = "undo_log"
      }
      log {
        exceptionRate = 100
      }
    }

    5、registry.conf

    每一个微服务的registry.conf都是一样的,红色部分根据实际情况配置

    registry {
      # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa、custom
      type = "nacos"
      loadBalance = "RandomLoadBalance"
      loadBalanceVirtualNodes = 10
    
      nacos {
        application = "seata-server"
        serverAddr = "192.168.216.132:8848"
        group = "SEATA_GROUP"
        namespace = "d52e8d91-7a2c-4f07-95de-083633fc31e3"
        username = "nacos"
        password = "nacos"
      }
      eureka {
        serviceUrl = "http://localhost:8761/eureka"
        weight = "1"
      }
      redis {
        serverAddr = "localhost:6379"
        db = "0"
        password = ""
        timeout = "0"
      }
      zk {
        serverAddr = "127.0.0.1:2181"
        sessionTimeout = 6000
        connectTimeout = 2000
        username = ""
        password = ""
      }
      consul {
        serverAddr = "127.0.0.1:8500"
      }
      etcd3 {
        serverAddr = "http://localhost:2379"
      }
      sofa {
        serverAddr = "127.0.0.1:9603"
        region = "DEFAULT_ZONE"
        datacenter = "DefaultDataCenter"
        group = "SEATA_GROUP"
        addressWaitTime = "3000"
      }
      file {
        name = "file.conf"
      }
      custom {
        name = ""
      }
    }
    
    config {
      # file、nacos 、apollo、zk、consul、etcd3、springCloudConfig、custom
      type = "nacos"
    
      nacos {
        serverAddr = "192.168.216.132:8848"
        namespace = "d52e8d91-7a2c-4f07-95de-083633fc31e3"
        group = "SEATA_GROUP"
        username = "nacos"
        password = "nacos"
      }
      consul {
        serverAddr = "127.0.0.1:8500"
      }
      apollo {
        appId = "seata-server"
        apolloMeta = "http://192.168.1.204:8801"
        namespace = "application"
        apolloAccesskeySecret = ""
      }
      zk {
        serverAddr = "127.0.0.1:2181"
        sessionTimeout = 6000
        connectTimeout = 2000
        username = ""
        password = ""
      }
      etcd3 {
        serverAddr = "http://localhost:2379"
      }
      file {
        name = "file.conf"
      }
      custom {
        name = ""
      }
    }

    6、DataSourceProxyConfig数据源代理

    使用seata数据源代理

    package com.atguigu.springcloud.alibaba.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;
    
    @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();
        }
    }

    7、业务类使用@GlobalTransactional

    OrderServiceImpl

    package com.atguigu.springcloud.alibaba.service.impl;
    
    import com.atguigu.springcloud.alibaba.dao.OrderDao;
    import com.atguigu.springcloud.alibaba.domain.Order;
    import com.atguigu.springcloud.alibaba.service.AccountService;
    import com.atguigu.springcloud.alibaba.service.OrderService;
    import com.atguigu.springcloud.alibaba.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 = "fsp-create-order", rollbackFor = Exception.class)
        public void create(Order order) {
            log.info("----->开始新建订单");
            //新建订单
            orderDao.create(order);
    
            //扣减库存
            log.info("----->订单微服务开始调用库存,做扣减Count");
            storageService.decrease(order.getProductId(), order.getCount());
            log.info("----->订单微服务开始调用库存,做扣减end");
    
            //扣减账户
            log.info("----->订单微服务开始调用账户,做扣减Money");
            accountService.decrease(order.getUserId(), order.getMoney());
            log.info("----->订单微服务开始调用账户,做扣减end");
    
    
            //修改订单状态,从零到1代表已经完成
            log.info("----->修改订单状态开始");
            orderDao.update(order.getUserId(), 0);
            log.info("----->修改订单状态结束");
    
            log.info("----->下订单结束了");
        }
    }

    AccountService 和 StorageService

    @FeignClient(value = "seata-account-service")
    public interface AccountService {
        @PostMapping(value = "/account/decrease")
        CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
    }
    
    
    @FeignClient(value = "seata-storage-service")
    public interface StorageService {
        @PostMapping(value = "/storage/decrease")
        CommonResult decrease(@RequestParam("productId") Long productId, @RequestParam("count") Integer count);
    }

    SeataOrderMainApp2001

    @EnableDiscoveryClient
    @EnableFeignClients
    @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//取消数据源自动创建的配置
    @MapperScan({"com.atguigu.springcloud.alibaba.dao"})
    public class SeataOrderMainApp2001 {
        public static void main(String[] args) {
            SpringApplication.run(SeataOrderMainApp2001.class, args);
        }
    }

    另外两个微服务搭建过程是一样的。

    四、注意事项

    注意:seata1.4版本和之前的版本不一样,即使设置了service.vgroupMapping,但在启用项目时,还是会提醒缺少,需要对每个微服务新增:service.vgroupMapping.微服务名称-fescar-service-group这样结构的配置,seata.global_table.transaction_service_group字段长度可能不够,需要增加。

  • 相关阅读:
    如何学习新技术
    创建模式之工厂方法模式
    SQL Server 存储过程
    ASP.NET Cache的一些总结
    ACE_TSS研究
    利用Thunk让C++成员函数变回调函数
    ACE内存映射学习
    ACE的初始化
    双检锁模式学习
    ACE_Task笔记
  • 原文地址:https://www.cnblogs.com/jwen1994/p/14487130.html
Copyright © 2020-2023  润新知