找不到的问题"/>
【总结】记一次log4j包冲突引发es类找不到的问题
问题现象
某天,某个应用搞新的迭代,突然报ElasticSearch 7.17.5 相关操作都失败了,且问题是必现,本地启动也能稳定复现。组内小伙伴按照es jar包冲突排查了一番,无果,于是问题转交给我来排查。
错误信息是:
Caused by: java.lang.NoClassDefFoundError: Could not initialize class org.elasticsearch.client.RestHighLevelClientat cn.tss.yunmon.util.es.restclient.ElasticSearchRestClient.restHighLevelClient(ElasticSearchRestClient.java:128)at cn.tss.yunmon.util.es.restclient.ElasticSearchRestClient.<init>(ElasticSearchRestClient.java:78)at cn.tss.yunmon.util.es.restclient.ElasticSearchRestClientFactory.getEsRestClient(ElasticSearchRestClientFactory.java:65)at cn.tss.yun.techponent.biz.service.impl.ElasticSearchService.getElasticSearchRestClient(ElasticSearchService.java:48)
排查分析
刚开始看到错误信息,注意,此处是NoClassDefFoundError,表明类是存在的,但是初始化时出错。如果是真的class文件都找不到,应该是报ClassNotFoundException.
我也按照ES jar包冲突的方向排查。一通解压反编译,排查下来,ES的相关的类和包,都没有版本冲突问题,和代码中使用的包路径,构造方法等,都是吻合的,但这里怪异的是,在其他应用中ES是正常使用的。
经过两次启动调试,笔者发现一个规律。应用在启动时,第一次访问ES相关接口,会先输出这么一段错误信息:
Caused by: java.lang.NoClassDefFoundError: Could not initialize class org.apache.logging.log4j.LogManager
at org.elasticsearch.client.RestHighLevelClient.(RestHighLevelClient.java:261)
于是瞬间,将焦点聚集在Log4j上。根据之前多次遇到类似的问题,这里很明显是log4j和logback的冲突。
进一步点开pom依赖证实,发现应用比其他应用多了:log4j-core、log4j-slf4j-impl 两个包。
瞬间豁然。表象上看是ES的RestHighLevelClient 类不存在,但是该类之所以不存在是因为,JVM第一次加载RestHighLevelClient class到内存中来时,由于class也要有初始化动作,该类有一个private static 的logger属性需要初始化,由于log4j和logback的冲突,导致该变量无法初始化,进一步导致RestHighLevelClient class 无法被加载成功。
line 261:private static final Logger logger = LogManager.getLogger(RestHighLevelClient.class);
程序上第二次、第三次、第N次访问接口时,报错就只报
Caused by: java.lang.NoClassDefFoundError: Could not initialize class org.elasticsearch.client.RestHighLevelClient
让人误以为是ES包冲突。
解决该问题,必须要对jvm 类加载机制有一定了解。
总结
直接原因
程序执行时创建ES 连接RestHighLevelClient 时,报该类不存在。
根本原因
hive-jdbc 升级版本到2.3.3后,间接依赖的log4j-core、log4j-slf4j-impl两个包和logback 的包有冲突。将引入log4j-core、log4j-slf4j-impl两个jar包排掉就可以了。
- 真正错误原因分析
关键错误信息:
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.NoClassDefFoundError: Could not initialize class org.apache.logging.log4j.LogManagerat org.elasticsearch.client.RestHighLevelClient.<clinit>(RestHighLevelClient.java:261)at cn.tss.yunmon.util.es.restclient.ElasticSearchRestClient.restHighLevelClient(ElasticSearchRestClient.java:128)at cn.tss.yunmon.util.es.restclient.ElasticSearchRestClient.<init>(ElasticSearchRestClient.java:78)
Caused by: java.lang.NoClassDefFoundError: Could not initialize class org.apache.logging.log4j.LogManager
at org.elasticsearch.client.RestHighLevelClient.(RestHighLevelClient.java:261)
这段核心错误的关键在于at org.elasticsearch.client.RestHighLevelClient.
clint是指jvm加载类初始化时,发生的错误。
JVM第一次加载RestHighLevelClient class到内存中来时,由于class初始化动作需要初始化static变量,该类有一个private static 的logger属性需要初始化,由于某些原因,导致该变量无法初始化,进一步导致RestHighLevelClient class 无法被加载成功。
- 源码截图
原因进一步分析
这个问题本以为按下面方式排除冲突的log4j-core、log4j-slf4j-impl两个jar包后,可以解决,但是最近系统运行一段时间后,又偶现这个问题了。
于是这次认真研究,争取找到真正的错误原因。经过一番网上资料查阅,最终发现偶现的问题,有可能和Tomcat WebappClassLoader 机制有关。
“原来Tomcat对webapp有一套自己的WebappClassLoader,它在启动的过程中会打开应用依赖的jar包来加载class信息,但是过一段时间就会把打开的jar包全部关闭从而释放资源。
然而如果后面需要加载某个新的class的时候,会把之前所有的jar包全部重新打开一遍,然后再从中找到对应的jar来加载。加载完后过一段时间会再一次全部释放掉。
所以应用依赖的jar包越多,同时打开的文件句柄数也会越多。
同时,我们在Tomcat的源码中也找到了上述WebappClassLoader的逻辑。”
解决方法一
可能是系统打开的文件句柄数达到上限了,然后当需要加载新的class文件时,jvm的加载机制需要将为所有jar包文件重新打开文件句柄,方便读取class文件。此时系统无法支持打开更多文件句柄数,导致加载class失败。
最终造成上述的错误现象。
详细的分析步骤:可以查看:/
方法1:修改linux 文件句柄数限制
方法2:在应用启动时,就提前加载LogManager 类。
// fix: Could not initialize class org.elasticsearch.client.RestHighLevelClient error
LogManager.getLogger(RestHighLevelClient.class);
new SpringApplicationBuilder(AppMain.class).run(args);
解决方法二
当然,还有一种情况,如果是log4j相关包冲突了,那么排除冲突的log4j-core、log4j-slf4j-impl两个jar包即可。
<dependency><groupId>org.apache.hive</groupId><artifactId>hive-jdbc</artifactId><exclusions><exclusion><groupId>org.apache.curator</groupId><artifactId>curator-framework</artifactId></exclusion><exclusion><groupId>org.apache.curator</groupId><artifactId>curator-recipes</artifactId></exclusion><exclusion><artifactId>*</artifactId><groupId>com.sun.jersey</groupId></exclusion><exclusion><artifactId>slf4j-log4j12</artifactId><groupId>org.slf4j</groupId></exclusion><exclusion><artifactId>log4j-core</artifactId><groupId>org.apache.logging.log4j</groupId></exclusion><exclusion><artifactId>log4j-slf4j-impl</artifactId><groupId>org.apache.logging.log4j</groupId></exclusion></exclusions></dependency>
思考
- 针对此类问题,jar包冲突导致类找不到,情况分两种。
- 一般是指某些包的版本有多个,maven仲裁选了其中一个导致class缺失。此时可以排掉错误的包,选用可用的包。
- 还有一种情况,可能是文件句柄数不够用了,应用本身依赖jar包文件比较多,瞬间需要打开的文件句柄比较大。在linux 一切皆文件的思想下,一个请求的发起,一个文件得打开都是要占用文件句柄数的。
- 任何问题的出现都有其必然性,偶然性的背后一定有规律可寻找。
更多推荐
【总结】记一次log4j包冲突引发es类找不到的问题
发布评论