没看过源码,却能找到Seata源码中的BUG

前言

Bug和明天,永远不知道哪一个先来。跑得好好的代码突然开始报错,本以为是个普通的bug,结果却万万妹想到。

原文链接:https://blog.csdn.net/Baisitao_/article/details/125216475
原文作者:Sicimike

关于Seata

Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。
https://seata.io/zh-cn/

问题

报错日志如下,看起来很简单

Failed to delete expired undo_log, error:table ‘**’ doesn’t exist

无非就是undo_log这个表不存在。但是问题就在于,我们用的是Seata框架的TCC模式,不会产生undo_log,根本就不需要undo_log表。博主眉头一皱,发现事情并不简单。
问题

结论

先说结论,这个错误是由定时任务删除过期的undo_log打印出来的,不会影响业务,也不会导致数据不一致。忽略即可,如果实在不想看到这个错误日志,在业务库(undo_log表需要和TM、RM操作的表在同一个库)新建一张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,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

表结构来自:https://seata.io/zh-cn/docs/dev/mode/at-mode.html

版本

框架调研时,当时的最新版本是1.4.0,所以项目中就采用了该版本

排查

Seata还处于高速迭代中(至博主撰写此文时,已经发布了1.5.1版本),并且当时使用的是最新版本,这种问题的解决方案用搜索引擎应该是搜不到的,所以自己看源码吧。

下载源码

https://github.com/seata/seata/tree/1.4.0

找到错误位置

根据日志可以知道,错误位置在io.seata.rm.RMHandlerAT#handle 59行
错误位置
可以看到RMHandlerAT继承自AbstractRMHandler,看下AbstractRMHandler的实现类,有如下几个
继承
因为博主用的TCC模式,所以理论上应该调用io.seata.rm.tcc.RMHandlerTCC#handle,但是实际上调用了io.seata.rm.RMHandlerAT#handle(根据错误日志可以知道)。所以需要找到Seata是如何选取这个Handler的,实际上就是在AbstractRMHandler的子类DefaultRMHandler
选择handler
可以看到选取Handler的逻辑在public void handle(UndoLogDeleteRequest request)方法中,根据request中的branchType的值来选取,换句话说,就是request中的branchType值不正确。沿着调用链一直往上,看看branchType是如何赋值的,以下是该方法的逐级调用

  • io.seata.core.protocol.transaction.UndoLogDeleteRequest#handle
  • io.seata.rm.AbstractRMHandler#onRequest
  • io.seata.core.rpc.processor.client.RmUndoLogProcessor#handleUndoLogDelete
  • io.seata.core.rpc.processor.client.RmUndoLogProcessor#process
  • io.seata.core.rpc.netty.AbstractNettyRemoting#processMessage

可以看到再往上就是netty通信了,整个调用链都没有给branchType赋值过,也就是说requestseata server端直接传递过来的,所以想知道branchType为什么不正确,还得去找server端的代码

如果不熟悉Seata的代码,其实不太好找,所以博主决定换一种方式。因为request是一个UndoLogDeleteRequest对象,所以看看server端在哪里构造了它,博主在idea里搜索new UndoLogDeleteRequest()
搜索结果
可以看到总共有5处,但是有2处是单测,不用看,所以直接看剩下的3个。最终确定是
io.seata.server.coordinator.DefaultCoordinator#undoLogDelete
undoLogDelete
可以看到构造之后,并没有设置branchType,因为branchType值默认就是BranchType.AT
AT
因为Seata没有提供扩展,让开发者自己设置,所以应该算是bug

博主把这个问题反馈了给了Seata的开发者,得到如下回复
官方回复

修复

最近博主又去翻了下Seata最新代码,看到了如下内容
io.seata.rm.RMHandlerAT#handle
undo log
官方修复的方式是加了判断,把error日志改成了debug日志。感兴趣的朋友可以看看这个PR optimize: client check whether undolog table exist before cleaning undolog #4216

后记

博主之前也没有看到Seata的源码,所以只能根据自己的经验一步步推断,文中若有不足或者错误之处,还请不吝赐教。

非常抱歉,我的回答有误。在 Seata 1.4.2 版本,确实有 `AdminController` 类,下面是 Seata 1.4.2 版本控制台查看事务信息调用的主要接口及其所在位置: 1. 获取全局事务列表:通过调用 Seata Server 的 `/registry/listGlobalTransactions` 接口来获取所有全局事务的列表,该接口位于 `io.seata.server.coordinator.registry.RegistryController` 类。 2. 获取全局事务详情:通过调用 Seata Server 的 `/registry/getGlobalTransaction/{xid}` 接口来获取指定全局事务的详情,该接口位于 `io.seata.server.coordinator.registry.RegistryController` 类。 3. 获取分支事务列表:通过调用 Seata Server 的 `/registry/listBranchTransactions/{xid}` 接口来获取指定全局事务的所有分支事务的列表,该接口位于 `io.seata.server.coordinator.registry.RegistryController` 类。 4. 获取分支事务详情:通过调用 Seata Server 的 `/registry/getBranchTransaction/{xid}/{branchId}` 接口来获取指定分支事务的详情,该接口位于 `io.seata.server.coordinator.registry.RegistryController` 类。 以上接口均可以通过调用 Seata Server 提供的 HTTP 或 gRPC 接口来实现。在 Seata 1.4.2 ,HTTP 接口实现位于 `io.seata.server.rpc.netty.HttpServer` 类,gRPC 接口实现位于 `io.seata.server.rpc.grpc.GrpcServer` 类。具体来说,在 `HttpServer` 类Seata 通过创建 Netty 服务器来启动 HTTP 接口;而在 `GrpcServer` 类Seata 通过创建 gRPC 服务器来启动 gRPC 接口。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值