家喻户晓,jvm类加载机制采纳双亲委派机制。但在有些框架中,经常为了提供某种模式的“隔离和沙盒”,自定义一种称为ChildFirst的了类加载器,简略的说就是毁坏了双亲委派,由自定义子类加载器优先加载类,而不是先委派给父加载器。因为同一个类能够在不同的类加载器中别离加载,应用ChildFirst机制,能够让类加载器造成一个“沙盒”,在程序中同时运行两个雷同但不同版本的类。

然而,笔者遇到一个常见的类加载抵触的案例,根因与ChildFirst机制无关。

起因

程序在flink平台上运行,将数据写入es,而某平台在开启了平安机制后,整个平台包含es都须要基于kerberos认证来拜访。基于先验的论断,须要替换魔改的elasticsearch-rest-client,其中应用GSSAPI登录了kerberos,并基于SENGPO协定,通过http发送了类token,还有一个独立的线程对token进行刷新。因为是个private jar,在工程中应用十分不敌对,所以思考在打包job的时候用maven-shade-plugin排除原先依赖的elasticsearch-rest-clientelasticsearch-rest-highlevel-client,而将定制版jar放到flink/lib目录下。

报错

提交作业后,Task Manager报错退出如下:

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

谬误形容的意思是:

org/apache/http/HttpHost同时在ChildFirstClassLoaderAppClassLoader中加载,在调用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.html

https://bigzuo.github.io/2017/03/19/java-LinkageError-loader-constraint-violation-error/

起因剖析

基于上面这些事实:

  1. job包中把HttpHost类打进去了。
  2. flink的tm过程启动的时候会将hadoop下的httpcore.x.x.x.jar加到classpath
  3. AppClassLoader负责加载classpath参数外面的类
  4. ChildFirstClassLoader负责加载job包中的类

事发代码

剖析:

实参 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,实测配置无效,但因为类加载连带关系简单,无奈穷举,所以作罢。