SpringCloud-快速通关(三)

发布于:2025-04-16 ⋅ 阅读:(34) ⋅ 点赞:(0)

SpringCloud-快速通关(一)

SpringCloud-快速通关(二)

SpringCloud-快速通关(三)

七、Seata - 分布式事务

官网:https://seata.apache.org/zh-cn/

seata服务器的web界面的端口是7091,而8091是TC协调者的TCP端口

7.1、环境搭建

7.1.1、简介

  1. seata有服务器端和客户端,客户端要连上服务器才能使用。
  2. TC(事务协调者)在服务器端:得官网下载: 全局事务的管理者。用于维护全局和分支事务的状态,驱动TM的全局提交和回滚。TM和RM通过TC注册分支和汇报状态。
  3. TM(事务管理器)在客户端:发起全局事务,定义全局事务的范围,操作全局事务的提交和回滚。
  4. RM(资源管理器)在客户端:操作自己分支的事务提交和回滚。
  5. 注意:seata的稳定性非常重要,如果TC崩了,那所有的事务管控都失效。

在这里插入图片描述
在这里插入图片描述

7.1.2、环境搭建

微服务

下载 seata 工程文件,导入到项目中,并在 services 中添加 module 聚合

seata-demo.zip

在这里插入图片描述

SQL

mysql中执行sql创建库表

CREATE DATABASE IF NOT EXISTS `storage_db`;
USE  `storage_db`;
DROP TABLE IF EXISTS `storage_tbl`;
CREATE TABLE `storage_tbl` (
                               `id` int(11) NOT NULL AUTO_INCREMENT,
                               `commodity_code` varchar(255) DEFAULT NULL,
                               `count` int(11) DEFAULT 0,
                               PRIMARY KEY (`id`),
                               UNIQUE KEY (`commodity_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO storage_tbl (commodity_code, count) VALUES ('P0001', 100);
INSERT INTO storage_tbl (commodity_code, count) VALUES ('B1234', 10);

-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
DROP TABLE IF EXISTS `undo_log`;
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;

CREATE DATABASE IF NOT EXISTS `order_db`;
USE  `order_db`;
DROP TABLE IF EXISTS `order_tbl`;
CREATE TABLE `order_tbl` (
                             `id` int(11) NOT NULL AUTO_INCREMENT,
                             `user_id` varchar(255) DEFAULT NULL,
                             `commodity_code` varchar(255) DEFAULT NULL,
                             `count` int(11) DEFAULT 0,
                             `money` int(11) DEFAULT 0,
                             PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
DROP TABLE IF EXISTS `undo_log`;
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;

CREATE DATABASE IF NOT EXISTS `account_db`;
USE  `account_db`;
DROP TABLE IF EXISTS `account_tbl`;
CREATE TABLE `account_tbl` (
                               `id` int(11) NOT NULL AUTO_INCREMENT,
                               `user_id` varchar(255) DEFAULT NULL,
                               `money` int(11) DEFAULT 0,
                               PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO account_tbl (user_id, money) VALUES ('1', 10000);
-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
DROP TABLE IF EXISTS `undo_log`;
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;

7.1.3、seata-server

  1. 下载:https://seata.apache.org/zh-cn/download/seata-server

    apache-seata-2.1.0-incubating-bin.tar.gz

  2. 解压并启动:seata-server.bat;访问:http://localhost:7091/ 账号密码都是seata

7.1.4、微服务配置

在微服务中导入spring-cloud-starter-alibaba-seata依赖

<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>

在resources里配置file.conf文件,每个seata服务都配置

service {
  #transaction service group mapping
  vgroupMapping.default_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
}

file.conf文件完整内容如下:【微服务只需要复制 service 块配置即可】

#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
#

transport {
  # tcp, unix-domain-socket
  type = "TCP"
  #NIO, NATIVE
  server = "NIO"
  #enable heartbeat
  heartbeat = true
  # the tm client batch send request enable
  enableTmClientBatchSendRequest = false
  # the rm client batch send request enable
  enableRmClientBatchSendRequest = true
   # the rm client rpc request timeout
  rpcRmRequestTimeout = 2000
  # the tm client rpc request timeout
  rpcTmRequestTimeout = 30000
  # the rm client rpc request timeout
  rpcRmRequestTimeout = 15000
  #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.default_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
    tableMetaCheckerInterval = 60000
    reportSuccessEnable = false
    sagaBranchRegisterEnable = false
    sagaJsonParser = "fastjson"
    sagaRetryPersistModeUpdate = false
    sagaCompensatePersistModeUpdate = false
    tccActionInterceptorOrder = -2147482648 #Ordered.HIGHEST_PRECEDENCE + 1000
    sqlParserType = "druid"
    branchExecutionTimeoutXA = 60000
    connectionTwoPhaseHoldTimeoutXA = 10000
  }
  tm {
    commitRetryCount = 5
    rollbackRetryCount = 5
    defaultGlobalTransactionTimeout = 60000
    degradeCheck = false
    degradeCheckPeriod = 2000
    degradeCheckAllowTimes = 10
    interceptorOrder = -2147482648 #Ordered.HIGHEST_PRECEDENCE + 1000
  }
  undo {
    dataValidation = true
    onlyCareUpdateColumns = true
    logSerialization = "jackson"
    logTable = "undo_log"
    compress {
      enable = true
      # allow zip, gzip, deflater, lz4, bzip2, zstd default is zip
      type = zip
      # if rollback info size > threshold, then will be compress
      # allow k m g t
      threshold = 64k
    }
  }
  loadBalance {
      type = "XID"
      virtualNodes = 10
  }
}
log {
  exceptionRate = 100
}
tcc {
  fence {
    # tcc fence log table name
    logTableName = tcc_fence_log
    # tcc fence log clean period
    cleanPeriod = 1h
  }
}

7.1.5、测试

  1. 在RM分支事务微服务中启动类上加入@EnableTransactionManagement,在具体事务service方法上加@Transactional
  2. 在TM全局事务微服务中启动类上不加@EnableTransactionManagement,但在事务service方法上要加上@GlobalTransactional。这个注解是核心
  3. 测试结果

测试本地事务:没有@EnableTransactionManagement@Transactional两注解

在这里插入图片描述
@Transactional,但没有@EnableTransactionManagement

会产生一个问题:没有@EnableTransactionManagement,异常数据也能回滚,为什么?

在 Spring Boot 中,不需要 @EnableTransactionManagement 也能回滚数据,因为 Spring Boot 默认开启了事务管理。 如果你在 Spring 传统项目里,可能就需要手动启用事务管理。

所以还是启动类加上@EnableTransactionManagement

@EnableTransactionManagement@Transactional两注解
seata-account, seata-storage单个本地事务是有效的,但是对于seata-order要远程调用seata-account,有异常订单不会创建,但是远程调用的余额扣减却成功了。

这就导致:分布式事务用这两个注解不管用,怎么办?

修改seata-order

去掉@EnableTransactionManagement

@Transactional改成@GlobalTransactional

在这里插入图片描述
在这里插入图片描述
效果:seata-order出现异常,订单没创建,远程调用账户扣减数据能回滚

再测试有@EnableTransactionManagement,有@GlobalTransactional

效果:和上面一样。再一次证明spring boot默认开启事务

测试分布式事务——模拟情景

采购服务要远程调用 扣库存和下订单服务,订单服务又要远程调用扣减余额服务,怎么保证分布式事务的数据一致性?

操作:

  1. 先在各个模块启动类上加入@EnableTransactionManagement,全局事务(采购模块)除外。
  2. 各分支事务加@Transactional,全局事务加@GlobalTransactional
  3. 在订单模块模拟错误进行测试

在这里插入图片描述

情景:采购服务中事务上没有加@GlobalTransactional情况,远程调用扣减库存服务和创建订单服务(订单服务要远程调用扣减余额服务)。模拟订单服务出错,看扣减余额远程调用能否回滚

结果:库存扣减,余额扣减,订单没有生成;只有订单服务回滚,其他皆无效。

在这里插入图片描述
在这里插入图片描述
其他

如果导入了seata的依赖:spring-cloud-starter-alibaba-seata

所在模块的file.conf配置文件先自动配置,不行再将格式改成properties

在这里插入图片描述
在这里插入图片描述

7.2、事务模式

seata各模式原理

区分狭义事务和广义事务

  • 狭义事务:数据库的操作

  • 广义事务:业务的事务(包括数据库操作),举例:事务中需要发邮件和发短信,这种不能撤回的情况,AT和XA模式不管用了。

7.2.1、AT模式

图片里是AT模式的二阶段提交协议,其他模式也是二阶段提交(一阶段:本地事务 二阶段:提交/回滚 );

AT:系统默认使用,各分支事务要经过两阶段提交协议。

二阶提交协议原理

在这里插入图片描述

第一阶段:本地提交

  1. 生成前镜像:将要操作的数据记录下来
  2. 执行SQL操作数据(期间启动MySQL的行锁,先对要操作的数据select …for update(此时数据被锁住,普通的select可以读取,要看隔离级别。还有如果读操作的select…for update这种想加锁的读是不可以的),再执行后续sql,执行完则释放行锁)
  3. 生成后镜像:将操作后的数据记录下
  4. 前镜像和后镜像等待保存到uodo_log日志表中
  5. 向TC注册分支,在TC中申请一个全局锁,锁定操作的数据防止其他人操作,读同上,有锁才能操作。注意这里TC的全局锁不是MySQL的全局锁,MySQL的全局锁是锁整个数据库,而TC的全局锁相当于MySQL的行级别锁,只锁操作的数据。
  6. 本地事务提交;将业务数据和uodo_log日志表数据一起保存到当前事务的的表中
  7. 和TC汇报事务执行状态

第二阶段:

  • 若各分支事务都成功:删除uodo_log记录
    1. TC能感知到每个事务的状态,通知他们进行提交
    2. 给异步任务对列添加异步任务,异步+批量删除对应的uodo_log日志表的记录
  • 若某个事务失败,TC会通知所有分支事务回滚:拿到前镜像,数据恢复,删除uodo_log记录
    1. 先找到uodo_log记录(通过XID,BranchID)
    2. 数据校验,后镜像和当前数据是否一致,一致就ok执行回滚;不一致说明当前数据被其他操作给篡改了,需要配置相应的策略(怎么处理这个脏数据,忽略还是人工处理?之类)
    3. 若一致,则回滚数据到前镜像的内容,完成后删除uodo_log记录
  • 只要有分支事务没处理完,全局锁会一直存在。但是第一阶段执行事务是真正提交了的,不会在第二阶段一直阻塞数据库。

7.2.2、XA模式

第一阶段不会提交数据,阻塞事务请求,在第二阶段确认提交再提交或者回滚。全局锁+MySQL行锁在第一阶段就开启,事务一开始就用阻塞模式,性能差。AT和XA区别是AT第一阶段执行完SQL释放行锁,XA是到第二阶段才提交SQL导致行锁从开始到最后,阻塞时间长性能差。但二者都是一直持有seata的全局锁的。

7.2.3、TCC模式

主要是广义上的事务,需要写侵入式的代码。举例业务需要三个事务,一个事务改数据库,一个发短信,一个发邮件,这就用AT和XA行不通了,无法回滚,如果全局事务失败,只能进行补偿性操作,例如再发邮件和短信提醒对方扣款失败或者订单失败等。

7.2.4、saga模式

用于长事务,一时半会执行不完的事务。例如请假审批,其他模式都用了锁,如果长期锁在那是对系统是非常大的阻塞。saga是基于消息队列做的,后续有替代方案,所以这个几乎不用。

7.3 小节

debug查看seata执行流程

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

seata要点

  1. 使用。引入alibaba-seata依赖,只要在分布式事务的方法加@GlobalTransactional,在其他本地事务加@EnableTransactionManagement@Transactional即可。这样分布式事务中无论哪个环节异常,都会回滚。
  2. 原理,以及四种模式的特点
    在这里插入图片描述

参考链接:
https://blog.csdn.net/weixin_56884174/article/details/145573890
https://www.yuque.com/leifengyang/sutong/oz4gbyh5maa0rmxu#tHTwd


网站公告

今日签到

点亮在社区的每一天
去签到