flink on yarn 模式支持兩種部署方式:
1. 多作業但集羣
2. 單作業但集羣
本文主要介紹單作業單集羣下作業提交流程:
核心組件:
Job CLI: 即flink run,非 detatched 模式下的客戶端進程,用以獲取 yarn Application Master 的運行狀態並將日誌輸出掉終端
Job Manager[JM]: 負責作業的運行計劃ExecutionGraph的生成,物理計劃生成和作業調度
TaskManager[TM]:負責被分發 task 的執行、心跳/狀態上報、資源管理
啓動方式:
啓動Flink Yarn Session有2種模式:分離模式、客戶端模式
通過-d指定分離模式,即客戶端在啓動Flink Yarn Session後,就不再屬於Yarn Cluster的一部分。如果想要停止Flink Yarn Application,需要通過yarn application -kill 命令來停止。
作業提交整體流程:
提交流程詳細分析:
啓動命令:
./bin/flink run -m yarn-cluster -yn 2 -j flink-demo-1.0.0-with-dependencies.jar —ytm 1024 -yst 4 -yjm 1024 —yarnname
flink 在收到這樣一條命令後會首先通過 Cli 獲取 flink 的配置,並解析命令行參數。
程序入口
CliFrontend.java 是 flink 提交作業的入口
解析參數,並路由到 run方法
解析參數,構建program, 獲取當前集羣模式入口即:FlinkYarnSessionCli 和 DefaultCli
其中FlinkYarnSessionCli是flink yarn session 模式,DefaultCli爲standalone模式
flink集羣構建:
flink 通過 兩個不同的 CustomCommandLine 來實現不同集羣模式的解析,分別是 FlinkYarnSessionCli和 DefaultCLI 解析命令行參數:
final CustomCommandLine<?> customCommandLine = getActiveCustomCommandLine(commandLine);
那麼什麼時候解析成 Yarn Cluster 什麼時候解析成 Standalone 呢?由於FlinkYarnSessionCli被優先添加到customCommandLine,所以會先觸發下面這段邏輯:
public boolean isActive(CommandLine commandLine) {
String jobManagerOption = commandLine.getOptionValue(addressOption.getOpt(), null);
boolean yarnJobManager = ID.equals(jobManagerOption);
boolean yarnAppId = commandLine.hasOption(applicationId.getOpt());
return yarnJobManager || yarnAppId || (isYarnPropertiesFileMode(commandLine) && yarnApplicationIdFromYarnProperties != null);
}
從上面可以看出如果用戶傳入了 -m參數或者application id或者配置了yarn properties 文件,則啓動yarn cluster模式,否則是Standalone模式的集羣
最後獲取YarnClusterDescriptor,YarnClusterDescriptor來描述yarn集羣的部署配置,具體對應的配置文件爲flink-conf.yaml
flink集羣部署:
flink通過YarnClusterDescriptor來描述yarn集羣的部署配置,具體對應的配置文件爲flink-conf.yaml。
獲取YarnClusterDescriptor
初始化JobGraph:
部署集羣:
大致過程:
-
check yarn 集羣隊列資源是否滿足請求
-
設置 AM Context、啓動命令、submission context
-
通過 yarn client submit am context
-
將yarn client 及相關配置封裝成 YarnClusterClient 返回
真正在 AM 中運行的主類是 YarnApplicationMasterRunner,它的 run方法做了如下工作:
-
啓動JobManager ActorSystem
-
啓動 flink ui
-
啓動YarnFlinkResourceManager來負責與yarn的ResourceManager交互,管理yarn資源
-
啓動 actor System supervise 進程
到這裏 JobManager 已經啓動起來
這樣一個 flink 集羣便構建出來了。下面附圖解釋下這個流程:
-
flink cli 解析本地環境配置,啓動 ApplicationMaster
-
在 ApplicationMaster 中啓動 JobManager
-
在 ApplicationMaster 中啓動YarnFlinkResourceManager
-
YarnFlinkResourceManager給JobManager發送註冊信息
-
YarnFlinkResourceManager註冊成功後,JobManager給YarnFlinkResourceManager發送註冊成功信息
-
YarnFlinkResourceManage知道自己註冊成功後像ResourceManager申請和TaskManager數量對等的 container
-
在container中啓動TaskManager
-
TaskManager將自己註冊到JobManager中
flink作業提交執行:
作業的執行其實只是調用了用戶jar包的主函數,真正的觸發生成過程由用戶代碼的執行來完成。
獲取Environment
val env = StreamExecutionEnvironment.getExecutionEnvironment,這段代碼會獲取客戶端的環境配置,它首先會轉到這樣一段邏輯:
//StreamExecutionEnvironment 1256
public static StreamExecutionEnvironment getExecutionEnvironment() {
if (contextEnvironmentFactory != null) {
return contextEnvironmentFactory.createExecutionEnvironment();
}
// because the streaming project depends on "flink-clients" (and not the other way around)
// we currently need to intercept the data set environment and create a dependent stream env.
// this should be fixed once we rework the project dependencies
ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
ExecutionEnvironment.getExecutionEnvironment();獲取環境的邏輯如下:
//ExecutionEnvironment line1137
public static ExecutionEnvironment getExecutionEnvironment() {
return contextEnvironmentFactory == null ?
createLocalEnvironment() : contextEnvironmentFactory.createExecutionEnvironment();
}
這裏的contextEnvironmentFactory是一個靜態成員,早在ContextEnvironment.setAsContext(factory)已經觸發過初始化了,其中包含了如下的環境信息:
//ContextEnvironmentFactory line51
public ContextEnvironmentFactory(ClusterClient client, List<URL> jarFilesToAttach,
List<URL> classpathsToAttach, ClassLoader userCodeClassLoader, int defaultParallelism,
boolean isDetached, String savepointPath)
{
this.client = client;
this.jarFilesToAttach = jarFilesToAttach;
this.classpathsToAttach = classpathsToAttach;
this.userCodeClassLoader = userCodeClassLoader;
this.defaultParallelism = defaultParallelism;
this.isDetached = isDetached;
this.savepointPath = savepointPath;
}
其中的 client 就是上面生成的 YarnClusterClient,
用戶在執行val env = StreamExecutionEnvironment.getExecutionEnvironment這樣一段邏輯後會得到一個StreamContextEnvironment,其中封裝了 streaming 的一些執行配置 【buffer time out等】,另外保存了上面提到的 ContextEnvironment 的引用。
到這裏關於 streaming 需要的執行環境信息已經設置完成。
StreamGraph 的生成:
接下來用戶代碼執行到DataStream<String> stream = env.addSource(consumer);這段邏輯實際會生成一個DataStream抽象,DataStream是flink關於streaming抽象的最核心抽象,後續所有的算子轉換都會在DataStream上來完成,上面的addSource操作會觸發下面這段邏輯:
public <OUT> DataStreamSource<OUT> addSource(SourceFunction<OUT> function, String sourceName, TypeInformation<OUT> typeInfo) {
if (typeInfo == null) {
if (function instanceof ResultTypeQueryable) {
typeInfo = ((ResultTypeQueryable<OUT>) function).getProducedType();
} else {
try {
typeInfo = TypeExtractor.createTypeInfo(
SourceFunction.class,
function.getClass(), 0, null, null);
} catch (final InvalidTypesException e) {
typeInfo = (TypeInformation<OUT>) new MissingTypeInfo(sourceName, e);
}
}
}
boolean isParallel = function instanceof ParallelSourceFunction;
clean(function);
StreamSource<OUT, ?> sourceOperator;
if (function instanceof StoppableFunction) {
sourceOperator = new StoppableStreamSource<>(cast2StoppableSourceFunction(function));
} else {
sourceOperator = new StreamSource<>(function);
}
return new DataStreamSource<>(this, typeInfo, sourceOperator, isParallel, sourceName);
}
簡要總結下上面的邏輯:
-
獲取數據源 source 的 output 信息 TypeInformation
-
生成 StreamSource sourceOperator
-
生成 DataStreamSource【封裝了 sourceOperator】,並返回
-
將 StreamTransformation 添加到算子列表 transformations 中【只有 轉換 transform 操作纔會添加算子,其它都只是暫時做了 transformation 的疊加封裝】
-
後續會在 DataStream 上做操作