微服务学习-Seata 解决分布式事务

news/2025/1/22 15:17:02/

1. 为什么要使用分布式事务?

1.1. 问题重现

使用微服务架构,当账户余额为 0 时,还可以继续下单,而且扣减库存;或者当库存不足时,也可以下单继续扣减余额等问题,造成数据不一致。

1.2. 新的需求

下单逻辑需要保证数据一致性,当账户余额不够或者库存不足,该回滚库存回滚库存,该回滚账户回滚账户,让当前下单失败。

1.3. 解决方案

思考:使用 Spring 事务能解决问题吗? 不能;

使用分布式事务解决方案 Seata(官方推荐)

2. Seata 是什么?

官方文档:Seata 是什么? | Apache Seata

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

首选 Seata AT 模式(官方推荐),可以做到业务无侵入。

Seata AT 模式 | Apache Seata

3. Seata AT 模式的工作流程

3.1. 非常重要的三个概念

  • TC(Transaction Coordinator):事务协调器,负责全局事务的管理,包括事务的开启、提交、回滚等操作,它会记录全局事务和分支事务的状态信息。
  • TM(Transaction Manager):事务管理器,主要用于开启、提交或回滚全局事务。在应用代码中,开发人员通过 TM 来定义全局事务的边界。
  • RM(Resource Manager):资源管理器,负责分支事务的管理,与 TC 交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

例如:当用户通过订单服务下单,调用库存服务扣减库存,调用账户服务扣减账户余额。

订单服务要接入 TM 组件,下单操作需要开启全局事务(向 TC 申请一个全局事务 XID),进入下单逻辑,如果下单正常,需要通知 TC 提交全局事务;如果下单出现异常,比如余额不够,需要通知 TC 回滚全局事务。

订单服务、库存服务、账户服务都需要接入 RM 组件,提交本地事务的同时,需要向 TC 注册分支事务信息,接受 TC 的通知,提交或回滚分支事务。

TC 是独立的服务,维护 TM 申请的全局事务信息和 RM 提交的分支事务信息,TM 通知 TC 全局事务提交或回滚的时候, TC 要通知 RM 分支事务提交或回滚。

3.2. AT 模式工作流程

问题:如何知道调用的是同一个分布式事务?

开启全局事务会分配一个全局事务 XID,微服务链路会传递 XID,注册每个分支事务都会带上 XID。

4. Seata Server(TC)安装部署

4.1. 注意版本

Seata Version:2.0.0

4.2. 下载地址

Seata Java Download | Apache Seata

4.3. 官网参考资料

4.3.1. Seata 新手部署指南

新人文档 | Apache Seata

4.3.2. Seata 官网参数配置

参数配置 | Apache Seata

4.4. TC 端存储模式

全局事务、分支事务信息存储到哪里了?

4.4.1. Seata 1.x 支持的模式
4.4.1.1. file:单机模式

全局事务、分支事务信息内存中读写并持久化本地文件 root.data,性能较高,但是只支持单机模式部署,生产环境不考虑。

4.4.1.2. db:高可用模式

全局事务、分支事务信息通过 db 共享,相应性能差点。

4.4.1.3. redis:1.3及以上版本支持,性能较高

存在事务信息丢失风险,请提前配置适合当前场景的 redis 持久化配置。

4.4.2. Seata2.x 新增的 Raft 模式

利用 Raft 算法实现多个 TC 之间数据的同步。Raft 模式是最理想的方案,但当前并不成熟,所以不考虑。

4.4.3. 从稳定性角度考虑,最终选择采用 db 模式

创建 Seata 数据库,sql 脚本在 seata 目录中

例如:seata-server-2.0.0\seata\script\server\db\mysql.sql

4.5. 思考: RM 和 TM 如何找到 TC 服务的?

可以将 TC 注册到 Nacos 注册中心,TM 和 RM 通过 Nacos 注册中心实现 TC 服务的发现。

注意:Seata 的注册中心是作用于 Seata 自身的,和微服务中的配置的注册中心无关,但可以共用注册中心。可以创建一个 Seata 的命名空间,区分 Seata 的 TC 服务和业务微服务

4.6. 思考: TC 的配置是不是也可以交给 Nacos 配置中心配置?

可以。

4.7. 最终方案:db 存储模式 + Nacos(注册&配置中)方式部署

4.7.1. 前置环境准备
4.7.1.1. db 模式准备好 seata 的数据库

例如:seata-server-2.0.0\seata\script\server\db\mysql.sql

4.7.1.2. 准备好 Nacos 环境

Nacos 控制台中创建一个 seata 的命名空间

4.7.2. Seata 配置融合 Nacos 注册中心

配置将 Seata Server 注册到 Nacos 中。主要配置 seata 的配置文件(路径:seata-server-2.0.0\seata\conf\application.yml)

seata:registry:# support: nacos, eureka, redis, zk, consul, etcd3, sofatype: nacosnacos:application: seata-serverserver-addr: icoolkj-mall-nacos-server:8848namespace: seatagroup: SEATA_GROUPcluster: default

注意:

  • 这个 cluster 配置,默认 TC 是 default 集群,TM 和 RM 都要通过这个集群名找 TC 集群。
  • 请确保client(RM 和 TM)与 server(TC)的注册出于同一个 namespace 和 group,不然会找不到 server(TC)服务。

4.8. Seata 配置融合 Nacos 配置中心

4.8.1.1. 配置 Seata 使用 Nacos 配置中心

主要配置 seata 的配置文件(路径:seata-server-2.0.0\seata\conf\application.yml)

seata:config:# support: nacos, consul, apollo, zk, etcd3type: nacosnacos:server-addr: icoolkj-mall-nacos-server:8848namespace: seatagroup: SEATA_GROUPdata-id: seataServer.properties
4.8.1.2. 将 seata server 的配置上传至 Nacos 配置中心
    1. 获取 seata server 配置信息(文件路径:seata\script\config-center\config.txt)
    2. 修改为 db 存储模式,并修改 mysql 连接配置
#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=#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=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://icoolkj-mall-mysql:33060/seata2.0.0?useSSL=false&characterEncoding=utf8&useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=icoolDP1988
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
    1. 配置事务分组,TC 要与 client(TM 和 RM)配置的事务分组一致
  • 事务分组:seata 的资源逻辑,可以按微服务的需要,在应用程序(客户端)对自行定义事务分组,每组取一个名字。
  • 集群:seata-server 服务端一个或多个节点组成的集群 cluster。 应用程序(客户端)使用时需要指定事务逻辑分组与 Seata 服务端集群的映射关系。
service.vgroupMapping.default_tx_group=default

注意:事务分组如何找到后端的 Seata 集群?

事务分组介绍 | Apache Seata

    1. 在 Nacos 控制台 seata 命名空间下新建 dataId 为 seataServer.properties 配置,配置内容为修改后的 config.txt 信息。
#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.default_tx_group=default
#If you use a registry, you can ignore it
#service.default.grouplist=127.0.0.1:8091
#service.enableDegrade=false
#service.disableGlobalTransaction=falseclient.metadataMaxAgeMs=30000
#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
# You can choose from the following options: fastjson, jackson, gson
tcc.contextJsonParserType=fastjson#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=#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=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://icoolkj-mall-mysql:3306/seata2.0.0?useUnicode=true&rewriteBatchedStatements=true&useSSL=false&characterEncoding=utf8&allowPublicKeyRetrieval=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.distributedLockTable=distributed_lock
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000#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.session.branchAsyncQueueSize=5000
server.session.enableBranchAsyncRemove=false
server.enableParallelRequestHandle=true
server.enableParallelHandleBranch=false#Metrics configuration, only for the server
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898

注意:在 seata 命名空间下建立的 seataServer.properties 配置,要和 seata 的配置文件(seata-server-2.0.0\seata\conf\application.yml)的 config 配置要对应上,特别注意 seataServer.properties 是否是 SEATA_GROUP。

4.9. 启动 Seata Server

  • windows 点击 bin 目录下 seata-server.bat 直接启动。
  • 启动成功,查看控制台 http://localhost:7091,账号密码:seata。
  • 在 Naocs 控制台,查看 seata-server 注册情况。

5. 微服务整合 Seata AT 模式实战

5.1. 业务场景

用户下单,订单服务调用库存服务扣减库存,调用账户服务扣减账户余额。

事务发起者:订单服务。

事务参与者:库存服务,账户服务,商品服务。

5.2. 订单服务(事务发起者)整合 Seata

5.2.1. 订单服务 pom.xml 引入 Seata 的依赖
<!-- seata 依赖-->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
5.2.2. 订单服务对应数据库中添加 undo_log 表(仅支持 AT 模式)
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';
ALTER TABLE `undo_log` ADD INDEX `ix_log_created` (`log_created`);
5.2.3. 订单服务 application.yml 中添加 seata 配置
seata:# seata 服务分组,要与服务端配置service.vgroup_mapping的后缀对应tx-service-group: default_tx_groupregistry:# 指定nacos作为注册中心type: nacosnacos:application: seata-serverserver-addr: icoolkj-mall-nacos-server:8848namespace: seatagroup: SEATA_GROUPconfig:# 指定nacos作为配置中心type: nacosnacos:server-addr: icoolkj-mall-nacos-server:8848namespace: seatagroup: SEATA_GROUPdata-id: seataServer.properties

优化:可以将 seata 配置移到 Nacos 配置中心。

    1. Nacos 控制台创建一个 dataId 为 seata-client.yml 的配置,配置内容如下:
seata:# seata 服务分组,要与服务端配置service.vgroup_mapping的后缀对应tx-service-group: default_tx_groupregistry:# 指定nacos作为注册中心type: nacosnacos:application: seata-serverserver-addr: icoolkj-mall-nacos-server:8848namespace: seatagroup: SEATA_GROUPconfig:# 指定nacos作为配置中心type: nacosnacos:server-addr: icoolkj-mall-nacos-server:8848namespace: seatagroup: SEATA_GROUPdata-id: seataServer.properties

    1. 订单微服务的 application.yml 引入 seata-client.yml
spring:config:import:- optional:nacos:${spring.application.name}.yml- optional:nacos:db-mysql-common.yml # mysql数据库公共配置- nacos:nacos-discovery.yml- optional:nacos:seata-client.yml  # Seata Client 配置
5.2.4. 订单服务作为全局事务发起者,在下单的方法上面添加 @GlobalTransactional 注解
@GlobalTransactional(name = "createOrder", rollbackFor = Exception.class)
//@Transactional
public Result<?> createOrder(Long userId, Long productId, Integer orderQuantity) {

5.3. 库存服务(事务参与者)整合 Seata

5.3.1. 和整合订单服务前三步一样
5.3.2. 库存服务只需要再扣减库存方法上添加 Spring 事务 @Transaction 注解
@Transactional
public void reduceInventory(Long productId, Integer inventoryQuantity) {

5.4. 账户服务(事务参与者)整合 Seata

5.4.1. 和整合订单服务前三步一样
5.4.2. 账户服务只需要再扣减余额方法上添加 Spring 事务 @Transaction 注解
@Transactional
public void reduceBalance(Long userId, BigDecimal orderCost) {

5.5. 商品服务(事务参与者)整合 Seata

5.5.1. 和整合订单服务前三步一样
5.5.2. 订单调用商品服务的查询商品价格,无需增加事务

5.6. Seata2.x 常见问题

5.6.1. 微服务启动报错

io.seata.conf.exception.ConfigNotFoundException:service.vgroupMapping.default_tx_group configuration itme is required

产生原因:无法拉取到 service.vgroupMapping.default_tx_proup=default 这个配置,也就是找不到集群名称为 default 的 seata server 服务。

解决方式:

思路1:检查下微服务 seata 配置是否未配置事务分组 seata.tx-service-group: default_tx_group

思路2:检查下 namespace 和 group 配置 Server 端和 Client 端是否对应,特别注意 seataSever.properties 是否是 SEATA_GROUP

6. 重启所有服务,测试分布式事务是否生效

6.1. 分布式事务成功场景:模拟正常下单,扣库存,扣余额

6.2. 分布式事务失败场景:模拟下单扣库存成功,扣余额失败,事务是否回滚

6.3. 目前 seata2.0.0 版本的中存在 bug

事务发生回滚,回滚成功了,但是最外层代码无法捕捉到原始的 BusinessException 异常,只能捕捉到 RuntimeException 运行时异常。

建议不要在生产上使用该版本。

7. 小结

通过 seata 可以解决微服务分布式事务的问题。


http://www.ppmy.cn/news/1565243.html

相关文章

阿里云服务器在Ubuntu上安装redis并使用

1、redis安装 sudo apt install lsb-release curl gpgcurl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpgecho "deb [signed-by/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.…

自动驾驶---方案从有图迈进无图

1 背景 近两年&#xff0c;自动驾驶量产领域&#xff0c;有一句话出现的频率很高&#xff1a;“无图也能开”&#xff0c;到底什么是有图&#xff0c;什么是无图呢&#xff1f;简单来说就是有高精地图&#xff08;High Definition Map&#xff09;和没有高精地图&#xff08;但…

使用tritonserver完成clip-vit-large-patch14图像特征提取模型的工程化。

1、关于clip-vit-large-patch14模型 关于openapi开源的clip-vit-large-patch14模型的特征提取&#xff0c;可以参考之前的文章&#xff1a;Elasticsearch向量检索需要的数据集以及768维向量生成这篇文章详细介绍了模型的下载地址、使用方式、测试脚本&#xff0c;可以让你一步…

小白爬虫——selenium入门超详细教程

目录 一、selenium简介 二、环境安装 2.1、安装Selenium 2.2、浏览器驱动安装 三、基本操作 3.1、对页面进行操作 3.1.1、初始化webdriver 3.1.2、打开网页 3.1.3、页面操作 3.1.4、页面数据提取 3.1.5、关闭页面 ?3.1.6、综合小案例 3.2、对页面元素进行操作 3…

【深度学习】Huber Loss详解

文章目录 1. Huber Loss 原理详解2. Pytorch 代码详解3.与 MSELoss、MAELoss 区别及各自优缺点3.1 MSELoss 均方误差损失3.2 MAELoss 平均绝对误差损失3.3 Huber Loss 4. 总结4.1 优化平滑4.2 梯度较好4.3 为什么说 MSE 是平滑的 1. Huber Loss 原理详解 Huber Loss 是一种结合…

开源模型应用落地-FastAPI-助力模型交互-进阶篇-中间件(四)

一、前言 FastAPI 的高级用法可以为开发人员带来许多好处。它能帮助实现更复杂的路由逻辑和参数处理&#xff0c;使应用程序能够处理各种不同的请求场景&#xff0c;提高应用程序的灵活性和可扩展性。 在数据验证和转换方面&#xff0c;高级用法提供了更精细和准确的控制&…

【PHP】部署和发布PHP网站到IIS服务器

欢迎来到《小5讲堂》 这是《PHP》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解。 温馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xff01; 目录 前言安装PHP 稳定版本线程安全版解压使用 PHP配置 配置文件扩展文件…

傅里叶变换在语音识别中的关键作用

在语音识别中&#xff0c;傅里叶变换起着至关重要的作用&#xff0c;主要体现在以下几个方面&#xff1a; 一、时域到频域的转换 语音信号的特点 语音信号是一种时域信号&#xff0c;它随时间变化。例如&#xff0c;当我们说话时&#xff0c;声带的振动产生声波&#xff0c;这…