环境:
Canal-adapter(1.1.8),MySQL8,ElasticSearch7,Windows11
概述:
项目中遇到MySQL同步数据到ES中的问题,查阅了相关资料之后发现Canal在这一块用的比较多,于是我采用Canal-adapter的方式来实现Canal客户端(省事,还可以手写Canal-Client),但是启动Adapter后发现了一系列的问题,比如Conf Not Found , 还有配置文件yml写错导致的一系列问题。但是其中最棘手的是空指针这个问题,因为我发现网上根本没有对应的解决方案,问了众多AI结果也是以失败告终,于是准备分享自己解决这个Bug的方法和思路。
场景:
我是在更新和删除MySQL行数据的时候出现的这个问题,插入的时候没有。
MySQL表结构(随便建的测试用):
create table test
(
id int not null
primary key,
a varchar(255) null,
b varchar(255) null,
age int null,
sex tinyint null
);
ES索引结构
"mappings" : {
"properties" : {
"a" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword"
}
},
},
"age" : {
"type" : "integer"
},
"b" : {
"type" : "text",
"index" : false
},
"sex" : {
"type" : "boolean"
}
}
}
修改MySQL Test表任意内容结果adapter出现以下错误
解决方案:
修改adapter的/conf/es7/test.yml(表名.yml)文件,修改sql属性
改为
sql: "SELECT t.id , t.a, t.b , t.age , t.sex FROM test t" # 查询字段,仅同步 id, a, b
结果:
同步成功
原因:
yml配置文件中的sql映射一定要加上表别名(from test t),select后的字段要通过表别名访问(select t.id,t.a ....)不然会报错空指针。
DEBUG思路:
下载Canal-adpter源码,通过IDEA来运行,复现问题场景
通过控制台的异常栈信息很容易进入出现问题的源码
进入ESSyncService.java:112的112行可以看到运行时异常,我们在87行打上断点一步一步的走,程序执行update方法(97行),一直跟进最终发现空指针异常的原因所在
空指针异常位置
原来是pkVal.toString发生了空指针,那么pkVal是什么东西呢?
我们往上寻找pkVal这个值是怎么来的
发现原来是通过getESDataFromDmlData这个方法得到的,那方法返回值肯定是空的,为什么是空的呢?进入方法,然后重新Debug此方法。
方法的返回值是resultIdVal,该变量初始值为空,为该变量赋值的地方只有
resultIdVal = getValFromData(mapping, dmlData, fieldItem.getFieldName(), columnName);
这段代码,通过debug单步执行发现这行根本没有执行,因为columnItem.getOwner()一直是空的所以说,循环会跳过赋值语句(continue),所以说空指针异常的根本原因就是columnItem.getOwner()是空的,但是这时候问题又来了!owner是什么东西呢?跟我们的配置文件有什么关系?
我们这里找到ColumnItem的类定义,发现这个类属于SchemaItem的静态内部类
那么SchemaItem又是什么东西呢?看到sql,aliasTableItems,selectFields这些字段之后,以及注释:ES映射配置视图,加上之前运行时看到SchemaItem的字段值可以猜测SchemaItem是由我们之前定义的,表名.yml这个配置文件所生成的。
并且我们之前定位到的columnItem这个对象就是从SchemaItem的对象中拿到的。
schemaItem又是由ESMapping对象得到的,然后ESMapping又是由ESSyncConfig拿到的
我们查看ESMapping的定义,看这些熟悉的字段index,type,id,sql这不是我们配置文件的字段吗?然后还有一个关键注释SchemaItem => sql解析结果模型,所以我们最终的目标是sql语句到SchemaItem是怎么映射的?然后columnItem的Owner和sql有着什么样的关系呢?
所以我们一定要留意schemaItem作为左值被赋值的语句(这些语句可以发现sql->schemaItem的过程),所以我们在Idea中按住ctrl+鼠标左键点击SchemaItem类名,然后发现有很多地方用到了SchemaItem。
这里我们发现一个非常重要的一段代码 SchemaItem schemaItem = SqlParser.parse(sql);(还是Test) 我们跟进去
看到这里真的感觉前途一片光明,直接把sql给写出来了(这里有一定的运气成分,Canal-adapter开发者做过sql到schemaIem的测试,如果没有的话应该会定位到其他地方稍微麻烦了点)
然后我们以Debug的方式来运行Test,查看SchemaItem内部各个字段的值!
发现owner是字段的前缀
然后把开发者原来写的sql改为我们之前出问题的SQL,重新debug查看owner字段,果然owner是null
所以得出结论yml配置文件sql,select的字段一定要加前缀不然会出现空指针异常,推荐使用表别名(即使表名比较简单)