• 微服务架构 | 分布式事务 - [Seata]


    §1 分布式事务

    事务
    事务的作用是使用户的操作一口气成功,要么成功提交,要么都失败回滚

    严格意义上的事务特性

    • 原子性(Atomicity),事务内的所有操作要么都执行,要么都不执行
    • 一致性(Consistency),数据是满足完整性约束的,不会存在中间状态的数据
    • 隔离性(Isolation),事务内部的数据对于其他事务来说是隔离的,因此多个事务可以同时进行
    • 持久性(Durability),事务完成后其数据进入持久化,后续操作和故障不会影响已经持久化的数据或即使有影响也可以恢复

    为什么会出现,分布式事务是什么
    传统事务是数据库层面的行为,它本身就是数据库提供的机制
    单机项目只需要连接一个数据库,事务回滚只需要回滚自身

    但当场景来到分布式系统,如下图
    事务实际生命周期是从开始到结束,中间经历(假设说)3 个数据库实例,并不是单一数据库的行为
    即使每个数据库都开启传统事务,那也是三个相互独立的事件,无法保证其同时同步
    在这里插入图片描述
    分布式场景中不仅可能涉及多个个数据库实例,还有可能涉及其他中间件、其他服务甚至其他系统
    比如订单服务调用了库存服务,运输系统调用了短信通知系统等,此时更不可能通过传统事务控制
    因此,分布式事务其实是一种业务上的概念,这是因为在分布式场景下,传统数据库事务是失效的

    达成分布式事务的思路和方式
    分布式事务整体上可以整理成两套大思路:强一致性最终一致性
    二者的本质区别是对事务原子性和一致性的执行力度,即实际执行中是否允许中间状态

    但是,在各个技术实际实现时,出于对各种业务情况的妥协会出现不同方向的妥协
    比如 2PC 最初是一个强一致性的思路,但 AT 在它的基础上允许它出现中间状态(写隔离)
    因此,现在大部分场景下是按照开发手感划分的,即是主要依赖听口令提交还是事后补偿来进行区分

    强一致性最终一致性
    允许中间状态×
    关注焦点事务是否可以执行,确认后才会提交事务怎么执行,事务步骤最终可以都完成或都不完成
    实现2PC(AT / XA) / 3PC / TCC本地消息表 / 消息事务 / 最大努力通知 / SAGA
    技术依赖依赖独立的分布式事务平台/很重的业务耦合耦合于业务和消息中间件
    事务层面持久化层 / 业务(TCC)业务
    开发复杂度低 / 高(TCC)
    事务解决程度不完善 / 完善(TCC)完善

    §2 Seata 简介

    高性能和简单易用的分布式事务服务
    提供了多种模式:AT | XA | TCC | SAGA

    术语
    Seata 官网给出了三个概念的诠释和示意图,如下,

    • TC (Transaction Coordinator) - 事务协调者
      维护全局和分支事务的状态,驱动全局事务提交或回滚

    • TM (Transaction Manager) - 事务管理器
      定义全局事务的范围:开始全局事务、提交或回滚全局事务

    • RM (Resource Manager) - 资源管理器
      管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚
      在这里插入图片描述

    TC 和 TM 在诠释和示意图中非常容易混淆,可以使用下面两个视角对应上图
    服务调用视角,核心是 Service,是由 Service 开始的调用,逻辑上它直接引用了其他服务
    事务交互视角,核心是 TC,虽然事务依然由 TM(Service) 发起,但是是由 TC 进行实际驱动

    • TM 要求分布式事务,RM 本身具有本地事务能力,TC 充当本地事务演变到分布式事务的桥梁
    • TC 相当于 TM 的代理方,TM/RM/TC 分别对应甲方、乙方、乙方分包
    • 在技术上,可以视为 TC 为分布式事务全局锁的实际支配者
    • TC 承接由 TM 发起的分布式事务
    • TC 进行分布式事务的众多参与方(Storage、WMS、Trans)的调度
    • TC 行使对分布式事务的众多参与方(Storage、WMS、Trans)的监督提交或回滚
    • TC 负责向 TM 交付分布式事务的执行结果
      在这里插入图片描述

    模式
    seata 提供了多种模式:AT | XA | TCC | SAGA,在使用和官网推荐上重点是 AT 模式

    前置:原始的 2PC 事务流程
    分布式事务的难点

    • 如何同步各个分支事务的执行状态,分支事务执行状态直接影响所有分支状态和整个分布式事务最终是提交还是回滚
    • 如何回滚或提交,重点是回滚

    原始的 2PC 事务二阶段的职责

    • 一阶段(Prepare phase)
      • TM 发起分布式事务
      • RM 发起本地事务
      • RM 的本地事务持有锁
      • RM 的本地事务执行,生成对应的 Undo / Redo 日志
      • RM 的本地事务依然持有锁,并且不提交
    • 二阶段(commit phase)
      • TM 处理接收到的 RM 的反馈
      • 所有 RM 的本地事务都执行成功时,通知所有 RM 提交事务
      • 任意 RM 的本地事务失败或超时时,通知所有 RM 回滚事务
      • 所有 RM 的本地事务结束并释放锁
      • TM 发起的分布式事务结束

    在这里插入图片描述
    原始的 2PC 事务的缺点
    原始 2PC 事务最大的缺点是 阻塞 问题
    即使事务参与者中的一部分已经处理完分布式事务中和自己相关的部分
    依然不能释放本地事务中的锁资源,但实际上这属于无效的等待,等待的时间被浪费了

    AT
    AT(自动事务:Auto Transaction) 模式,是对 2PC(两阶段提交协议) 的演变:
    AT 模式的流程

    • 在原始 2PC 的基础上优化
    • 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源,但保留相关资源的行级锁
    • 二阶段:提交异步化,非常快速地完成;回滚通过一阶段的回滚日志进行反向补偿
      即,RM 可以不等待其他 RM 而进行独立的提交,然后去处理其他业务,出了问题能回滚到正常数据即可

    AT 模式提交回滚
    依赖于对 sql 的解析,从原始 sql 中解析相关数据的前后镜像,并依赖它们完成回滚操作
    这种方式可以看做对基于消息通知的分布式事务的 2PC 式改造,并由框架自动完成

    AT 模式缺点

    • 不能支持复杂 sql
    • 只能支持对数据库的分布式事务,如果涉及其他中间件则失效

    2PC 阻塞问题 是它为了实现强一致性做出的悲观让步,2PC 可以看做一个悲观锁,它认为在一个事务中,各个 RM 是很可能出问题的,所以它会一直让 RM 持有资源锁不释放,直到所有 RM 都确认正常的完成了。
    而在 AT 模式,它相当于乐观锁,认为一个事务中,不出现问题一切正常才是大概率,所以 RM 应该执行完成就提交,然后去处理其他事务(注意,这个事务和上一个事务对应不同的全局锁,即对应不同的资源)。当其他 RM 异常后,本 RM 对于对应的分布式事务进行回滚(Undo)即可。如下图,
    在这里插入图片描述
    XA
    XA 模式的流程

    • 与原始 2PC 基本一致
    • 前后镜像替换成 XA start / XA end,资源行锁替换成 XA prepare

    XA 模式提交回滚 依赖于对数据源和数据源连接的 XA 代理,其实现机理比 TA 更底层
    最好的分布式事务其实是数据库天然支持分布式事务
    XA 模式就是用代理数据源的形式变相实现这一效果
    在这里插入图片描述
    XA 模式的缺点

    • 无法支持所有数据库,虽然主流数据库都支持了

    TCC
    TCC 模式的流程

    • 将每个 RM 分为 try/comfirm/cancel 三个阶段
    • try 是用于确认 RM 是否可以执行的阶段,相当于在业务层面上锁
    • comfirm 是正式执行的阶段,对应 commit
    • cancel 是放弃阶段,对应 rollback

    比如对于扣减库存,try 对应预占库存、comfirm 对应正式扣减库存、cancel 对应预占库存的释放

    TCC 模式泛用性极高,但 try/comfirm/cancel 三个阶段都需要开发人员手撸代码,因此业务侵入极高

    SAGA
    saga 是一种长事务解决方案,在Saga模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务都由业务开发实现,如下图
    在这里插入图片描述
    这是一种按流程机制处理分布式事务的方式

    各模式横向比较

    ATXATCCSAGA
    一致性级别最终一致性强一致性强一致性最终一致性
    侵入中?
    保障redo/undo 日志补偿数据源/数据源连接的 XA 代理手撸补偿流程机制

    §3 搭建 seata-server

    版本选择
    不同版本的 Seata-server 配置使用上差别较大,这里使用 1.5.2(这组件简直反复横跳)
    1.5.2 版本的配置都被合并到了一个 application.yml 中
    同时对 nacos 作为配置中心的支持程度也已经很完善了

    下载 server
    官网Github Release 页面 下载需要的版本

    解压

    cd /opt
    tar -zxvf seata-server-1.5.2.tar.gz 
    
    • 1
    • 2

    目录结构
    在这里插入图片描述

    配置
    application.yml,重点配置 seata 的注册中心、配置中心

    cd /opt/seata/conf
    vim application.yml
    
    • 1
    • 2
    # 此文件可以只用于配置注册中心、配置中心、控制台等
    # 一些 server 端的琐碎配置可以直接在 配置中心 中指定
    server:
      port: 7091
    
    spring:
      application:
        name: seata-server
    
    logging:
      config: classpath:logback-spring.xml
      file:
        path: ${user.home}/logs/seata
    
    # seata 控制台
    console:
      user:
        username: seata
        password: seata
    
    seata:
      # seata 的配置中心
      config:
        # support: nacos 、 consul 、 apollo 、 zk  、 etcd3
        type: nacos
        nacos:
          server-addr: 192.168.3.10:8848
          namespace:
          group: SEATA_GROUP
          username: nacos
          password: nacos
          ##if use MSE Nacos with auth, mutex with username/password attribute
          #access-key: ""
          #secret-key: ""
          data-id: seataServer.properties
      # seata 的注册中心
      registry:
        # support: nacos 、 eureka 、 redis 、 zk  、 consul 、 etcd3 、 sofa
        type: nacos
        preferred-networks: 30.240.*
        nacos:
          application: seata-server
          server-addr: 192.168.3.10:8848
          group: SEATA_GROUP
          namespace:
          cluster: default
          username: nacos
          password: nacos
          ##if use MSE Nacos with auth, mutex with username/password attribute
          #access-key: ""
          #secret-key: ""
    
    # 下面的配置可以放到 nacos 中去
    #  server:
    #  store:
    #  metrics:
    #  transport:
    
      security:
        secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
        tokenValidityInMilliseconds: 1800000
    
    
    • 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

    nacos 中配置,重点配置 seata-server 参数(比如事务组)、存储模式等,官网上相关说明如下图
    在这里插入图片描述
    配置全文可以从如下地址获取

    https://github.com/seata/seata/tree/develop/script/config-center
    
    • 1

    对应不需要的配置,比如设置 store.mode=db 后所有 store.file.* 的内容

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

    准备 seata 库
    官方为 seata 使用的数据库提供了初始化脚本,初始化脚本可以通过两个位置获取

    https://github.com/seata/seata/blob/develop/script/server/db/mysql.sql
    
    • 1

    使用 url 时注意匹配 seata 的版本(切换对应分支),或 安装目录/script/server/db 中
    在这里插入图片描述

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

    启动 seata-server

    cd /opt/seata/bin
    ./seata-server.sh
    
    • 1
    • 2

    验证
    在这里插入图片描述

    §4 搭建 seata-client

    client 涉及各组件版本
    下面示例使用 springcloud alibaba + nacos + mysql
    版本说明页面参考 github wiki

    组件版本选择原因
    Seata1.5.2因为 seata 不同版本配置区别较大,所以尽量使用新版本
    Spring Cloud Alibaba2.2.8.RELEASE由 seata 版本确定
    Nacos2.1.0由 seata 版本确定
    Spring CloudHoxton.SR12由 springcloud-alibaba 版本确定
    Spring Boot2.3.12.RELEASE由 springcloud-alibaba 版本确定
    druid-spring-boot-starter1.2.11ide 自动推荐

    注意:版本选择尤为关键,版本相互不兼容可能出现各种型号的启动异常、配置无提示、配置不生效等等问题

    依赖

    <dependency>
        <groupId>com.alibaba.cloudgroupId>
        <artifactId>spring-cloud-starter-alibaba-seataartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4

    Undo 表

    CREATE TABLE IF NOT EXISTS `undo_log`
    (
        `branch_id`     BIGINT       NOT NULL COMMENT 'branch transaction id',
        `xid`           VARCHAR(128) 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 = utf8mb4 COMMENT ='AT transaction mode undo table';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    配置

    seata:
      enabled: true
      application-id: seata-stock-service
      #与 server 端 service.vgroupMapping.[learning]=default 括号中一致
      #此配置用于定义事务组,约等于 GROUP 之于 nacos,常用于划分机房
      tx-service-group: learning 
      # 定义 seata-client 的配置中心
      config:
        type: nacos
        nacos:
          namespace: "" #为空默认是public,更改其他值则使用对应名字的 nacos 命名空间
          server-addr: 192.168.3.10:8848
          group: SEATA_GROUP
          username: "nacos"
          password: "nacos"
          ##if use MSE Nacos with auth, mutex with username/password attribute
          #access-key: ""
          #secret-key: ""
          data-id: seata-stock-service.yml #具体配置的位置
      # 定义 seata-client 的注册中心,即 seata-server ,即 TC
      registry:
        type: nacos
        nacos:
          application: seata-server # 与 server 端 spring.application.name 一致
          server-addr: 192.168.3.10:8848
          group : "SEATA_GROUP"
          namespace: ""
          username: "nacos"
          password: "nacos"
    
    • 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

    nacos
    service.vgroupMapping.learning
    用于指定 seata-server 的集群名
    需与 seata-server 端配置 service.vgroupMapping.learning=default 保持一致,如下图
    在这里插入图片描述
    seata-stock-service.yml
    用于配置 seata-client 的各种参数
    名字与上面 yml 配置中 seata.config.nacos.data-id 保持一致
    此配置文件中配置均有默认值

    seata:
      enabled: true
      application-id: seata-so-service
      tx-service-group: learning
      enable-auto-data-source-proxy: true
      data-source-proxy-mode: AT
      use-jdk-proxy: false
      client:
        rm:
          async-commit-buffer-limit: 10000
          report-retry-count: 5
          table-meta-check-enable: false
          report-success-enable: false
          saga-branch-register-enable: false
          saga-json-parser: fastjson
          saga-retry-persist-mode-update: false
          saga-compensate-persist-mode-update: false
          tcc-action-interceptor-order: -2147482648 #Ordered.HIGHEST_PRECEDENCE + 1000
          sql-parser-type: druid
          lock:
            retry-interval: 10
            retry-times: 30
            retry-policy-branch-rollback-on-conflict: true
        tm:
          commit-retry-count: 5
          rollback-retry-count: 5
          default-global-transaction-timeout: 60000
          degrade-check: false
          degrade-check-period: 2000
          degrade-check-allow-times: 10
          interceptor-order: -2147482648 #Ordered.HIGHEST_PRECEDENCE + 1000
        undo:
          data-validation: true
          log-serialization: jackson
          log-table: undo_log
          only-care-update-columns: true
          compress:
            enable: true
            type: zip
            threshold: 64k
        load-balance:
          type: RandomLoadBalance
          virtual-nodes: 10
      service:
        vgroup-mapping:
          learning: default
        grouplist:
          default: 192.168.3.10:8091
        enable-degrade: false
        disable-global-transaction: false
      transport:
        shutdown:
          wait: 3
        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
          worker-thread-size: default
          boss-thread-size: 1
        type: TCP
        server: NIO
        heartbeat: true
        serialization: seata
        compressor: none
        enable-tm-client-batch-send-request: false
        enable-rm-client-batch-send-request: true
        rpc-rm-request-timeout: 30000
        rpc-tm-request-timeout: 30000
      log:
        exception-rate: 100
      tcc:
        fence:
          log-table-name: tcc_fence_log
          clean-period: 1h
      saga:
        enabled: false
        state-machine:
          table-prefix: seata_
          enable-async: false
          async-thread-pool:
            core-pool-size: 1
            max-pool-size: 20
            keep-alive-time: 60
          trans-operation-timeout: 1800000
          service-invoke-timeout: 300000
          auto-register-resources: true
          resources:
            - classpath*:seata/saga/statelang/**/*.json
          default-tenant-id: 000001
          charset: UTF-8
    
    • 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

    seata.tx-service-group 和 seata.service.vgroup-mapping.learning 的关系
    以使用 nacos 为例
    seata 的 server 端启动后,会向 nacos 中组成服务,如下图
    在这里插入图片描述
    其详情如下图,其中
    服务名即 server 端 yml 配置中 spring.application.name,client 端中 seata.registry.nacos.application 指向此服务名
    seata.service.vgroup-mapping.learning 即 server 端配置的集群名
    在这里插入图片描述

    在 client 端中:

    • 通过 seata.tx-service-group 配置指定事务组名

    • 通过配置 seata.service.vgroup-mapping.事务组名 获取集群名,若无法获取则出现下面提示

       can not get cluster name in registry config 'service.vgroupMapping.learning', please make sure registry config correct
      
      • 1
    • 最后获取名为 seata.registry.nacos.application 的 seata-server 服务中,对应集群名下的服务列表作为 TC 端

    • 若上述配置没有对应上,会出现下面提

      no available service found in cluster 'default', please make sure registry config correct and keep your seata server running
      
      • 1

    启动验证
    client 启动正常后,除正常的启动信息外,可见下面日志信息

    register RM success. client version:1.5.2, server version:1.5.2,channel:[id: 0xec773717, L:/192.168.3.7:53074 - R:/192.168.3.10:8091]
    
    • 1

    §5 业务示例

    启动并注册两个服务

    • 销售单 so,使用数据库 so
    • 库存 stock,使用数据库 storage

    两个服务的依赖配置等参考上面

    业务简述
    so 服务中提供一个服务 /so,模拟下单,下单后先在 so 库中插入订单信息
    随后 so 服务调用 stock 服务中 /stock 接口扣减库存,
    若上面两步成功,则下单成功,否则回滚

    so 代码
    注意 @GlobalTransactional(name = “add-so”)

    @RequestMapping(value = "", method = RequestMethod.POST)
    @GlobalTransactional(name = "add-so")
    @ResponseBody
    public CommonResult<SoEntity> add(@RequestBody SoEntity entity){
        entity = new SoEntity();
        entity.setProductId(1L);
        entity.setCount(1);
        entity.setMoney(new BigDecimal(1));
        entity.setUserId(1L);
        entity.setStatus(0);
    
        int count = soManager.add(entity);//1
        stockOpenfeignService.stock(new StockEntity());//2
        //int i = 1/0; //3
        if (0!= count)
            return new CommonResult<SoEntity>(200,"下单成功 for: " + this.serverPort ,entity);
        else return new CommonResult<>(400,"下单失败 for: " + this.serverPort);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    @FeignClient(value = "seata-stock-service")
    public interface StockOpenfeignService {
    
        @RequestMapping(value = "/stock", method = RequestMethod.POST)
        CommonResult<StockEntity> stock(@RequestBody StockEntity entity);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    stock 代码

    @RequestMapping(value = "", method = RequestMethod.POST)
    @ResponseBody
    public CommonResult<StockEntity> add(@RequestBody StockEntity entity){
        entity = new StockEntity();
        entity.setId(1L);
        entity.setProductId(1L);
        entity.setCount(1);
    
        int count = stockManager.stock(entity);
        if (0!= count)
            return new CommonResult<>(200,"扣减库存成功 for: " + this.serverPort ,entity);
        else return new CommonResult<>(400,"扣减库存成功 for: " + this.serverPort);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    效果
    //1 执行后断点,可见 so 库中 so 表已有数据,undo_log 存在一条日志
    在这里插入图片描述
    在这里插入图片描述
    //2 执行后断点,可见 storage 库中 storage 表已有数据,undo_log 存在一条日志

    在这里插入图片描述
    在这里插入图片描述
    继续执行,业务执行成功,两个 undo_log 表中数据消失
    undo_log 表中的数据是临时数据,用于回滚本地事务使用,当分布式事务完成时会自动删除

    打开==//3==处的注释,并在这里断点,可见数据如下
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    继续执行,//3 处出现异常,数据库数据如下,实现全局回滚
    在这里插入图片描述
    在这里插入图片描述

  • 相关阅读:
    实训十八:RIP2邻居认证
    第6讲:Python中的关键字和标识符的概念
    【一周AI简讯】OpenAI奥特曼王者归来,马斯克AI模型Grok下周开放测试,ChatGPT语音对话功能向所有用户免费开放
    [山东科技大学OJ]1214 Problem B: 编写函数:字符串的连接 之二 (Append Code)
    LLM 大模型学习必知必会系列(十):基于AgentFabric实现交互式智能体应用,Agent实战
    【mcuclub】时钟模块DS1302
    高可用集群 keepalived
    API网关之微服务网关Spring Cloud Gateway与Netflix Zuul
    亚马逊卖家转独立站行动指南
    [C++]:1.初识C++和C语言缺陷补充。
  • 原文地址:https://blog.csdn.net/ZEUS00456/article/details/126042032