Canal Adapter运行出现空指针异常java.lang.RuntimeException: java.lang.NullPointerException

环境:

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的字段一定要加前缀不然会出现空指针异常,推荐使用表别名(即使表名比较简单)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值