正文
什么都不说,我们按照惯例,先上一个图(图里不规范的使用请忽略):
简单一眼就看出来, 比我们平时用的东西,多了 Seata Server 微服务 。
同样这个 Seata Server 微服务 ,也是需要注册到eureka上面去的。
那么我们首先就搞一搞这个 seata server ,那么剩下的就是一些原本的业务服务整合配置了。
该篇用的 seata server 版本,用的是1.4.1 , 可以去git下载下。
当然,我也是给你们备了的:
seata server 1.4.1
某度网盘分享地址:
https://pan.baidu.com/s/1R9McfkSkoj72Pf98ugCvBw
提取码:4ynp
第一步,下载下来解压 :
第二步,创个 seata server 用的数据库 :
CREATE TABLE `branch_table` ( `branch_id` bigint(20) NOT NULL, `xid` varchar(128) NOT NULL, `transaction_id` bigint(20) DEFAULT NULL, `resource_group_id` varchar(32) DEFAULT NULL, `resource_id` varchar(256) DEFAULT NULL, `branch_type` varchar(8) DEFAULT NULL, `status` tinyint(4) DEFAULT NULL, `client_id` varchar(64) DEFAULT NULL, `application_data` varchar(2000) DEFAULT NULL, `gmt_create` datetime DEFAULT NULL, `gmt_modified` datetime DEFAULT NULL, PRIMARY KEY (`branch_id`), KEY `idx_xid` (`xid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `global_table` ( `xid` varchar(128) NOT NULL, `transaction_id` bigint(20) DEFAULT NULL, `status` tinyint(4) NOT NULL, `application_id` varchar(32) DEFAULT NULL, `transaction_service_group` varchar(32) DEFAULT NULL, `transaction_name` varchar(128) DEFAULT NULL, `timeout` int(11) DEFAULT NULL, `begin_time` bigint(20) DEFAULT NULL, `application_data` varchar(2000) DEFAULT NULL, `gmt_create` datetime DEFAULT NULL, `gmt_modified` datetime DEFAULT NULL, PRIMARY KEY (`xid`), KEY `idx_gmt_modified_status` (`gmt_modified`,`status`), KEY `idx_transaction_id` (`transaction_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `lock_table` ( `row_key` varchar(128) NOT NULL, `xid` varchar(96) DEFAULT NULL, `transaction_id` bigint(20) DEFAULT NULL, `branch_id` bigint(20) NOT NULL, `resource_id` varchar(256) DEFAULT NULL, `table_name` varchar(32) DEFAULT NULL, `pk` varchar(36) DEFAULT NULL, `gmt_create` datetime DEFAULT NULL, `gmt_modified` datetime DEFAULT NULL, PRIMARY KEY (`row_key`), KEY `idx_branch_id` (`branch_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
创建完后:
第三步,修改下 \seata-1.4.1\seata-server-1.4.1\seata\conf 里的配置文件一些信息 :
1. registry.conf
以上两个文件配置完(记得保存), 我们先把我们的注册中心 eureka服务跑起来,然后点击启动 seata server:
可以看到启动成功(前提是eureka已经启动):
第三步 ,配置我们需要用到 分布式事务 seata组件的 微服务 :
我这里的示例实践,需要用到的有2个微服务 :
那么我们这两个微服务都需要做点什么呢?
1. 在对应的微服务的对应的不同数据库里(只要你想用上seata的), 都加上undo_log 这个表:
SQL语句:
SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for 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) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, `context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int(11) NOT NULL, `log_created` datetime(0) NULL, `log_modified` datetime(0) NULL, PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; SET FOREIGN_KEY_CHECKS = 1;
2. 关键步骤了 , 导入jar包 (需要用到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> <artifactId>seata-all</artifactId> <groupId>io.seata</groupId> <version>1.4.1</version> </dependency>
3.更加关键了,就是做配置
先总得了解下,需要用到seata的 微服务需要做些什么配置 ?
1. 在resources 下 新增2个配置文件 , file.conf 和 registry.conf
2.yml 新增配置seata 事务组参数
3.代码调整数据源代理,交给seata代理
1. registry.conf
以上是业务微服务里的registry.conf 需要改动的配置信息 ,给出一份该篇文章使用的:
registry { # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa type = "eureka" loadBalance = "RandomLoadBalance" loadBalanceVirtualNodes = 10 nacos { application = "seata-server" serverAddr = "127.0.0.1:8848" group = "SEATA_GROUP" namespace = "" cluster = "default" username = "" password = "" } eureka { serviceUrl = "http://localhost:8761/eureka/" application = "seata-server" weight = "1" } redis { serverAddr = "localhost:6379" db = 0 password = "" cluster = "default" timeout = 0 } zk { cluster = "default" serverAddr = "127.0.0.1:2181" sessionTimeout = 6000 connectTimeout = 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 type = "file" nacos { serverAddr = "127.0.0.1:8848" namespace = "" group = "SEATA_GROUP" username = "" password = "" } consul { serverAddr = "127.0.0.1:8500" } apollo { appId = "seata-server" apolloMeta = "http://192.168.1.204:8801" namespace = "application" apolloAccesskeySecret = "" } zk { serverAddr = "127.0.0.1:2181" sessionTimeout = 6000 connectTimeout = 2000 username = "" password = "" } etcd3 { serverAddr = "http://localhost:2379" } file { name = "file.conf" } }
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 { #这里注意,等号前后都是配置,前面是yml里配置的事务组,后面是register.conf里定义的seata-server vgroupMapping.test_tx_group = "seata-server" #only support when registry.type=file, please don't set multiple addresses seata_tc_server.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 } }
2. 需要在yml配置文件加上配置项,指明当前服务使用了 seata分布式事务组件,且需要加入的分布式事务组是哪个:
spring: cloud: alibaba: seata.tx-service-group: test_tx_group
3.然后是需要将数据源交给seata去代理:
去掉默认自动加载数据源
配置dao层扫描位置
@MapperScan("com.cloud.client1.dao") @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
然后是seata代理数据源(红色标注的地方需要注意下,都是实打实踩出来的坑。导入类的来源以及扫描的mapper的xml位置):
ok,到这里一个微服务 usercent的 整合 seata 分布式组件算是完成了。
同样,我们继续对 另外一个微服务 coredata 做seata 分布式组件 整合 。
也是第一步,file.conf 和 registry.conf (其实我们现在就是让这两个微服务在同一个分布式事务组,我们直接把刚才在usercent那边的两个文件直接粘贴过来就好)。
然后第二步就是修改yml配置:
第三步就是新增seata数据源代理、application上的注解排除自动加载数据源和扫描dao层地址,yml上面的配置项:
完事,两个业务微服务都整合了 分布式事务组件 seata ,而且都设置在同个分布式事务组里 (test_tx_group).
那么到这其实已经完事了, 接下来玩下 分布式事务回滚场景 的例子 (怎么去使用)。
PS: 使用方式关注 @GlobalTransactional 注解, 想把全局事务从哪里开启,就把这个注解放到哪个方法上去, 这个方法内 调用的其他服务只要都使用了seata,且在同个事务组,那么就会加入到当前的全局事务里。
我该篇就不弄太多服务了,就弄了2个分布式服务。
展示的例子内容 :
1. 上游服务 出错, 触发分布式事务, 上下游服务都会事务回滚;
2.下游服务 出错,触发分布式事务, 上下游服务都会事务回滚;(某种程度上讲,下游出错如果只有一个下游,其实不需分布式事务,通过错误传递单个回滚也是可以的。但是如果服务调用链很长,中游服务出错,需要整个链上的服务都事务回滚,那么就有必要都使用seata )
我们开始模拟:
第一个场景
上游服务 coredata 使用seata全局事务注解@GlobalTransactional 标记方法,先插入一个Account数据 ;
然后调用下游服务 usercent 插入一条数据;
然后 上游服务 coredata继续执行业务逻辑,继续插入数据;
接着模拟上游服务coredata开始报错(我们通过name长度故意触发错误);
期望结果: 上下游两个服务 在当前方法事务下插入的数据都回滚!
上游服务 coredata 方法:
通过fegin调用下游服务 usercent 方法:
下游服务 usercent 的插入方法:
开始模拟:
1.先把eureka跑起来:
暂时就注册中心自己,没有别的服务:
2.把seata server 服务跑起来,注册到eureka上去:
我是window环境,执行.bat
可以看到已经成功注册到eureka上了:
3. 把上游微服务 coredata 和 下游服务 usercent 都跑起来:
然后看下seata server上,也可以看到 两个服务 都成功‘注册’到了 seata server上了,而且都在同一个事务组 test_tx_group里面:
接下来,就是开始调用一下我们模拟的场景代码就完事了(不过我会加点图来给大家简单分析下):
一开始,都是没有数据的:
调用上游服务coredata接口触发一下整个流程:
调用开始:
我们打断点到 上游已经插入过一次数据,下游也插入过一次数据,
可以看到上游服务和下游服务的数据库里面的undo_log表出现了 事务记录,有关当前事务的 branch_id和 所在事务 xid,而且可以看到在两个服务内的undo_log表中记录的xid都是一致的,代表他们都在一个事务中:
然后我们继续往下执行,故意让上游报错:
这时候,接口调用完毕结束:
我们看到seata server里面的信息,可以看到全局事务 xid为22080结尾,回滚成功:
回滚成功后,undo_log表中的记录会删除掉:
当然我们两个服务里面也是没有数据的,因为回滚了:
这里可能有人会想,你查一下是空就能证明是回滚了么?
这时候我们也可以利用主键自增当前值可以看到确实发生了数据回滚的场景:
第一个场景就到此吧。
接下来我们模拟第二个场景:
上游服务 coredata 使用seata全局事务注解@GlobalTransactional 标记方法,先插入一个Account数据 ;
然后调用下游服务 usercent 插入一条数据;
然后下游服务 直接模拟出错, 这样触发事务回滚。
期望结果: 上下游两个服务 在当前方法事务下插入的数据都回滚!
也就是说我们需要对下游服务的插入方法里面做手脚,故意抛出错误:
快速调用一下:
可以看到下游出错了:
看下我们的seata server怎么说,已出发分布式事务,回滚成功:
数据库里面的数据也是回滚了,空的:
好了,该篇springboot cloud使用eureka整合 分布式事务组件 Seata 就到此吧。
ps: 最近比较忙,每篇文章其实都是用一些零碎时间拼凑出来的。不过我会坚持我的文章的初衷,能让大家跟着实践,能搞懂,能学会。
我...只是个散工。