文章目录
- 背景
- 思考
- 初步解决方案
- 深入思考下
- 终极解决方案
- 总结
背景
实时作业在页面提交任务后,报NoSuchMethodException 方法,看了下是关于log4j的,首先是作业升级了很多依赖的版本,其次flink 也升级 到了1.19版本
思考
- 打的Jar有bug?
先想了一下是不是打的Jar有问题,后面想了下如果Jar有问题是类找不到才对 - 类冲突才对?
观察了一下
业务代码
flink 自带的日志类
结论,很明显版本不一样
初步解决方案
-
实时作业代码日志jar 降级
我先想到的解决方案是实时作业代码日志jar 降级,细看了一些实时里有很多依赖,而且是升级到java 21的,不好处理 -
flink 本身 日志jar 升级
尝试了下把报冲突的jar类升到了2.20的版本,服务启动不了,直接报错
然后尝试了下把其它日志类都升到了2.20的版本,服务可以启动,作业也可以正常提交,好像这里事情已经解决,但是我再看了一下,发现flink 自身的日志打印有总是,所有日志都没能正常打印 -
插件的方法把类重新命名?
这个方法不行,太多地方引用这个类了
似乎陷入了死胡同 ? 好像没招了,常用的这些方法都不行
深入思考下
回到问题本身,从第一性原理的角度出来想一下
- 为啥相同的类是加载的flink lib 目录下的jar,而不是实时作业的呢?
flink 本身实现了ChildFirstClassLoader ,默认是先加载用户代码里的class,如果没有加载到,再从其它地方加载,那理论上不应该有这个问题
打开源码看下
@Override
protected Class<?> loadClassWithoutExceptionHandling(String name, boolean resolve)
throws ClassNotFoundException {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
// check whether the class should go parent-first
for (String alwaysParentFirstPattern : alwaysParentFirstPatterns) {
if (name.startsWith(alwaysParentFirstPattern)) {
return super.loadClassWithoutExceptionHandling(name, resolve);
}
}
try {
// check the URLs
c = findClass(name);
} catch (ClassNotFoundException e) {
// let URLClassLoader do it, which will eventually call the parent
c = super.loadClassWithoutExceptionHandling(name, resolve);
}
} else if (resolve) {
resolveClass(c);
}
return c;
}
if (name.startsWith(alwaysParentFirstPattern)) {
上面这段引起了我的注意,有一些类是由AppClassLoader 加载的,顺藤摸瓜
发现了有这些类,发现也没有log4j的呀,差点看漏了,
还有这个变量的,终于发现了,原来在这,那看来找到了问题所在的地方
@Internal
public static final String[] PARENT_FIRST_LOGGING_PATTERNS =
new String[] {
"org.slf4j",
"org.apache.log4j",
"org.apache.logging",
"org.apache.commons.logging",
"ch.qos.logback"
};
终极解决方案
既然问题找到了,那最简单的是就是把这个配置的默认值修改下,修改config.yaml,服务启动,作业提交成功。 各方法都正常,问题解决
总结
- 可以想下为啥flink 要把那些类让AppClassLoader去加载
- 为啥要设计ChildFirstClassLoader