众所周知,jvm类加载机制采用双亲委派机制。但在有些框架中,经常为了提供某种形式的“隔离和沙盒”,自定义一种称为ChildFirst
的了类加载器,简单的说就是破坏了双亲委派,由自定义子类加载器优先加载类,而不是先委派给父加载器。因为同一个类能够在不一样的类加载器中分别加载,使用ChildFirst
机制,可让类加载器造成一个“沙盒”,在程序中同时运行两个相同但不一样版本的类。html
可是,笔者遇到一个罕见的类加载冲突的案例,根因与ChildFirst
机制有关。java
程序在flink平台上运行,将数据写入es,而某平台在开启了安全机制后,整个平台包括es都须要基于kerberos认证来访问。基于先验的结论,须要替换魔改的elasticsearch-rest-client
,其中使用GSSAPI登陆了kerberos,并基于SENGPO协议,经过http发送了类token,还有一个独立的线程对token进行刷新。因为是个private jar,在工程中使用很是不友好,因此考虑在打包job的时候用maven-shade-plugin排除原先依赖的elasticsearch-rest-client
和elasticsearch-rest-highlevel-client
,而将定制版jar放到flink/lib目录下。git
提交做业后,Task Manager报错退出以下:github
java.lang.LinkageError: loader constraint violation: when resolving method "org.elasticsearch.client.RestClient.builder([Lorg/apache/http/HttpHost;)Lorg/elasticsearch/client/RestClientBuilder;" the class loader (instance of org/apache/flink/util/ChildFirstClassLoader) of the current class, org/apache/flink/streaming/connectors/elasticsearch6/Elasticsearch6ApiCallBridge, and the class loader (instance of sun/misc/Launcher$AppClassLoader) for the method's defining class, org/elasticsearch/client/RestClient, have different Class objects for the type [Lorg/apache/http/HttpHost; used in the signature
错误描述的意思是:apache
org/apache/http/HttpHost
同时在ChildFirstClassLoader
和AppClassLoader
中加载,在调用org.elasticsearch.client.RestClient.builder
的时候传入org/apache/http/HttpHost
实例的时候发现,方法签名(形参)中的类org/apache/http/HttpHost
属于AppClassLoader
,而实参的org/apache/http/HttpHost
却属于ChildFirstClassLoader
,形成了冲突。安全
下面这两篇文章能够参考一下:框架
https://www.cnblogs.com/deepnighttwo/archive/2011/08/31/2160990.htmljvm
https://bigzuo.github.io/2017/03/19/java-LinkageError-loader-constraint-violation-error/elasticsearch
基于下面这些事实:maven
事发代码
分析:
实参 httpHosts 是序列化到tm上的List<HttpHost>,也是Elasticsearch6ApiCallBridge这个类(在job包中)的私有属性。这意味着实参 httpHosts的类 优先由 ChildFirstClassLoader加载,且基于事实1,ChildFirstClassLoader能加载到HttpHost
RestClient类位于elasticsearch-rest-client,即在flink/lib目录,因为咱们打包的时候不会将elasticsearch-rest-client打进来,因此ChildFirstClassLoader没法加载到这个类,只能由AppClassLoader加载,并且因为事实2,AppClassLoader也能加载到HttpHost
这么一来,就会出现上面报错
最初咱们经过将flink改成parent-first
,能解决。通过分析:这是由于ParentFirstClassLoader
不会先从job包中加载HttpHost,转而由AppClassLoader加载HttpHost,这样就不会有冲突。
从上述根因分析,还有一个方案是将elasticsearch-rest-client等相关jar也打包到job里面,这样保证所有由ChildFirstClassLoader加载。不过这样引入private jar,形成了版本管理的混乱。
flink支持classloader.parent-first-patterns.additional,在child-first的前提下,对某些类作parent-first,实测配置有效,但因为类加载连带关系复杂,没法穷举,因此做罢。