深入学习Apache Flink提交流程的源码对于理解和优化Flink应用程序至关重要。源码阅读将揭示Flink运行时系统的内部工作原理,包括作业提交、调度、任务执行等关键流程。通过深入源码,你将更好地理解Flink的执行模型、任务调度策略和容错机制。这种深度理解有助于解决性能问题、调优应用程序,并在特定场景下优化资源利用率。此外,学习Flink提交流程的源码还有助于扩展Flink,定制化特定功能,以满足个性化需求。总的来说,源码学习是成为Flink高级用户和贡献者的关键一步,为构建高效、稳定的流处理应用奠定基础。本文Flink源码分析基于软件版本flink-1.16.0。

flink 本地启动任务没日志 flink 启动流程_命令行

  1. Flink源码分析让我们从flink per job启动脚本开始,如下:
./bin/flink run \
-t yarn-per-job \
--detached ./examples/streaming/WorldCount.jar

根据启动任务,首先会进入shell脚本./bin/flink中执行;

  1. 让我们跟踪调用过程:
......
# get flink config
. "$bin"/config.sh
......
# Add HADOOP_CLASSPATH to allow the usage of Hadoop file systems
exec "${JAVA_RUN}" ...... org.apache.flink.client.cli.CliFrontend "$@"
......

根据shell脚本内容可知,紧接着调用了org.apache.flink.client.cli.CliFrontend.main执行flink任务,同时将shell参数原样传递给Flink程序;

  1. 下面让我们一起看看org.apache.flink.client.cli.CliFrontend:
......
// 1. find the configuration directory,发现配置目录
final String configurationDirectory = getConfigurationDirectoryFromEnv();

// 2. load the global configuration,从flink-yaml.conf加载配置
final Configuration configuration = GlobalConfiguration.loadConfiguration(configurationDirectory);

// 3. load the custom command lines,加载命令行客户端:普通 yarn 默认 
final List<CustomCommandLine> customCommandLines = loadCustomCommandLines(configuration, configurationDirectory);
......
final CliFrontend cli = new CliFrontend(configuration, customCommandLines);

SecurityUtils.install(new SecurityConfiguration(cli.configuration));

// 运行flink程序
retCode = SecurityUtils.getInstalledContext().runSecured(() -> cli.parseAndRun(args));
......

在这里主要对flink配置参数进行封装,创建了命令行客户端对象,这里客户端对象创建了三个,分别是普通客户端、yarn客户端(这里需要说明的是yarn客户端实现类:org.apache.flink.yarn.cli.FlinkYarnSessionCli)和默认客户端。同时显示实时化了CliFrontend对象,并且通过cli.parseAndRun(args)提交运行;

  1. 下面让我们跟踪cli.parseAndRun(args)
......
// get action 即 run
String action = args[0];

// remove action from parameters 即 标识去除run后的参数数组
final String[] params = Arrays.copyOfRange(args, 1, args.length);
......
case ACTION_RUN:
    run(params); // 调用
    return 0;
......

因为我们跟踪的Flink Per Job提交模式,所以最终执行ACTION_RUN分支,即run(params);

  1. 继续跟踪run方法:
protected void run(String[] args) throws Exception {

    final Options commandOptions = CliFrontendParser.getRunCommandOptions();
    // 解析用户指定的命令行配置项
    final CommandLine commandLine = getCommandLine(commandOptions, args, true);
    
    // 校验并且获取到活跃的命令行(上边初始化了三个命令行客户端:Generic、Yarn、Default)
    // flink-per-job 对应的活跃对象是 GenericCLI
    final CustomCommandLine activeCommandLine = validateAndGetActiveCommandLine(checkNotNull(commandLine));
    
    // 获取jar包和依赖 
    final ProgramOptions programOptions = ProgramOptions.create(commandLine);
    final List<URL> jobJars = getJobJarAndDependencies(programOptions);
    
    // 重新封装成有效配置对象 
    final Configuration effectiveConfiguration = getEffectiveConfiguration(activeCommandLine, commandLine, programOptions, jobJars);

    /* 
        这里的program包含:
            String[] programArgs = runOptions.getProgramArgs(); // 参数
            String jarFilePath = runOptions.getJarFilePath(); // jar包路径 
            List<URL> classpaths = runOptions.getClasspaths(); // classpath
            String entryPointClass = runOptions.getEntryPointClassName(); // 入口类 
            
        这其实只的就是用户编写应用程序对应的基础信息 
    */
    try (PackagedProgram program = getPackagedProgram(programOptions, effectiveConfiguration)) {
        // 执行程序,入参:活跃客户端的有效配置和封装的用户程序对象
        executeProgram(effectiveConfiguration, program);
    }
}
  1. 下面调用 executeProgram(effectiveConfiguration, program); 执行应用程序
ClientUtils.executeProgram(new DefaultExecutorServiceLoader(), configuration, program, false, false);

继续点击跟踪代码:

public static void executeProgram(
        PipelineExecutorServiceLoader executorServiceLoader,
        Configuration configuration,
        PackagedProgram program,
        boolean enforceSingleJobExecution,
        boolean suppressSysout
) throws ProgramInvocationException {

    final ClassLoader userCodeClassLoader = program.getUserCodeClassLoader();
    final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
    try {
        Thread.currentThread().setContextClassLoader(userCodeClassLoader);

        // 初始化用户执行环境 StreamExecutionEnvironment
        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);
    }
}
  1. 继续跟踪program.invokeInteractiveModeForExecution();
public void invokeInteractiveModeForExecution() throws ProgramInvocationException {
    FlinkSecurityManager.monitorUserSystemExitForCurrentThread();
    try {
        // 调用程序 
        // mainClass:用户主方法 
        // args: 执行参数
        callMainMethod(mainClass, args);
    } finally {
        FlinkSecurityManager.unmonitorUserSystemExitForCurrentThread();
    }
}
  1. 下面进入方法callMainMethod,主要功能代码如下:
......
mainMethod = entryClass.getMethod("main", String[].class);
......
mainMethod.invoke(null, (Object) args);
......

这里主要通过反射方式调用执行用户代码程序,这个方法调用成功后就进入到了我们自己编写程序的main方法,即WordCount.main()方法