Flink的双亲委派机制

我们知道,在 JVM 中,一个类加载的过程大致分为加载、链接(验证、准备、解析)、初始化5个阶段。而我们通常提到类的加载,就是指利用类加载器(ClassLoader)通过类的全限定名来获取定义此类的二进制字节码流,进而构造出类的定义。

Flink 作为基于 JVM 的框架,在 flink-conf.yaml 中提供了控制类加载策略的参数 classloader.resolve-order,可选项有 child-first(默认)和 parent-first。本文来简单分析一下这个参数背后的含义。

b9e1758d84c848bc2140b82bacbb4da5.png
parent-first 类加载策略
ParentFirstClassLoader 和 ChildFirstClassLoader 类的父类均为 FlinkUserCodeClassLoader 抽象类,先来看看这个抽象类,代码很短。

public abstract class FlinkUserCodeClassLoader extends URLClassLoader {    public static final Consumer NOOP_EXCEPTION_HANDLER = classLoadingException -> {};    private final Consumer classLoadingExceptionHandler;    protected FlinkUserCodeClassLoader(URL[] urls, ClassLoader parent) {        this(urls, parent, NOOP_EXCEPTION_HANDLER);    }    protected FlinkUserCodeClassLoader(            URL[] urls,            ClassLoader parent,            Consumer classLoadingExceptionHandler) {        super(urls, parent);        this.classLoadingExceptionHandler = classLoadingExceptionHandler;    }    @Override    protected final Class> loadClass(String name, boolean resolve) throws ClassNotFoundException {        try {            return loadClassWithoutExceptionHandling(name, resolve);        } catch (Throwable classLoadingException) {            classLoadingExceptionHandler.accept(classLoadingException);            throw classLoadingException;        }    }    protected Class> loadClassWithoutExceptionHandling(String name, boolean resolve) throws ClassNotFoundException {        return super.loadClass(name, resolve);    }}


FlinkUserCodeClassLoader 继承自 URLClassLoader。因为 Flink App 的用户代码在运行期才能确定,所以通过 URL 在 JAR 包内寻找全限定名对应的类是比较合适的。而 ParentFirstClassLoader 仅仅是一个继承 FlinkUserCodeClassLoader 的空类而已。

static class ParentFirstClassLoader extends FlinkUserCodeClassLoader {    ParentFirstClassLoader(URL[] urls, ClassLoader parent, Consumer classLoadingExceptionHandler) {        super(urls, parent, classLoadingExceptionHandler);    }}


这样就相当于 ParentFirstClassLoader 直接调用了父加载器的 loadClass() 方法。之前已经讲过,JVM 中类加载器的层次关系和默认 loadClass() 方法的逻辑由双亲委派模型(parents delegation model)来体现,复习一下含义:

e0df2874dc679a3641c2874380de3e91.png

 

如果一个类加载器要加载一个类,它首先不会自己尝试加载这个类,而是把加载的请求委托给父加载器完成,所有的类加载请求最终都应该传递给最顶层的启动类加载器。只有当父加载器无法加载到这个类时,子加载器才会尝试自己加载。


可见,Flink 的 parent-first 类加载策略就是照搬双亲委派模型的。也就是说,用户代码的类加载器是 Custom ClassLoader,Flink 框架本身的类加载器是 Application ClassLoader。用户代码中的类先由 Flink 框架的类加载器加载,再由用户代码的类加载器加载。但是,Flink 默认并不采用 parent-first 策略,而是采用下面的 child-first 策略,继续看。

child-first 类加载策略
我们已经了解到,双亲委派模型的好处就是随着类加载器的层次关系保证了被加载类的层次关系,从而保证了 Java 运行环境的安全性。但是在 Flink App 这种依赖纷繁复杂的环境中,双亲委派模型可能并不适用。例如,程序中引入的 Flink-Cassandra Connector 总是依赖于固定的 Cassandra 版本,用户代码中为了兼容实际使用的 Cassandra 版本,会引入一个更低或更高的依赖。而同一个组件不同版本的类定义有可能会不同(即使类的全限定名是相同的),如果仍然用双亲委派模型,就会因为 Flink 框架指定版本的类先加载,而出现莫名其妙的兼容性问题,如 NoSuchMethodError、IllegalAccessError 等。

鉴于此,Flink 实现了 ChildFirstClassLoader 类加载器并作为默认策略。它打破了双亲委派模型,使得用户代码的类先加载,官方文档中将这个操作称为"Inverted Class Loading"。代码仍然不长,录如下。

public final class ChildFirstClassLoader extends FlinkUserCodeClassLoader {    private final String[] alwaysParentFirstPatterns;    public ChildFirstClassLoader(            URL[] urls,            ClassLoader parent,            String[] alwaysParentFirstPatterns,            Consumer classLoadingExceptionHandler) {        super(urls, parent, classLoadingExceptionHandler);        this.alwaysParentFirstPatterns = alwaysParentFirstPatterns;    }    @Override    protected synchronized 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);            }        }        if (resolve) {            resolveClass(c);        }        return c;    }    @Override    public URL getResource(String name) {        // first, try and find it via the URLClassloader        URL urlClassLoaderResource = findResource(name);        if (urlClassLoaderResource != null) {            return urlClassLoaderResource;        }        // delegate to super        return super.getResource(name);    }    @Override    public Enumeration getResources(String name) throws IOException {        // first get resources from URLClassloader        Enumeration urlClassLoaderResources = findResources(name);        final List result = new ArrayList<>();        while (urlClassLoaderResources.hasMoreElements()) {            result.add(urlClassLoaderResources.nextElement());        }        // get parent urls        Enumeration parentResources = getParent().getResources(name);        while (parentResources.hasMoreElements()) {            result.add(parentResources.nextElement());        }        return new Enumeration() {            Iterator iter = result.iterator();            public boolean hasMoreElements() {                return iter.hasNext();            }            public URL nextElement() {                return iter.next();            }        };    }}


核心逻辑位于 loadClassWithoutExceptionHandling() 方法中,简述如下:

1、调用 findLoadedClass() 方法检查全限定名 name 对应的类是否已经加载过,若没有加载过,再继续往下执行。
检查要加载的类是否以 alwaysParentFirstPatterns 集合中的前缀开头。如果是,则调用父类的对应方法,以 parent-first 的方式来加载它。
2、如果类不符合 alwaysParentFirstPatterns 集合的条件,就调用 findClass() 方法在用户代码中查找并获取该类的定义(该方法在 URLClassLoader 中有默认实现)。如果找不到,再 fallback 到父加载器来加载。
3、最后,若 resolve 参数为 true,就调用 resolveClass() 方法链接该类,最后返回对应的 Class 对象。
可见,child-first 策略避开了“先把加载的请求委托给父加载器完成”这一步骤,只有特定的某些类一定要“遵循旧制”。alwaysParentFirstPatterns 集合中的这些类都是 Java、Flink 等组件的基础,不能被用户代码冲掉。它由以下两个参数来指定:

classloader.parent-first-patterns.default,不建议修改,固定为以下这些值:

java.;scala.;org.apache.flink.;com.esotericsoftware.kryo;org.apache.hadoop.;javax.annotation.;org.slf4j;org.apache.log4j;org.apache.logging;org.apache.commons.logging;ch.qos.logback;org.xml;javax.xml;org.apache.xerces;org.w3c
classloader.parent-first-patterns.additional:除了上一个参数指定的类之外,用户如果有其他类以 child-first 模式会发生冲突,而希望以双亲委派模型来加载的话,可以额外指定(分号分隔)。
以上是关于 flink-conf.yaml 中提供的控制类加载策略的参数 classloader.resolve-order 含义的理解和分享,希望对大家有所启发和帮助~

原文作者:LittleMagic
————————————————
版权声明:本文为CSDN博主「蔡惘然」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_36185435/article/details/112520219

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Flink序列化机制是将数据从Java对象转换为字节序列的过程,以便在网络上传输或存储到磁盘中。Flink使用Kryo作为默认的序列化器,Kryo是一个快速高效的Java序列化框架,可以将Java对象序列化为字节数组,也可以将字节数组反序列化为Java对象。在Flink中,序列化器是根据数据类型来选择的,不同的数据类型有不同的序列化器。例如,对于Tuple、Pojo和CaseClass等复合类型,它们的序列化器是复合的,会将内嵌类型的序列化委托给对应类型的序列化器。在序列化操作时,会委托相应具体序列化的序列化器进行相应的序列化操作。Flink还提供了WritableSerializer和AvroSerializer等其他类型的序列化器,用户可以根据需要选择不同的序列化器。 示例代码如下: ```java // 定义一个POJO类 public class Person implements Serializable { private String name; private int age; public Person() {} public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } // 使用Kryo序列化器将Person对象序列化为字节数组 Person person = new Person("张三", 20); KryoSerializer<Person> serializer = new KryoSerializer<>(Person.class, new ExecutionConfig()); byte[] bytes = serializer.serialize(person); // 使用Kryo序列化器将字节数组反序列化为Person对象 Person person2 = serializer.deserialize(bytes); System.out.println(person2.getName() + " " + person2.getAge()); // 输出:张三 20 ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值