本地環境提交flink on yarn作業
在使用雲廠商提供的flink job管理平臺時,透過介面操作提交flink任務到yarn上十分方便,那麼開發除錯時能否在本地環境直接提交flink任務到yarn呢?
開源的flink管理平臺 streampark 有提交flink on yarn作業的程式碼實現,可以參照 streampark 裡對應模組的程式碼實現本地環境下的flink on yarn作業的提交。
其中 streampark-flink-client-core 作為提交flink job的核心模組,這裡我們只關心flink on yarn作業的提交。
flink job提交流程分析:
1、flink啟動指令碼
以flink 1.14.4版本為例,flink安裝目錄的bin/目錄下的 flink指令碼 有詳細的任務提交步驟,其中最後一行為:
# Add HADOOP_CLASSPATH to allow the usage of Hadoop file systems
exec "${JAVA_RUN}" $JVM_ARGS $FLINK_ENV_JAVA_OPTS "${log_setting[@]}" -classpath "`manglePathList "$CC_CLASSPATH:$INTERNAL_HADOOP_CLASSPATHS"`" org.apache.flink.client.cli.CliFrontend "$@"
可知,flink任務仍以java命令方式提交,程式入口為:org.apache.flink.client.cli.CliFrontend
2、啟動程式main方法
根據引數提交作業,CliFrontend#main方法執行流程如下:
// 1. find the configuration directory - 獲取配置檔案目錄
final String configurationDirectory = getConfigurationDirectoryFromEnv();
// 2. load the global configuration - 載入配置引數
final Configuration configuration = GlobalConfiguration.loadConfiguration(configurationDirectory);
// 3. load the custom command lines - 載入命令列引數
final List<CustomCommandLine> customCommandLines = loadCustomCommandLines(configuration, configurationDirectory);
// 4. 建立CliFrontend的物件並呼叫CliFrontend#parseAndRun方法
final CliFrontend cli = new CliFrontend(configuration, customCommandLines);
SecurityUtils.install(new SecurityConfiguration(cli.configuration));
retCode = SecurityUtils.getInstalledContext().runSecured(() -> cli.parseAndRun(args));
3、CliFrontend#parseAndRun方法
CliFrontend#parseAndRun方法主要程式碼如下:
// check for action
...
// get action
String action = args[0];
// remove action from parameters
final String[] params = Arrays.copyOfRange(args, 1, args.length);
try {
// do action
switch (action) {
case ACTION_RUN: // String ACTION_RUN = "run";
run(params);
return 0;
case ACTION_RUN_APPLICATION: // String ACTION_RUN_APPLICATION = "run-application";
runApplication(params);
return 0;
case ACTION_LIST: // String ACTION_INFO = "list";
list(params);
return 0;
case ACTION_INFO: // String ACTION_LIST = "info";
info(params);
return 0;
... 省略後續步驟
可以看到CliFrontend#parseAndRun方法透過獲取命令列的第一個引數,匹配並執行指定方法。
假設我們以run-application
命令啟動程式,則呼叫CliFrontend#runApplication方法,進入該方法。
4、CliFrontend#runApplication方法
直接展示關鍵程式碼,CliFrontend#runApplication方法透過傳入clusterClientServiceLoader
引數來建立一個ApplicationDeployer物件,然後呼叫該物件的ApplicationDeployer#run方法,執行完成結束呼叫。
final ApplicationDeployer deployer = new ApplicationClusterDeployer(clusterClientServiceLoader);
...
deployer.run(effectiveConfiguration, applicationConfiguration);
那麼application命令提交任務到yarn的具體實現就在這裡了,點選run方法並進入具體實現方法ApplicationClusterDeployer#run。
public <ClusterID> void run(
final Configuration configuration,
final ApplicationConfiguration applicationConfiguration)
throws Exception {
checkNotNull(configuration);
checkNotNull(applicationConfiguration);
LOG.info("Submitting application in 'Application Mode'.");
final ClusterClientFactory<ClusterID> clientFactory =
clientServiceLoader.getClusterClientFactory(configuration);
try (final ClusterDescriptor<ClusterID> clusterDescriptor =
clientFactory.createClusterDescriptor(configuration)) {
final ClusterSpecification clusterSpecification =
clientFactory.getClusterSpecification(configuration);
// 提交flink on yarn任務
clusterDescriptor.deployApplicationCluster(
clusterSpecification, applicationConfiguration);
}
}
程式碼實現流程:
- 透過
clientServiceLoader
生成ClusterClientFactory客戶端工廠; - 透過ClusterClientFactory物件建立 ClusterDescriptor 叢集描述符 及 ClusterSpecification 叢集規格物件;
- ClusterDescriptor#deployApplicationCluster為提交任務方法,跳轉到具體實現方法YarnClusterDescriptor#deployApplicationCluster;
- 進入後我們看到deployApplicationCluster方法及下面的deployJobCluster方法,二者都呼叫了YarnClusterDescriptor#deployInternal方法,以完成flink on yran任務提交;
- 透過引數描述也可以看出deployApplicationCluster對應的是application提交模式,deployJobCluster對應的是per-job提交模式;
總結:透過對run方法的梳理,可以確定step2是我們提交任務所需要建立的物件,YarnClusterDescriptor#deployInternal方法是實現提交需要呼叫方法;
5、YarnClusterDescriptor#deployInternal方法
進入該方法,前面是一些引數校檢及認證操作,然後透過 yarnClient 建立一個YarnClientApplication(這裡的yarnClient在哪裡生成?先不管,後面再看),後面進入startAppMaster方法,傳入flinkConfiguration、yarnClient、yarnApplication等引數,這裡應該會進行yarn任務的提交。
// Create application via yarnClient
final YarnClientApplication yarnApplication = yarnClient.createApplication();
...
ApplicationReport report =
startAppMaster(
flinkConfiguration,
applicationName,
yarnClusterEntrypoint,
jobGraph,
yarnClient,
yarnApplication,
validClusterSpecification);
進入YarnClusterDescriptor#startAppMaster方法,方法實現較長,這裡模糊搜尋'submit'關鍵詞,定位到 yarnClient.submitApplication 方法執行任務的提交,就此flink on yarn任務正式開始提交到叢集中。
LOG.info("Submitting application master " + appId);
yarnClient.submitApplication(appContext);
6、yarnClient物件在哪裡生成?
梳理提交流程後,已知入口程式CliFrontend#main方法會載入flink的FLINK_CONF_DIR配置檔案目錄並裝載配置引數,而我在FLINK_CONF_DIR目錄的配置檔案中並沒有找到對應的yarn引數配置,那麼flink如何和yarn建立起聯絡呢?
仍需要從ApplicationClusterDeployer#run方法中建立的物件入手:
-
clientServiceLoader是由DefaultClusterClientServiceLoader生成,使用SPI機制動態載入其ClusterClientFactory客戶端工廠;
-
clientFactory.createClusterDescriptor(configuration) 生成ClusterDescriptor叢集描述符物件,呼叫的方法為 YarnClusterClientFactory#createClusterDescriptor,方法程式碼如下:
public YarnClusterDescriptor createClusterDescriptor(Configuration configuration) { checkNotNull(configuration); // 讀取flink configuration的CONF_DIR獲取配置檔案目錄 final String configurationDirectory = configuration.get(DeploymentOptionsInternal.CONF_DIR); // 設定日誌相關引數,如未設定日誌引數,預設配置檔案目錄下的"log4j.properties"或"logback.xml"檔案路徑為該引數值 YarnLogConfigUtil.setLogConfigFileInConfig(configuration, configurationDirectory); // 獲取YarnClient return getClusterDescriptor(configuration); } private YarnClusterDescriptor getClusterDescriptor(Configuration configuration) { // 建立YarnClient物件 final YarnClient yarnClient = YarnClient.createYarnClient(); // 獲取yarn叢集配置 final YarnConfiguration yarnConfiguration = Utils.getYarnAndHadoopConfiguration(configuration); // 根據yarnConfiguration配置,初始化yarn客戶端並啟動建立連線 yarnClient.init(yarnConfiguration); yarnClient.start(); // 建立並返回ClusterDescriptor叢集描述符物件 return new YarnClusterDescriptor( configuration, yarnConfiguration, yarnClient, YarnClientYarnClusterInformationRetriever.create(yarnClient), false); }
點選進入Utils#getYarnAndHadoopConfiguration方法獲取yarn叢集配置,程式碼邏輯如下:
public static YarnConfiguration getYarnAndHadoopConfiguration( org.apache.flink.configuration.Configuration flinkConfig) { // 從flink configuration配置中獲取對應的yarn配置引數(如未設定相關yarn引數,則不新增) final YarnConfiguration yarnConfig = getYarnConfiguration(flinkConfig); // 獲取系統環境變數"HADOOP_HOME"、"HADOOP_CONF_DIR",讀取環境變數下的配置檔案目錄並新增到yarnConfig中 yarnConfig.addResource(HadoopUtils.getHadoopConfiguration(flinkConfig)); return yarnConfig; }
HadoopUtils#getHadoopConfiguration方法部分截圖:
由此確定flink與yarn叢集建立連線,需要在提交flink任務的系統上配置hadoop環境變數,這與安裝flink時需要配置環境變數完成與YARN叢集的對接操作描述一致。
java實現flink on yarn作業的提交
實現思路:
由上分析可知,提交flink job需要flink配置檔案、hadoop環境變數,在本地環境下需要在專案中新增 flink-conf.yaml 配置檔案,沒有配置hadoop環境變數的話,可以自行新增 core-site.xml、hdfs-site.xml、yarn-site.xml 配置檔案到專案指定路徑中並建立YarnClient物件,或手動配置引數建立YarnClient物件。
剩下的就是將 streampark 的streampark-flink-client-core模組下的flink on yarn提交任務程式碼提取出來,透過閱讀程式碼發現提交flink任務還需要flink-dist_*.jar
檔案,這是flink任務提交到yarn的前提條件之一。
實現流程:
建立自定義的任務提交客戶端,透過HdfsUtils將任務jar包及依賴lib/路徑上傳至指定hdfs檔案目錄中,呼叫提交客戶端的 doSubmit方法 提交任務到yarn叢集,doCancel方法 取消正在執行的flink任務。
Windows系統提交任務失敗的問題解決
由於windows系統和linux系統下是不同的路徑分隔符,導致windows下的本地環境提交flink on yarn作業失敗(windows下找不到主類:YarnJobClusterEntrypoint)。
需在專案下建立 org.apache.flink.yarn 包路徑,複製並修改 Utils 和 YarnClusterDescriptor 類檔案,在啟動時覆蓋原始碼類載入執行,可以解決windows下提交任務失敗的問題。
————————————————
[FLINK-17858] Yarn mode, windows and linux environment should be interlinked - ASF JIRA
專案倉庫地址:Cyanty/flink-job-publish-demo