前言
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
的逻辑在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
赋值过,也就是说request
是seata server
端直接传递过来的,所以想知道branchType
为什么不正确,还得去找server
端的代码
如果不熟悉Seata的代码,其实不太好找,所以博主决定换一种方式。因为request
是一个UndoLogDeleteRequest
对象,所以看看server
端在哪里构造了它,博主在idea里搜索new UndoLogDeleteRequest()
可以看到总共有5处,但是有2处是单测,不用看,所以直接看剩下的3个。最终确定是
io.seata.server.coordinator.DefaultCoordinator#undoLogDelete
可以看到构造之后,并没有设置branchType
,因为branchType
值默认就是BranchType.AT
因为Seata
没有提供扩展,让开发者自己设置,所以应该算是bug
博主把这个问题反馈了给了Seata
的开发者,得到如下回复
修复
最近博主又去翻了下Seata
最新代码,看到了如下内容
io.seata.rm.RMHandlerAT#handle
官方修复的方式是加了判断,把error日志改成了debug日志。感兴趣的朋友可以看看这个PR optimize: client check whether undolog table exist before cleaning undolog #4216
后记
博主之前也没有看到Seata
的源码,所以只能根据自己的经验一步步推断,文中若有不足或者错误之处,还请不吝赐教。