Flink基于用户程序生成JobGraph,提交到集群进行分布式部署运行。本篇从源码角度讲解一下Flink Jar包是如何被提交到集群的。(本文源码基于Flink 1.11.3)

1 Flink run 提交Jar包流程分析

首先分析run脚本可以找到入口类CliFrontend,这个类在main方法中解析参数,基于第二个参数定位到run方法:

try {
// do action
switch (action) {
case ACTION_RUN:
run(params);
return 0;
case ACTION_RUN_APPLICATION:
runApplication(params);
return 0;
case ACTION_LIST:
list(params);
return 0;
case ACTION_INFO:
info(params);
return 0;
case ACTION_CANCEL:
cancel(params);
return 0;
case ACTION_STOP:
stop(params);
return 0;
case ACTION_SAVEPOINT:
savepoint(params);
return 0;
case "-h":
case "--help":
...
return 0;
case "-v":
case "--version":
...
default:
...
return 1;
}
}

在run方法中,根据classpath、用户指定的jar、main函数等信息创建PackagedProgram。在Flink中通过Jar方式提交的任务都封装成了PackagedProgram对象。

protected void run(String[] args) throws Exception {
...
final ProgramOptions programOptions = ProgramOptions.create(commandLine);
final PackagedProgram program = getPackagedProgram(programOptions); // 把用户的jar配置到config里面
final List<URL> jobJars = program.getJobJarAndDependencies();
final Configuration effectiveConfiguration = getEffectiveConfiguration(
activeCommandLine, commandLine, programOptions, jobJars); try {
executeProgram(effectiveConfiguration, program);
} finally {
program.deleteExtractedLibraries();
}
}

创建PackagedProgram后,有个非常关键的步骤就是这个effectiveConfig,这里面会把相关的Jar都放入pipeline.jars这个属性里,后面pipeline提交作业时,这些jar也会一起提交到集群。

其中比较关键的是Flink的类加载机制,为了避免用户自己的jar内与其他用户冲突,采用了逆转类加载顺序的机制。

private PackagedProgram(
@Nullable File jarFile,
List<URL> classpaths,
@Nullable String entryPointClassName,
Configuration configuration,
SavepointRestoreSettings savepointRestoreSettings,
String... args) throws ProgramInvocationException { // 依赖的资源
this.classpaths = checkNotNull(classpaths); // 保存点配置
this.savepointSettings = checkNotNull(savepointRestoreSettings); // 参数配置
this.args = checkNotNull(args); // 用户jar
this.jarFile = loadJarFile(jarFile); // 自定义类加载
this.userCodeClassLoader = ClientUtils.buildUserCodeClassLoader(
getJobJarAndDependencies(),
classpaths,
getClass().getClassLoader(),
configuration); // 加载main函数
this.mainClass = loadMainClass(
entryPointClassName != null ? entryPointClassName : getEntryPointClassNameFromJar(this.jarFile),
userCodeClassLoader);
}

在类加载器工具类中根据参数classloader.resolve-order决定是父类优先还是子类优先,默认是使用子类优先模式。

executeProgram方法内部是启动任务的核心,在完成一系列的环境初始化后(主要是类加载以及一些输出信息),会调用packagedProgram的invokeInteractiveModeForExecution的,在这个方法里通过反射调用用户的main方法。

private static void callMainMethod(Class<?> entryClass, String[] args)
throws ProgramInvocationException {
...
Method mainMethod = entryClass.getMethod("main", String[].class);
mainMethod.invoke(null, (Object) args);
...
}

执行用户的main方法后,就是flink的标准流程了。创建env、构建StreamDAG、生成Pipeline、提交到集群、阻塞运行。当main程序执行完毕,整个run脚本程序也就退出了。

总结来说,Flink提交Jar任务的流程是:
1 脚本入口程序根据参数决定做什么操作
2 创建PackagedProgram,准备相关jar和类加载器
3 通过反射调用用户Main方法
4 构建Pipeline,提交到集群

2 通过PackagedProgram获取Pipeline

有的时候不想通过阻塞的方式卡任务执行状态,需要通过类似JobClient的客户端异步查询程序状态,并提供停止退出的能力。

要了解这个流程,首先要了解Pipeline是什么。用户编写的Flink程序,无论是DataStream API还是SQL,最终编译出的都是Pipeline。只是DataStream API编译出的是StreamGraph,而SQL编译出的Plan。Pipeline会在env.execute()中进行编译并提交到集群。

既然这样,此时可以思考一个问题:Jar包任务是独立的Main方法,如何能抽取其中的用户程序获得Pipeline呢?

通过浏览源码的单元测试,发现了一个很好用的工具类:PackagedProgramUtils。

public static Pipeline getPipelineFromProgram(
PackagedProgram program,
Configuration configuration,
int parallelism,
boolean suppressOutput) throws CompilerException, ProgramInvocationException { // 切换classloader
final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(program.getUserCodeClassLoader()); // 创建env
OptimizerPlanEnvironment benv = new OptimizerPlanEnvironment(
configuration,
program.getUserCodeClassLoader(),
parallelism);
benv.setAsContext();
StreamPlanEnvironment senv = new StreamPlanEnvironment(
configuration,
program.getUserCodeClassLoader(),
parallelism);
senv.setAsContext(); try {
// 执行用户main方法
program.invokeInteractiveModeForExecution();
} catch (Throwable t) {
if (benv.getPipeline() != null) {
return benv.getPipeline();
} if (senv.getPipeline() != null) {
return senv.getPipeline();
} ...
} finally {
// 重置classloader
}
}

这个工具类首先在线程内创建了一个env,这个env通过threadload保存到当前线程中。当通过反射调用用户代码main方法时,内部的getEnv函数直接从threadlocal中获取到这个env。

ThreadLocal<StreamExecutionEnvironmentFactory> factory = new ThreadLocal<>();

public static StreamExecutionEnvironment getExecutionEnvironment() {
return Utils.resolveFactory(factory , contextEnvironmentFactory)
.map(StreamExecutionEnvironmentFactory::createExecutionEnvironment)
.orElseGet(StreamExecutionEnvironment::createLocalEnvironment);
}

再回头看看env有什么特殊的。

public class StreamPlanEnvironment extends StreamExecutionEnvironment {

    private Pipeline pipeline;

    public Pipeline getPipeline() {
return pipeline;
} @Override
public JobClient executeAsync(StreamGraph streamGraph) {
pipeline = streamGraph; // do not go on with anything now!
throw new ProgramAbortException();
}
}

原来是重写了executeAysnc方法,当用户执行env.execute时,触发异常,从而在PackagedProgramUtils里面拦截异常,获取到用户到pipeline。

总结起来流程如下:

3 编程实战

通过阅读上述源码,可以学习到:

1 classloader类加载的父类优先和子类优先问题
2 threadlocal线程级本地变量的使用
3 PackagedProgramUtils 利用枚举作为工具类
4 PackagedProgramUtils 利用重写env,拦截异常获取pipeline。

关于pipeline如何提交到集群、如何运行,就后文再谈了。

Flink源码剖析:Jar包任务提交流程的更多相关文章

  1. 转】MyEclipse使用总结——使用MyEclipse打包带源码的jar包

    原博文出自于: http://www.cnblogs.com/xdp-gacl/p/4136303.html 感谢! 平时开发中,我们喜欢将一些类打包成jar包,然后在别的项目中继续使用,不过由于看不 ...

  2. MyEclipse使用总结——使用MyEclipse打包带源码的jar包

    平时开发中,我们喜欢将一些类打包成jar包,然后在别的项目中继续使用,不过由于看不到jar包里面的类的源码了,所以也就无法调试,要想调试,那么就只能通过关联源代码的形式,这样或多或少也有一些不方便,今 ...

  3. eclipse导出附带源码的jar包

    最近在搞Andengine游戏开发,发现andengine的jar包可以直接点击查看源码,而其他项目的jar包却看不了,因此自己研究了下如何生成可以直接查看源码的jar包. 1.eclipse中点击项 ...

  4. MyEclipse打包带源码的jar包

    平时开发中,我们喜欢将一些类打包成jar包,然后在别的项目中继续使用,不过由于看不到jar包里面的类的源码了,所以也就无法调试,要想调试,那么就只能通过关联源代码的形式,这样或多或少也有一些不方便,今 ...

  5. Eclipse使用总结——使用Eclipse打包带源码的jar包

    平时开发中,我们喜欢将一些类打包成jar包,然后在别的项目中继续使用,不过由于看不到jar包里面的类的源码了,所以也就无法调试,要想调试,那么就只能通过关联源代码的形式,这样或多或少也有一些不方便,今 ...

  6. Flink 源码解析 —— Standalone Session Cluster 启动流程深度分析之 Job Manager 启动

    Job Manager 启动 https://t.zsxq.com/AurR3rN 博客 1.Flink 从0到1学习 -- Apache Flink 介绍 2.Flink 从0到1学习 -- Mac ...

  7. Flink 源码解析 —— Standalone session 模式启动流程

    Standalone session 模式启动流程 https://t.zsxq.com/EemAEIi 博客 1.Flink 从0到1学习 -- Apache Flink 介绍 2.Flink 从0 ...

  8. Flink 源码解析 —— Standalone Session Cluster 启动流程深度分析之 Task Manager 启动

    Task Manager 启动 https://t.zsxq.com/qjEUFau 博客 1.Flink 从0到1学习 -- Apache Flink 介绍 2.Flink 从0到1学习 -- Ma ...

  9. Apache DolphinScheduler 源码剖析之 Worker 容错处理流程

    今天给大家带来的分享是 Apache DolphinScheduler 源码剖析之 Worker 容错处理流程 DolphinScheduler源码剖析之Worker容错处理流程 Worker容错流程 ...

  10. DolphinScheduler 源码剖析之 Master 容错处理流程

    点击上方蓝字关注 Apache DolphinScheduler Apache DolphinScheduler(incubating),简称"DS", 中文名 "海豚调 ...

随机推荐

  1. JVM虚拟机(三):Java内存区域

    运行时数据区   Java虚拟机再执行Java程序过程中会把它所管理的内存划分为若干个不同分工的数据区域. 程序计数器   程序计数器时一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示 ...

  2. hadoop_MapReduce_idea上打jar包,在虚拟机上运行

    打包前的介绍和准备工作 指定主类可以在运行jar包的时候不用输入要运行哪一个类,直接就可以运行了 指定主类 编辑jar 的信息 修改jar包的名称 build Complete!!! MapReduc ...

  3. Django使用channels实现Websocket连接

    简述: 需求:消息实时推送消息以及通知功能,采用django-channels来实现websocket进行实时通讯.并使用docker.daphne启动通道,保持websocket后台运行 介绍Dja ...

  4. Web服务器-并发服务器-单进程单线程非堵塞方式(3.4.3)

    @ 目录 1.分析 2.代码 关于作者 1.分析 当socket去监听的时候,是堵塞的状态 通过tcp_sever_socket.setblocking(False)去设置不堵塞 当socket发现没 ...

  5. centos7安装Hive及其问题解决

    本地如何安装hive (安装hive之前需要安装hadoop并启动hadoop的相关集群,mysql数据库) hadoop集群是两台,一台作为master,两台作为slaver,mysql单独占用一台 ...

  6. Linux下安装ffmpeg,视频格式转换

    下载ffmpeg 从ffmpeg官网:http://ffmpeg.org/download.html 下载最新的ffmpeg安装包,然后通过如下命令解压: 解压 ffmpeg-*.tar.bz2 文件 ...

  7. 10 个 GitHub 上超火的 CSS 奇技淫巧项目,找到写 CSS 的灵感!

    大家好,我是你们的 超级猫,一个不喜欢吃鱼.又不喜欢喵 的超级猫 ~ 如果 CSS 是女孩子,肯定如上图那样吧

  8. CentOS8更换国内YUM源

    rm -rf /etc/yum.repos.d/* wget -O /etc/yum.repos.d/CentOS-cnnic.repo https://feieryun.oss-cn-zhangji ...

  9. Android 安全研究 hook 神器frida学习(一)

    在进行安卓安全研究时,hook技术是不可或缺的,常用的有Xposed:Java层的HOOK框架,由于要修改Zgote进程,需要Root,体验过Xposed,整个过程还是很繁琐的,并且无法hook,na ...

  10. 认识ollydbg

    四个区域:汇编区:虚拟地址,机器码,汇编指令,注释: 寄存器区:寄存器,数据: 数据区, 栈. 这是上面按钮的作用 热键: Ctrl+F2 - 重启程序. Alt+F2 - 关闭被调试程序. F3 - ...