Flink类加载机制与--classpath参数动态加载外部类分析

1. Flink类加载机制

Flink采用不同于Java默认的类加载机制,而是采用两个类加载器的层级结构

  • Java应用程序加载器
    • AppClassLoader,加载类路径中包含的所有类
  • 用户代码类加载器
    • FlinkUserCodeClassLoader,用于从插件或用户代码jar加载类,动态代码类加载器使用应用程序类加载器作为父类

默认情况下Flink颠倒了类加载的顺序,它首先从动态类加载器中加载类,如果类不是动态类加载器加载的部分则只看父类(应用程序类加载器)

反向类加载的好处是插件和作业可以使用与Flink核心本身不同的库版本,这在不同版本的库不兼容时非常有用。 该机制有助于避免常见的依赖冲突错误,如IllegalAccessError或NoSuchMethodError。 代码的不同部分只是有类的单独副本(Flink的核心或它的一个依赖项可以使用不同于用户代码或插件代码的副本)。 在大多数情况下,这可以很好地工作,不需要来自用户的额外配置。
但是,反向类加载也会导致问题(“X不能转换为X”)。 对于用户代码的类加载,您可以通过ClassLoader配置ClassLoader的解析顺序来恢复到Java的默认模式。 Flink配置中的resolve-order为parent-first(来自Flink的默认child-first)。
请注意,某些类总是以父类优先的方式解析(通过父类ClassLoader优先),因为它们在Flink的核心和插件/用户代码或插件/用户代码接口之间共享。 这些类的包通过类加载器配置。 parent-first-patterns-default classloader.parent-first-patterns-additional。 要添加父级优先加载的新包,请设置类加载器。 parent-first-patterns-additional配置选项。

2. Flink动态类加载器

Flink使用FlinkUserCodeClassLoader加载器作为默认的加载器,该类继承了URLClassLoader使用AppClassLoader作为它的父类

public abstract class FlinkUserCodeClassLoader extends URLClassLoader {
   public static final Consumer<Throwable> NOOP_EXCEPTION_HANDLER = classLoadingException -> {};

   private final Consumer<Throwable> classLoadingExceptionHandler;

   protected FlinkUserCodeClassLoader(URL[] urls, ClassLoader parent) {
       this(urls, parent, NOOP_EXCEPTION_HANDLER);
   }

   protected FlinkUserCodeClassLoader(
           URL[] urls, ClassLoader parent, Consumer<Throwable> classLoadingExceptionHandler) {
       super(urls, parent);
       this.classLoadingExceptionHandler = classLoadingExceptionHandler;
   }

   @Override
   public final Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
       try {
           synchronized (getClassLoadingLock(name)) {
               return loadClassWithoutExceptionHandling(name, resolve);
           }
       } catch (Throwable classLoadingException) {
           classLoadingExceptionHandler.accept(classLoadingException);
           throw classLoadingException;
       }
   }

   /**
    * Same as {@link #loadClass(String, boolean)} but without exception handling.
    *
    * <p>Extending concrete class loaders should implement this instead of {@link
    * #loadClass(String, boolean)}.
    */
   protected Class<?> loadClassWithoutExceptionHandling(String name, boolean resolve)
           throws ClassNotFoundException {
       return super.loadClass(name, resolve);
   }
}

Flink 默认的类加载规则是children-first,加载器为ChildFirstClassLoader是FlinkUserCodeClassLoader的子类,核心加载类的方法如下:

@Override
    protected Class<?> loadClassWithoutExceptionHandling(String name, boolean resolve)
            throws ClassNotFoundException {

        // 首先检查该类是否已经被加载了
        Class<?> c = findLoadedClass(name);

        if (c == null) {
            // 未被加载,则判断该类是否为总是通过父类加载器进行加载的
            for (String alwaysParentFirstPattern : alwaysParentFirstPatterns) {
                if (name.startsWith(alwaysParentFirstPattern)) {
                    return super.loadClassWithoutExceptionHandling(name, resolve);
                }
            }

            try {
                // 使用当前类加载器进行加载
                c = findClass(name);
            } catch (ClassNotFoundException e) {
                // 当前类加载器加载失败则只通过父加载器进行加载
                c = super.loadClassWithoutExceptionHandling(name, resolve);
            }
        } else if (resolve) {
            resolveClass(c);
        }

        return c;
    }

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

配置规则也是通过分号分割的全限定名的前缀

3. 注意事项

Flink的父类加载器加载的类只能是classpath下的类,Flink会将flink的lib目录加载到classpath中,对于–classpath动态加载的类,如果使用Flink的父类加载器会出现ClassNotFound的错误,这也是–classpath指定jar路径和直接把jar放在lib目录的区别。

所以对于一些特殊的jar包不能通过–classpath进行加载,只能将其放在lib目录,因为这些特殊的jar包使用的是父类加载器而不是动态类加载器,会导致ClassNotFound的错误(因为有碰到这种情况,这个具体还没有验证,待后期验证猜想)

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值