当前位置: 首页 > article >正文

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

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, sofa
    type: nacos
    nacos:
      application: seata-server
      server-addr: icoolkj-mall-nacos-server:8848
      namespace: seata
      group: SEATA_GROUP
      cluster: 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, etcd3
    type: nacos
    nacos:
      server-addr: icoolkj-mall-nacos-server:8848
      namespace: seata
      group: SEATA_GROUP
      data-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=false

client.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_group
  registry:
    # 指定nacos作为注册中心
    type: nacos
    nacos:
      application: seata-server
      server-addr: icoolkj-mall-nacos-server:8848
      namespace: seata
      group: SEATA_GROUP

  config:
    # 指定nacos作为配置中心
    type: nacos
    nacos:
      server-addr: icoolkj-mall-nacos-server:8848
      namespace: seata
      group: SEATA_GROUP
      data-id: seataServer.properties

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

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

  config:
    # 指定nacos作为配置中心
    type: nacos
    nacos:
      server-addr: icoolkj-mall-nacos-server:8848
      namespace: seata
      group: SEATA_GROUP
      data-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.kler.cn/a/512208.html

相关文章:

  • 【Java面试】RabbitMQ
  • Scala语言的循环实现
  • 队列的一些注意
  • 力扣动态规划-5【算法学习day.99】
  • R 语言科研绘图第 20 期 --- 箱线图-配对
  • 【Golang/nacos】nacos配置的增删查改,以及服务注册的golang实例及分析
  • aosp系统源码aidl文件如何查看对应生成的java文件-安卓系统开发实战小技巧分享
  • pcm | Parity Check Matrix(奇偶校验矩阵)
  • Linux 网络 序列化与反序列化~
  • 电脑如何访问手机文件?
  • 以太坊深入解析:概念与原理全面剖析
  • ToDesk云电脑、顺网云、网易云、易腾云、极云普惠云横测对比:探寻电竞最佳拍档
  • 将IDLE里面python环境pyqt5配置的vscode
  • 黑马点评之导入初始项目(java)
  • [八省联考 2018] 制胡窜
  • 畅游Diffusion数字人(14):基于3D人体网格的语音驱动手势视频生成 ECCV 2024
  • 如何使用C++来实现OPENAI协议通过OLLAMA来与AI大模型通信
  • 搭建一个基于Spring Boot的外贸平台
  • browser-use 的简单使用
  • [Datawheel学习]用Llama-index创建Agent、数据库对话Agent和RAG接入Agent
  • Python采集modBus协议数据
  • Linux网络IOv1.1介绍-纯PDF版
  • MySQL 中单独获取已知日期的年月日
  • 直驱式风电储能制氢仿真模型matlab/simulink
  • Type-C充电与智能家居的结合
  • 【王树森搜索引擎技术】概要01:搜索引擎的基本概念