关于seata1.4.2版本适配达梦数据库8.0的改造方案
一、前言
- 近期公司接到不少客户需求,希望系统支持国产达梦数据库
- 由于系统默认一直适配的是MySQL,所以在应用层面改造了较多地方
- 同时,也配置了达梦数据库对MySQL的兼容配置,尽可能减少系统业务代码的修改
- 最后还有一个问题就是分布式事务解决方案,seata的适配
- 系统目前使用的是最新稳定版本 1.4.2,查看了GitHub及官方文档,考虑在2.0版本才会适配达梦数据库,这对我们来说肯定有点来不及了
- 同时也查看到https://github.com/seata/seata/pull/3672,这个PR
- 也是考虑尽可能少的改动,这个PR暂时作为后备方案
- 目前的思路就是,seata-server端改用Redis做存储,只修改客户端这边兼容代码,这样看来似乎改动不会太大
- 自把数据库驱动及连接换成达梦后,测试了seata就工作不正常了,关键错误如下
not found service provider for : io.seata.sqlparser.druid.SQLOperateRecognizerHolder
二、具体改造过程(艰难)
-
首先感谢 seata QQ交流群中的大佬 wangliang181230 指点 -
诊断问题肯定是dbType变成了dm,经过我debug,确实 -
既然达梦做了适配MySQL的配置,那么思路是将dbType固定为MySQL -
目前dbType没有直接的配置项,只有dbParserType可以配置,把dbParserType配置成自定义开发的,也就是这个实现类 io.seata.sqlparser.util.DbTypeParser
-
然后就不啰嗦了,直接3个步骤,进行一波修改 -
第一步:在自己的客户端工程下,创建类MyDbTypeParser,内容如下 package io.seata.sqlparser.druid;
import io.seata.common.loader.LoadLevel;
import io.seata.sqlparser.util.DbTypeParser;
@LoadLevel(name = "druid", order = 1)
public class MyDbTypeParser implements DbTypeParser {
public MyDbTypeParser() {
}
@Override
public String parseFromJdbcUrl(String jdbcUrl) {
return "mysql";
}
}
-
第二步:创建配置文件,并添加到resource目录下 文件名:/META-INF/services/io.seata.sqlparser.util.DbTypeParser
文件内容:io.seata.sqlparser.druid.MyDbTypeParser
-
第三步:添加启动配置: seata.client.rm.sql-parser-type=druid
-
以上三步做完,情况如上图所示,重启服务提供者和调用者测试 -
从debug情况看,dbType已经对了,不过又出现一个问题 needs to contain the primary key. -
经过确认,达梦库的表肯定是有主键的,然后一波debug,最后发现这个地方判主键的地方太MySQL了,直接取索引名称=PRIMARY,达梦那边主键索引名是类似 INDEX33566573,而且经过一顿研究,还无法自定义这个索引名称 -
于是先简单粗暴的改下代码试试,加一个或列名=ID ,再探下还有没有别的坑 if ("PRIMARY".equalsIgnoreCase(indexName) || "id".equalsIgnoreCase(colName)) {
-
然后接着调试,又发现如下异常,没办法,都到这里了,不想放弃,于是又是一波研究,找到了问题所在 -
看下面的debug里的SQL,拿到达梦客户端那边执行下就知道了,content是达梦的关键字,哎!!! -
不想放弃,继续尝试把列名修改下,然后再修改下 seata 代码,把这个常量改成列名为 context_val io.seata.core.constants.ClientTableColumnsName#UNDO_LOG_CONTEXT=context_val
-
再试,哎,功夫不负有心人,成功了!最后不能高兴太早,完整的提交、异常回滚都得要测试一波,没问题才好
16. 对了,还有个地方要放开,之前为了让其不干扰排查seata问题给注释掉了,所以这个再加上,主要也是解决MP框架下的列名关键字问题,找到 MybatisAutoConfiguration
GlobalConfig.DbConfig dbConfig = new GlobalConfig.DbConfig();
dbConfig.setPropertyFormat("\"%s\"");
dbConfig.setColumnFormat("\"%s\"");
globalCfg.setDbConfig(dbConfig);
- 经过多种情况测试,一切正常,nice!
三、总结
- 通过上面的三个步骤,主要的目的是利用SPI机制,将DbTypeParser替换为自己的MyDbTypeParser,让seata工作时,dbType固定为 mysql,因为还么有适配dm的一系列处理类,所以没办法。
- 其次就是再遇到其他问题时,仔细的调试,根据情况,结合上下文推断问题所在,因为有源码在手,很多东西还是比较好研究、调试。不过这块也挺难的,需要有耐心、细心跟代码,也需要一定的推断经验,才好搞定问题。
- 这个思路也是尽可能偷懒、求稳定的思路,不确定一些复杂的业务场景下,seata是否能工作正常。
- 最后把代码整理下,可以考虑把DbTypeParser类和SPI配置文件放到自己工程的公共模块下,那个配置也是一样,可以放到配置中心,这样就不需要每个服务都加了
- 期待后续发布的2.0版本,能够适配更多的国产数据库,感谢你能看到这,希望这篇文章对你有帮助,省得自己去绕弯子了!
|