【总结】记一次log4j包冲突引发es类找不到的问题

编程入门 行业动态 更新时间:2024-10-27 16:24:52

【总结】记一次log4j包冲突引发es类<a href=https://www.elefans.com/category/jswz/34/1771416.html style=找不到的问题"/>

【总结】记一次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类找不到的问题

本文发布于:2024-03-15 03:10:14,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1737983.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:找不到   冲突   log4j   es

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!