Flink-1.19.0源码详解2-Flink CliFrontend客户端启动源码解析

        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转换的源码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值