Flink是Apache软件基金会下开源的分布式流批一体计算框架,具备实时流计算和高吞吐批处理计算的大数据计算能力。本专栏内容为Flink源码解析的记录与分享。
本文解析的Flink源码版本为:flink-1.19.0
1.Flink CliFrontend功能概述
CliFrontend是Flink的客户端,负责进行Transformation、StreamGraph、JobGraph生成,并最终将JobGraph、Jar包及依赖向Yarn集群提交。
本文解析的Flink源码为Flink CliFrontend客户端的启动(内容为下流程图的红色部分)。Flink CliFrontend启动内容主要是用户通过bin/flink run命令启动Flink的CliFrontend客户端Java程序,CliFrontend客户端解析提交参数、生成配置与上下文环境、执行Flink的主程序。
Flink on Yarn的计算调度与任务执行完整流程:

具体步骤如下:
1.执行bin/flink run命令,启动Flink程序。
2.flink run命令通过java命令启动org.apache.flink.client.cli.CliFrontend的main()方法。
3.CliFrontend的main()方法创建并启动CliFrontend实例。
4.启动PackagedProgram。
5.PackagedProgram通过反射执行Flink主类的Main()主程序。
完整代码解析:

2.Flink启动命令
Flink程序启动开始于在操作系统执行Flink的bin/flink run命令,启动Flink CliFrontend客户端,并开始进行计算调度及向Yarn集群提交Flink任务。
源码图解:

bin/flink run命令在启动时需配置Flink App相关参数,需指定Flink程序在Yarn上的部署模式(yarn-per-job/yarn-session/yarn-application ),配置Flink中JobManager与TaskManager的内存,配置计算资源与程序并行度,配置程序主类、Jar包、依赖和程序输入参数。
Flink run提交命令样例:
bin/flink run \
-t yarn-per-job/yarn-session/yarn-application \ # 部署模式(作业模式)
-m yarn-cluster \ # 指定 JobManager 地址
-yqu <yarn队列名> \ # YARN 队列名称(如 default)
-p <并行度> \ # 作业并行度(默认 1)
-yjm <JobManager内存> \ # JobManager 内存(如 1024m)
-ys <每个TM的Slot数> \ # 每个 TaskManager 的 Slot 数(默认 1)
-ytm <TaskManager内存> \ # TaskManager 内存(如 2048m)
/path/to/job.jar \ # 作业 JAR 路径
com.example.MainClass # 主类名(可选,若 JAR 中已定义则可省略)
[程序参数] # 传递给主类的参数(若有)
flink run命令会通过"$JAVA_HOME"/bin/java命令启动org.apache.flink.client.cli.CliFrontend。
bin/flink-命令源码:
# 通过Java命令启动CliFrontend客户端
exec $JAVA_RUN $JVM_ARGS "${log_setting[@]}" -classpath "`manglePathList "$CC_CLASSPATH:$INTERNAL_HADOOP_CLASSPATHS"`" org.apache.flink.client.cli.CliFrontend "$@"
注:其中JAVA_RUN为JAVA_RUN="$JAVA_HOME"/bin/java,定义在bin/config.sh中
3.CliFrontend主方法
flink run执行java命令后,进入Flink java源码中org.apache.flink.client.cli.CliFrontend的main()方法。
源码图解:

在CliFrontend.main()方法中,Flink继续调用CliFrontend.mainInternal()方法进行CliFrontend启动。
CliFrontend.main()方法源码:
/** Submits the job based on the arguments. */
public static void main(final String[] args) {
int retCode = INITIAL_RET_CODE;
try {
//进入mainInternal()方法
retCode = mainInternal(args);
} finally {
System.exit(retCode);
}
}
CliFrontend.mainInternal()方法主要是进行了CliFrontend启动准备和实例化的操作,读取了Flink的默认配置、命令行输出参数、安全配置、动态选项,创建了ClientFrontend实例,并执行ClientFrontend实例的parseAndRun()方法启动并运行ClientFrontend实例。
CliFrontend.mainInternal()方法源码:
static int mainInternal(final String[] args) {
EnvironmentInformation.logEnvironmentInfo(LOG, "Command Line Client", args);
//1.读取配置及默认命令行参数
final String configurationDirectory = getConfigurationDirectoryFromEnv();
final Configuration configuration =
GlobalConfiguration.loadConfiguration(configurationDirectory);
final List<CustomCommandLine> customCommandLines =
loadCustomCommandLines(configuration, configurationDirectory);
//...
//2.新建CliFrontend实例
final CliFrontend cli = new CliFrontend(configuration, customCommandLines);
//3.读取命令行提交参数
CommandLine commandLine =
cli.getCommandLine(
new Options(),
Arrays.copyOfRange(args, min(args.length, 1), args.length),
true);
//4.获取安全配置、动态选项参数
Configuration securityConfig = new Configuration(cli.configuration);
DynamicPropertiesUtil.encodeDynamicProperties(commandLine, securityConfig);
SecurityUtils.install(new SecurityConfiguration(securityConfig));
//5.调用parseAndRun()进行任务提交
etCode = SecurityUtils.getInstalledContext().runSecured(
() -> cli.parseAndRun(args));
//...
return retCode;
}
4.CliFrontend实例启动
CliFrontend的mainInternal()方法创建了CliFrontend实例,并调用CliFrontend实例的parseAndRun()方法启动CliFrontend实例。
源码图解:

CliFrontend的parseAndRun()方法有多个分支,定义了CliFrontend对于各类状态的操作。启动时,CliFrontend会进入ACTION_RUN分支,调用了CliFrontend.run()方法继续进行CliFrontend启动操作。
CliFrontend.parseAndRun()方法源码:
public int parseAndRun(String[] args) {
//...
//根据不同分支进行不同操作
switch (action) {
case ACTION_RUN:
//进入"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 ACTION_CHECKPOINT:
checkpoint(params);
return 0;
case "-h":
case "--help":
CliFrontendParser.printHelp(customCommandLines);
return 0;
case "-v":
case "--version":
String version = EnvironmentInformation.getVersion();
String commitID = EnvironmentInformation.getRevisionInformation().commitId;
System.out.print("Version: " + version);
System.out.println(
commitID.equals(EnvironmentInformation.UNKNOWN)
? ""
: ", Commit ID: " + commitID);
return 0;
default:
System.out.printf("\"%s\" is not a valid action.\n", action);
System.out.println();
System.out.println(
"Valid actions are \"run\", \"run-application\", \"list\", \"info\", \"savepoint\", \"stop\", or \"cancel\".");
System.out.println();
System.out.println(
"Specify the version option (-v or --version) to print Flink version.");
System.out.println();
System.out.println(
"Specify the help option (-h or --help) to get help on the command.");
return 1;
//...
}
CliFrontend的run()方法主要获取了flink run命令的部署模式、提交参数、程序运行参数、jar包与url,创建用于执行Flink主程序的main()方法的PackagedProgram实例。
CliFrontend.run()方法源码:
protected void run(String[] args) throws Exception {
LOG.info("Running 'run' command.");
//获取"run"操作
final Options commandOptions = CliFrontendParser.getRunCommandOptions();
//获取命令行提交的"-c -p -t"参数
final CommandLine commandLine = getCommandLine(commandOptions, args, true);
//...
//判断active的提交方式是:Generic、Yarn、Default
final CustomCommandLine activeCommandLine =
validateAndGetActiveCommandLine(checkNotNull(commandLine));
//根据命令行提交创建程序运行参数:jarFilePath、classpaths、programArgs、parallelism、detachedMode等
final ProgramOptions programOptions = ProgramOptions.create(commandLine);
//获取jar包和依赖的url
final List<URL> jobJars = getJobJarAndDependencies(programOptions);
//用effectiveConfiguration封装各提交参数activeCommandLine, commandLine, programOptions, jobJars
final Configuration effectiveConfiguration =
getEffectiveConfiguration(activeCommandLine, commandLine, programOptions, jobJars);
LOG.debug("Effective executor configuration: {}", effectiveConfiguration);
//创建PackagedProgram实例
try (PackagedProgram program = getPackagedProgram(programOptions, effectiveConfiguration)) {
//执行flink程序
executeProgram(effectiveConfiguration, program);
}
}
5.PackagedProgram执行Flink主程序
CliFrontend创建PackagedProgram实例后,会通过PackagedProgram执行Flink主程序的main()方法。
源码图解:

CliFrontend通过getPackagedProgram()和buildProgram()方法创建了用于执行Flink主方法的PackagedProgram实例。
CliFrontend.getPackagedProgram()方法源码:
private PackagedProgram getPackagedProgram(
//...
//继续调用buildProgram()方法
program = buildProgram(programOptions, effectiveConfiguration);
//...
return program;
}
CliFrontend.buildProgram()方法源码:
PackagedProgram buildProgram(final ProgramOptions runOptions, final Configuration configuration)
throws FileNotFoundException, ProgramInvocationException, CliArgsException {
runOptions.validate();
//从提交命令中提取提交参数、jar、主类、classpaths
String[] programArgs = runOptions.getProgramArgs();
String jarFilePath = runOptions.getJarFilePath();
List<URL> classpaths = runOptions.getClasspaths();
String entryPointClass = runOptions.getEntryPointClassName();
File jarFile = jarFilePath != null ? getJarFile(jarFilePath) : null;
//创建PackagedProgram,并配置其Jar/classpaths/entryPointClass/configuration等参数
return PackagedProgram.newBuilder()
.setJarFile(jarFile)
.setUserClassPaths(classpaths)
.setEntryPointClassName(entryPointClass)
.setConfiguration(configuration)
.setSavepointRestoreSettings(runOptions.getSavepointRestoreSettings())
.setArguments(programArgs)
.build();
}
CliFrontend进入executeProgram()方法通过ClientUtils激活PackagedProgram实例。
CliFrontend.executeProgram()方法源码:
protected void executeProgram(final Configuration configuration, final PackagedProgram program)
throws ProgramInvocationException {
//继续调用ClientUtils.executeProgram()
ClientUtils.executeProgram(
new DefaultExecutorServiceLoader(), configuration, program, false, false);
}
ClientUtils配置类加载器和运行上下文环境,通过激活PackagedProgram执行Flink主程序。
ClientUtils.executeProgram()方法源码:
public static void executeProgram(
PipelineExecutorServiceLoader executorServiceLoader,
Configuration configuration,
PackagedProgram program,
boolean enforceSingleJobExecution,
boolean suppressSysout)
throws ProgramInvocationException {
checkNotNull(executorServiceLoader);
//获取并配置类加载器
final ClassLoader userCodeClassLoader = program.getUserCodeClassLoader();
final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(userCodeClassLoader);
LOG.info(
"Starting program (detached: {})",
!configuration.get(DeploymentOptions.ATTACHED));
//配置运行的上下文环境
ContextEnvironment.setAsContext(
executorServiceLoader,
configuration,
userCodeClassLoader,
enforceSingleJobExecution,
suppressSysout);
StreamContextEnvironment.setAsContext(
executorServiceLoader,
configuration,
userCodeClassLoader,
enforceSingleJobExecution,
suppressSysout);
try {
//激活程序的交互模式
program.invokeInteractiveModeForExecution();
} finally {
//运行结束则重置运行的上下文环境
ContextEnvironment.unsetAsContext();
StreamContextEnvironment.unsetAsContext();
}
} finally {
//配置当前线程的上下文类加载器
Thread.currentThread().setContextClassLoader(contextClassLoader);
}
}
PackagedProgram的invokeInteractiveModeForExecution()方法继续调用了callMainMethod()方法。
PackagedProgram.invokeInteractiveModeForExecution()方法源码:
public void invokeInteractiveModeForExecution() throws ProgramInvocationException {
FlinkSecurityManager.monitorUserSystemExitForCurrentThread();
try {
//继续执行
callMainMethod(mainClass, args);
} finally {
FlinkSecurityManager.unmonitorUserSystemExitForCurrentThread();
}
}
PackagedProgram.callMainMethod()方法找到Flink主程序的main()方法,通过反射执行主程序的main()方法启动Flink主程序。
PackagedProgram.callMainMethod()方法源码:
private static void callMainMethod(Class<?> entryClass, String[] args)
throws ProgramInvocationException {
Method mainMethod;
//...
//找到Flink主程序的main()方法
mainMethod = entryClass.getMethod("main", String[].class);
//...
//用反射执行jar包Flink程序的main方法
mainMethod.invoke(null, (Object) args);
//...
}
6.结语
当Flink主程序被执行后,将进行DataStream向Transformation转换,并执行StreamExecutionEnvironment.execute()方法开始进入Flink计算调度与任务执行的过程。下篇博文《Flink-1.19.0源码详解3-Flink DataStream数据流转换为Transformation集合源码解析》将继续解析Flink主程序被执行后DataStream向Transformation转换的源码。
849

被折叠的 条评论
为什么被折叠?



