这是我参与更文挑战的第2天,活动详情查看:更文挑战
问题由来
商城搜索之前是基于Solr实现的,现在根据领导要求要替换为分布式性更好的ElasticSerch,数据同步方案在调研一番之后,选择使用阿里出品的canal和canal adapter for ElasticSearch,但是从Github上下载下来的canal adapter在部署之后一直出错,下面记录一下自己的跟踪过程.
定位思路
现象描述
从Github上下载最先的canal和canal_adapter的二进制包,canal_adapter测试的全量导入数据没问题,但是无法增量导入
查看启动日志
在查案canal adapter时发现启动日志有报错,原来canala adapter在启动的时候有错仍然会继续启动,自己有点理想化了,以为能启动起来就没问题.
查看启动日志发现如下报错:
Load canal adapter: es7 failed
java.lang.RuntimeException: java.lang.RuntimeException: java.lang.ClassCastException: com.alibaba.druid.pool.DruidDataSource cannot be cast to com.alibaba.druid.pool.DruidDataSource
有经验的老鸟应该一眼就能猜出问题在哪里,自己之前Java做的不多,所以走了一些弯路,下面记录一下定位步骤吧
跑源码定位问题
把canal adapter的源码下载下来,然后运行调试,最后定位到一个catch异常:
DruidDataSource dataSource = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey());
简单记录一下中排除的一些点:
- DATA_SOURCES是一个HashMap类型,先确认Map不为空 -- 排除
- 因为日志中说的是cast时报错,所以怀疑类型不匹配,所以分别把HashMap的put和get方法处的class类型打印出来,类名相同 -- 排除
- 既然是在做类型转换时报错,尝试着不取出具体的类型,而是用Object去存取出的对象,结果是取出时没问题,但在最后调用类中方法时报错 -- 排除
分析依赖关系
用mvn dependency:tree命令分析依赖包的依赖关系,分析结果没有异常,使用的都是druid的1.2.6的包,但是看到一个和druid相关的Warning
[WARNING] The POM for com.alibaba:druid:jar:1.2.6 is invalid, transitive dependencies (if any) will not be available, enable debug logging for more details
用下面命令查看详细报错内容:
mvn -X -U dependency:tree
详细信息如下:
[WARNING] The POM for com.alibaba:druid:jar:1.2.6 is invalid, transitive dependencies (if any) will not be available: 2 problems were encountered while building the effective model for com.alibaba:druid:1.2.6
[ERROR] 'dependencies.dependency.systemPath' for com.sun:tools:jar must specify an absolute path but is ${project.basedir}/lib/openjdk-1.8-tools.jar @
[ERROR] 'dependencies.dependency.systemPath' for com.sun:jconsole:jar must specify an absolute path but is ${project.basedir}/lib/openjdk-1.8-jconsole.jar @
这个错网上查了查,没有和这个特别相关的,但是在编译的时候是可以正常编译通过的,所以猜测可能是这里在做依赖分析时一些依赖关系查找,而且druid的包都是直接从网上下载编译好的,所以这个问题就没再深究下去
问题根源:classloader
迷茫之中,用Google搜了一下,看到有人有同样的问题,然后去Github上看了一下issue,有这个问题,大概意思是之前的alpha版中就反馈了这个问题,但是在最新release版中,仍然没有解决掉
问题根源就是使用了两个类加载器来加载Druid的这个包,下面是从issue中摘抄并加上自己分析:
1). 应用启动的时候,AppClassLoader类加载器加载了一遍druid
2). 因为client-adapter.es7x-1.1.5-SNAPSHOT-jar-with-dependencies.jar中包含了druid的包,所以自定义的URLClassExtensionLoader又从client-adapter.es7x-1.1.5-SNAPSHOT-jar-with-dependencies.jar这个包里面加载了一遍druid
3). 因为HashMap的put()操作和get()操作是在不同的模块中操作的,导致在put()时使用的是AppClassLoader类加载器加载的druid,而在get()时使用的是由URLClassExtensionLoader加载的druid,因为不同加载器加载的同一个jar包也是不同的类型,所以这里才会报无法cast的错误,根本原因就是因为不同的类加载器来加载的druid包
解决方案
在client-adapter的escore module中的pom.xml中,找到druid依赖配置,加上scope范围
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<scope>provided</scope>
</dependency>
把scope配置为provided之后,表示只在测试和编译时使用这个依赖,在运行的时候不使用这个依赖,即最终结果是让es的xxxx-with-dependency.jar不包含druid这个包