Seata实现Sprincloud(Eureka+Feign)事务
目录
说明
谨以此记录学习 Seata 时踩过的坑
项目是一个微服务的架构,需要使用到“分布式事务”,在折腾了tx-lcn、tcc-transaction等几个玩意儿后,最终把目光定格在seata上,决定用seata给基于Eureka+Feign的项目添加分布式事务管理。
资料如下:
步骤
-
下载、配置、运行 Seata Server
Seata 需要使用 Server 端的配合来实现分布式事务,因此需要先部署 Seata 的 Server 环境。 -
配置共同环境
服务的调用方以及服务提供方,都需要进行一些共同的、一致的配置。 -
配置各自的环境
其实就是给服务调用方加上全局事务(分布式事务)的注解"@GlobalTransactional",服务提供方不需要额外配置。
实战
下载、配置并运行SeataServer
下载
下载地址:
http://seata.io/zh-cn/blog/download.html
Seata 1.2.0 (2020-04-20) 目录结构如下:
bin用于运行Seata Server; lib是server用到的资源库,无需理会; conf是配置文件目录。
bat为win下脚本文件; sh为unix-like系统下的脚本。
关注file.conf与register.conf
配置
根据自己需求修改配置文件—— "file.conf" 和 "registry.conf"。文章开头已贴出服务端以及应用端(客户端)配置项的说明的地址。
file.conf
此次需要用到数据库来保存 "undo_log" 等等的数据,需要在"file.conf"修改保存模式"mode"为"db",修改"db"中数据库相关的配置:
store { ## store mode: file、db mode = "db" ## database store property db { ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc. datasource = "druid" ## mysql/oracle/postgresql/h2/oceanbase etc. dbType = "mysql" driverClassName = "com.mysql.jdbc.Driver" url = "jdbc:mysql://localhost:3306/seata_server" user = "root" password = "root" minConn = 5 maxConn = 30 globalTable = "global_table" branchTable = "branch_table" lockTable = "lock_table" queryLimit = 100 maxWait = 5000 } }
mode = "db" 表明事务信息用db存储,当mode="db"时,只有db节点下的配置生效,不用管file节点
registry.conf
同样根据自己需求修改。
seata server会把自己注册到注册中心,像其它的微服务模块一样。registry.conf就是配置seata server使用何种注册中心以及如何注册。
registry节点为服务注册中心的类型及配置,此处选择type=eureka并修改对应的eureka的配置。
registry { # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa type = "eureka" eureka { serviceUrl = "http://localhost:8761/eureka" application = "default" weight = "1" } } #配置中心的配置,根据自己需求修改,eureka不支持充当配置中心,保留默认值 config { # file、nacos 、apollo、zk、consul、etcd3 ... ... }
运行
根据操作系统类型选择脚本运行。
基本环境的配置
由于此处使用数据库存储事务相关的数据,所以应先建库、建表。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;
调用方和服务方的共同配置
数据库
以上是基本的数据表。另外,每个被调用的服务以及服务的调用方都需要用到 "undo_log"表用于事务的回滚,sql如下:
-- 注意此处0.3.0+ 增加唯一索引 ux_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;
maven依赖:
<!--seata--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-seata</artifactId> <version>2.1.0.RELEASE</version> <exclusions> <exclusion> <artifactId>seata-all</artifactId> <groupId>io.seata</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>io.seata</groupId> <artifactId>seata-all</artifactId> <version>1.2.0</version> </dependency>
其它的配置文件
需要在resource目录下创建 "file.conf" 和 "registry.conf" 文件。这两个文件与之前下载的seata目录里面的配置文件不是一样的,两者内容如下:
file.conf
transport { # tcp udt 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,will not be used for UDT 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.my_test_tx_group = "default" #only support when registry.type=file, please don't set multiple addresses #关于grouplist,只有当registry.type=file,注册中心是file方式时,才会起作用。 https://blog.csdn.net/weixin_39800144/article/details/100726116 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 } tm { commitRetryCount = 5 rollbackRetryCount = 5 } undo { dataValidation = true logSerialization = "jackson" logTable = "undo_log" } log { exceptionRate = 100 } }
registry.conf:
registry { # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa type = "eureka" nacos { serverAddr = "localhost" namespace = "" cluster = "default" } 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" session.timeout = 6000 connect.timeout = 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、springCloudConfig type = "file" nacos { serverAddr = "localhost" namespace = "" group = "SEATA_GROUP" } consul { serverAddr = "127.0.0.1:8500" } apollo { app.id = "seata-server" apollo.meta = "http://192.168.1.204:8801" namespace = "application" } zk { serverAddr = "127.0.0.1:2181" session.timeout = 6000 connect.timeout = 2000 username = "" password = "" } etcd3 { serverAddr = "http://localhost:2379" } file { name = "file.conf" } }
可根据自己需求修改,一般情况下默认值即可。
参考:
application.yml的配置
application.yml:
spring: cloud: alibaba: seata: tx-service-group: my_test_tx_group datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/xx username: root password: root
启动类的配置
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
seata 需要使用代理的 DataSource, 因此不使用 DataSourceAutoConfiguration
事务的实现
基本的配置已经实现,被调用的服务的方法(即服务提供方的方法)不需要做额外的配置,只需要在调用方(即需要通过feign调用其它服务)的方法上添加 "@GlobalTransactional" 注解,如:
@GlobalTransactional(name = "fsp-create-order", rollbackFor = Exception.class) public void createOrder(Param param){ //通过feign调用其它服务 accountFeign.checkAccount(); productFeign.checkNum(); }
拓展
在client端的配置文件"file.conf"中,可以看到如上的service节点,红框部分是事务组的名称,官方说该名称应该和Server中的配置保持一致,然而在1.2.0版本的seata server端的配置文件中找不到该配置项...所以此处保留了sample的名称,此时可以运行成功。seata的文档真是一言难尽...
项目是使用eureka作为注册中心,而在文章开头也提到对seata配置文件registry.conf的修改,把type改为"eureka",即把seata server注册到eureka中,此时,"vgroupMapping.my_test_tx_group"的值"default",就是seata server在eureka中的Application name:
参考:
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· dotnet 源代码生成器分析器入门
· ASP.NET Core 模型验证消息的本地化新姿势
· 对象命名为何需要避免'-er'和'-or'后缀
· SQL Server如何跟踪自动统计信息更新?
· AI与.NET技术实操系列:使用Catalyst进行自然语言处理
· dotnet 源代码生成器分析器入门
· 官方的 MCP C# SDK:csharp-sdk
· 一款 .NET 开源、功能强大的远程连接管理工具,支持 RDP、VNC、SSH 等多种主流协议!
· 一步一步教你部署ktransformers,大内存单显卡用上Deepseek-R1
· 一次Java后端服务间歇性响应慢的问题排查记录