本篇记录原写于去年。
背景
一个下单逻辑跨了3个服务,采用 Seata AT 模式做分布式事务。
发现问题
分布式事务的处理并未成功,具体表现为:在出现异常后,3 个数据库里的表谁也没回滚。
本来以为是自己看错了,但是经过笔者的多次验证后,得到的结果都是如此,分支事务并未被正常处理。
好的,发现问题后该怎么办呢?
尝试解决问题
“小问题,轻轻松松~”
刚开始看到这个问题,笔者并没有觉得是个大问题,此时我并没有意识到严重性,也可以说是“轻敌”了。
当时,主要觉得问题可能出现配置和整合步骤上,于是做了如下4个事情:
- 检查整合步骤,是不是漏掉了哪个步骤,或者少了哪些配置,亦或者是哪个配置项因为粗心没配置好。
- 数据库中的表是否有问题。
- 项目重启(遇事不决,重启试试)。
- 查看日志,确认与Seata是否连接通信,是否正常注册TM、RM等。
由于觉得是小问题,所以从下午4点左右到6点,一直在做上述的4个事情。
结果就是,没发现步骤问题,也没发现配置问题,与Seata Server也正常通信,但是“分支事务不回滚”的问题还在。这个时候我其实有点意识到这可能不是个小问题了,有点小慌。但是搞了一段时间没头绪,脑子也乱了索性就下班回家了。
“坏了!见鬼了!”
本来想着第二天上班再处理的,但是被这个问题搞得实在睡不着,晚上11点半打开电脑继续处理。处理的过程和下午一样,我还是觉得可能是哪里不小心漏掉了步骤或者配置不对,扩大了检查范围,除了检查代码中的配置、数据库,还检查了Nacos Server的配置、Seata Server的配置,结果发现配置没问题,也没有遗漏什么。
为什么我觉得一开始是个小问题,然后主要是在检查配置项之类的。其原因就是之前在上一版已经整合过、配置过,而且验证通过,源码也没问题,分布式事务的处理结果是正确的。因此,我肯定会觉得只要整合步骤没有遗漏、配置项正确,分布式事务肯定会被正常处理。同样的代码、同样的配置、同样的测试环境,一个正常,一个不正常,这有点出乎意料了。这个时候我意识到了问题的严重性了,我觉得可能是遇见鬼了。确切地说,当时有点麻了,一份代码中分布式事务正常处理,一份完全没反应,说不麻是假的。
凌晨了,换个思路吧。
冷静下来后,我也不骗自己了,这代码肯定是有问题的,不然分支事务怎么会不回滚。但是我确实不知道问题在哪,怎么同样的东西在这一版项目里就不能用了呢?
不过,再去查配置、对比代码已经没意义了。既然确认代码有问题(不嘴硬了),那就开始根据Seata运行流程查一下哪里出了问题吧,主要是根据微服务实例的运行日志和Seata Server的运行日志来查的。
- 与Seata Server通信正常。
- 三个微服务实例都正常注册TM、RM等。
- 全局事务正常开启。
- 两个分支事务开启的日志一行都没出现。
不管是微服务实例的运行日志和Seata Server的运行日志,都没有看到两个分支事务的开启和处理,是的,没有任何信息和踪迹。再去数据库中确认了一下,undo_log表中也并没有数据。虽然不知道哪里出了问题,但是至少有方向了。
全局事务能够正常开启和回滚,而两个分支事务不正常(与Seata Server正常通信,但是都没有生效)。到这里已经大致有了眉目,乘客微服务和订单微服务两个服务实例的运行日志和Seata Server的运行日志,都没有看到任何关于全局事务的信息,这也说明了两个分支事务可能根本就没有注册成功。全局事务正常开启和处理,而两个本应出现的分支事务没有出现,它们之间“失联”了。
从代码层面来说,全局事务和分支事务的联系主要在一个变量上,这个变量就是全局事务的ID——xid。现在它们“失联”了,只能通过这个变量的产生、传递、接收、处理等几个步骤来确认问题在哪里了。
此时的要检查的内容就确定了下来:
- 全局事务是否正常开启?xid是否正确地生成了?
- xid是否正确地传递给下游的调用实例中?
- 下游的调用实例是否正确地接到了xid?
- 接到xid后是否正确处理并且开启分支事务?
“问题不清晰,看源码分析”。
为了确认上述的几个检查内容,还是要用debug模式看一看Seata处理分布式事务过程中所涉及到的源码,由于牵涉的源码太多,这里笔者挑几个重要节点介绍一下。
对于“全局事务是否正常开启?xid是否正确地生成了?”,主要跟进了下方两个类的源码:
io.seata.spring.annotation.GlobalTransactionalInterceptor.java
io.seata.tm.api.TransactionalTemplate.java
这两个类主要涉及全局事务的开启和处理,感兴趣的读者可以仔细地去探索一下。当然,结果是这个步骤并没有问题,全局事务正常开启,xid正确生成。
难道是xid生成了却没有传递给下游?对于这个问题,笔者主要在debug模式下跟进了com.alibaba.cloud.seata.feign.SeataFeignClient.java这个类的源码:
@Override
public Response execute(Request request, Request.Options options) throws IOException {
Request modifiedRequest = getModifyRequest(req