• 没看过源码,却能找到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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    表结构来自: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
    
    • 1

    找到错误位置

    根据日志可以知道,错误位置在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的源码,所以只能根据自己的经验一步步推断,文中若有不足或者错误之处,还请不吝赐教。

  • 相关阅读:
    PWR电源控制
    【快手面试】Word2vect生成的向量,为什么可以计算相似度,相似度有什么意义?
    【MySQL功法】第3话 · MySQL中常见的数据类型
    【BAT-表姐御用03MD(ren)命令】文件夹批量创建/命名/改名
    用于LLM的Chain-of-Symbol Prompting(符号链提示、CoS)
    PolarDB-X 的 in 常量查询
    查询方法需要使用事务吗
    Spring Boot 自定义注解
    Linux部署Nginx并实现网络代理
    【牛客网】BC146 添加逗号
  • 原文地址:https://blog.csdn.net/Baisitao_/article/details/125216475