Flink源码剖析:Jar包任务提交流程
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包任务提交流程的更多相关文章
- 转】MyEclipse使用总结——使用MyEclipse打包带源码的jar包
原博文出自于: http://www.cnblogs.com/xdp-gacl/p/4136303.html 感谢! 平时开发中,我们喜欢将一些类打包成jar包,然后在别的项目中继续使用,不过由于看不 ...
- MyEclipse使用总结——使用MyEclipse打包带源码的jar包
平时开发中,我们喜欢将一些类打包成jar包,然后在别的项目中继续使用,不过由于看不到jar包里面的类的源码了,所以也就无法调试,要想调试,那么就只能通过关联源代码的形式,这样或多或少也有一些不方便,今 ...
- eclipse导出附带源码的jar包
最近在搞Andengine游戏开发,发现andengine的jar包可以直接点击查看源码,而其他项目的jar包却看不了,因此自己研究了下如何生成可以直接查看源码的jar包. 1.eclipse中点击项 ...
- MyEclipse打包带源码的jar包
平时开发中,我们喜欢将一些类打包成jar包,然后在别的项目中继续使用,不过由于看不到jar包里面的类的源码了,所以也就无法调试,要想调试,那么就只能通过关联源代码的形式,这样或多或少也有一些不方便,今 ...
- Eclipse使用总结——使用Eclipse打包带源码的jar包
平时开发中,我们喜欢将一些类打包成jar包,然后在别的项目中继续使用,不过由于看不到jar包里面的类的源码了,所以也就无法调试,要想调试,那么就只能通过关联源代码的形式,这样或多或少也有一些不方便,今 ...
- Flink 源码解析 —— Standalone Session Cluster 启动流程深度分析之 Job Manager 启动
Job Manager 启动 https://t.zsxq.com/AurR3rN 博客 1.Flink 从0到1学习 -- Apache Flink 介绍 2.Flink 从0到1学习 -- Mac ...
- Flink 源码解析 —— Standalone session 模式启动流程
Standalone session 模式启动流程 https://t.zsxq.com/EemAEIi 博客 1.Flink 从0到1学习 -- Apache Flink 介绍 2.Flink 从0 ...
- Flink 源码解析 —— Standalone Session Cluster 启动流程深度分析之 Task Manager 启动
Task Manager 启动 https://t.zsxq.com/qjEUFau 博客 1.Flink 从0到1学习 -- Apache Flink 介绍 2.Flink 从0到1学习 -- Ma ...
- Apache DolphinScheduler 源码剖析之 Worker 容错处理流程
今天给大家带来的分享是 Apache DolphinScheduler 源码剖析之 Worker 容错处理流程 DolphinScheduler源码剖析之Worker容错处理流程 Worker容错流程 ...
- DolphinScheduler 源码剖析之 Master 容错处理流程
点击上方蓝字关注 Apache DolphinScheduler Apache DolphinScheduler(incubating),简称"DS", 中文名 "海豚调 ...
随机推荐
- modelViewSet 获取未序列化的数据
1 views/ perform_create 方法的使用 获取未序列化的数据 def perform_create(self, serializer): user_obj=serializer. ...
- 马赛克密码破解——GitHub 热点速览 Vol.50
作者:HelloGitHub-小鱼干 "xx"(爆粗口) 这个词是最能体现本人看到本周 GitHub 热点的心情的.那一天,看到用图片处理技术还原马赛克密码的 Depix 便惊为天 ...
- druid数据源yml配置
application.yml配置 spring: datasource: username: root password: 123456 url: jdbc:mysql://localhost:33 ...
- AdaBoost 算法-分析波士顿房价数据集
公号:码农充电站pro 主页:https://codeshellme.github.io 在机器学习算法中,有一种算法叫做集成算法,AdaBoost 算法是集成算法的一种.我们先来看下什么是集成算法. ...
- Python读写EXCEL文件常用方法大全
前言 python读写excel的方式有很多,不同的模块在读写的讲法上稍有区别,这里我主要介绍几个常用的方式. 用xlrd和xlwt进行excel读写: 用openpyxl进行excel读写: 用pa ...
- css3选择器归类整理---基本选择器和属性选择器
css3选择器分类 CSS3选择器分类如下图所示 选择器的语法 1.基本选择器 类型 代码 功能描述 通配选择器 *{ margin: 0; padding: 0; border: none; } 选 ...
- 探究 | App Startup真的能减少启动耗时吗
前言 之前我们说了启动优化的一些常用方法,但是有的小伙伴就很不屑了: "这些方法很久之前就知道了,不知道说点新东西?比如App Startup?能对启动优化有帮助吗?" ok,既然 ...
- C#中的深度学习(一):使用OpenCV识别硬币
在本系列文章中,我们将使用深度神经网络(DNN)来执行硬币识别.具体来说,我们将训练一个DNN识别图像中的硬币. 在本文中,我们将描述一个OpenCV应用程序,它将检测图像中的硬币.硬币检测是硬币完整 ...
- OSM地图本地发布-如何生成各省市矢量地图
目录 1.缘起 2.问题 3.分析 4.生成自定义地区矢量瓦片 4.1.启动docker 4.2.启动postGIS容器 4.3.设置不清理上次的结果 4.4.删除默认切图范围 4.5.修改切图层级和 ...
- SQLServer访问WebServices提示:SQL Server 阻止了对组件 'Ole Automation Procedures' 的 过程'sys.sp_OACreate' 的访问
问题描述 在数据库中调用webservices, 提示:SQLServer访问WebServices提示:SQL Server 阻止了对组件 'Ole Automation Procedures' 的 ...