前段时间写了几篇关于分布式事务的文章,包括理论和实战,实战是以阿里的Seata
来进行讲解,因为我们现在的系统中也大量使用分布式事务,只不过后端脚手架进行 二次封装,所以出问题得理解框架的原理和结构,才能更好地找到问题,最近我又加了一个模块进去,涉及好几个数据库的CRUD
,所以为了保证数据的一致性,所以就必须得 使用分布式事务(只不过公司框架太过于封装,不太喜欢),过程中遇到一些问题,还有总结一些大家可能会遇到的问题,于是总结出来,供大家参考,可能一些问题是比较 幼稚的,不应该犯的,不够也没关系嘛,谁都会犯一些低级的错误,学会总结,然后下次避免。
下面是我总结的三个方面,分别是从数据表undo_log
,服务是否引入Seata
,Feign
调用问题来出发,当然,还有很多其他问题,比如使用不同的模式,也会出现其他问题, 我们使用的模式是file
,如果使用db
,redis
,可能又会遇到其他问题。
只要参与分布式事务的数据库,在库中都需要有undo_log
表,undo_log
表就是用来记录那一张表参与了分布式事务以及执行前和执行后的快照
,以便对数据进行回滚,因为我们系统 使用的模式是file
,自然就不需要global_table
,branch_table
,lock_table
这三张表,所以就不用去看是否有这些表。
主事务是一个入口,然后从主事务处调用各个分支事务,主事务需要注册上seata-server
,分支事务也需要注册上seata-server
,如果没有加入seata
依赖, 那么事务是无法注册上seata-server
的,我在数据库中加了undo_log
表后,事务依然无法回滚,于是我就debug
一下,看undo_log
里面是否有事务数据, 果然,主事务的undo_log
中有事务数据,分支事务中undo_log
无事务数据,那么很显然,分支事务没有接入Seata
,于是在分支事务的微服务中引入seata
依赖。
你以为成功了吗,然而并没有呢!分支事务依然没有回滚,虽然undo_log
表中已经存入了数据,继续排查问题,看下面。
分支事务微服务中引入了Seata
依赖,在各个服务启动后,也都打印事务注册成功日志,然后执行后,分支事务异常依然没有回滚,于是看主事务接口打印的日志, 因为从主事务调用分支事务采用的是Feign
方式调用,而我司使用的框架对Feign
进行了全局异常处理,异常只在被调用的服务抛出,不会抛到调用服务,所以 分支事务抛出异常后,主事务没有感知到异常,因此不能对其进行回滚,因为我司框架对Feign
进行全局异常捕获,所以光使用@GlobalTransactional
注解 肯定不行。
于是就采用编码对事务进行回滚,图下,因为我们的Feign
返回一个统一消息体,如果不为200
,就标识失败,如果失败,那么就手动回滚事务,采用 GlobalTransactionContext.reload(RootContext.getXID()).rollback()
,这样就能回滚事务。
R response = messageClient.pushData(data);
if (response.getCode() != 200) {
try {
GlobalTransactionContext.reload(RootContext.getXID()).rollback();
} catch (TransactionException e) {
e.printStackTrace();
}
}
如果我们的Feign
没有对异常进行统一处理,那么异常就能抛到主事务所在的服务,那么就不用手动回滚事务,直接使用@GlobalTransactional
就行,但是还有一个问题, 就是我们不要使用Feign
的服务降级,如果使用服务降级,那么也需要对事务进行手动回滚,我们在使用Feign的时候通常会使用fallback
,如果服务发生异常,那么 就对调用fallback
所指定的类中的方法,因为fallback
已经对异常进行处理,所以就导致主事务感知不到异常,所以就无法对事务进行回滚。
所以,要使分支事务能进行回滚,要么进行手动回滚,要么抛出异常(得让主事务感受到异常)。
今天的分享就到这里,感谢你的观看,我是小四,我们下期见