0. 背景
- 本文基于Flink 1.12;
- 本文将沿着从程序开发到任务执行这条主线涉及到的核心类做简要解析;
1. 代码编写阶段
1.1. DataStream
DataStream描述的是具有相同类型的数据流,其提供了多种对流数据进行转换的接口。算子的底层是通过具体的Transformation实现的。
1.2. TypeInformation
Flink使用TypeInformation去统一的描述用户方法输入、输出的所有类型。TypeInformation提供了生成序列化器、比较器功能,与此同时,也提供了语意检查的功能。
在TypeInformation将一个type的字段以flat schema展开时,不是type中的每个字段都能在schema中以独立的字段存在,通常,某一个类型会以一个字段的形式存在。但值得注意的是,schema中会拥有该type的所有实例,所以,类似list、array中的所有元素会以一个字段的形式存在。在映射过程会遵循以下规则:
- 基本的类型不会被分割,会被认为是单个字段;
- arrays和集合类是一个字段;
- Tuple和 case class的字段和其类拥有的字段相同。
为了理解TypeInformation,可以将TypeInformation接口中主要的方法和其实现的类结合起来看,比如:StringDataTypeInfo,可以比较清晰的理解。
1.3. Transformation
Transformation描述了创建数据流的操作,以及该操作的并行度、输出数据类型等信息。但一个Transformation不一定对应着运行时的physical operator,因为有些算子仅仅是逻辑上的概念,如Rebalance。
2. Flink的执行模型
2.1. 执行模型过程
Flink 中的执行图可以分成四层:StreamGraph ---> JobGraph ---> ExecutionGraph -> 物理执行图。
- StreamGraph
StreamGraph代表了程序的拓扑图,是根据stream API生成的最初的图,该拓扑图包含了构建JobGraph所有必须信息。 - JobGraph
JobGraph代表了Flink数据流程序,其实通过优化StreamGraph得到的,优化的点:主要将多个符合条件的节点 chain 在一起作为一个节点。好处是:可以减少数据在节点之间流动所需要的序列化/反序列化/传输消耗。 - ExecutionGraph
JobManager 根据 JobGraph 生成ExecutionGraph。ExecutionGraph是JobGraph的并行化版本,是调度层最核心的数据结构。 - 物理执行图
JobManager 根据 ExecutionGraph 对 Job 进行调度后,在各个TaskManager 上部署 Task 后形成的“图”,并不是一个具体的数据结构
2.2. 关键类与过程
2.2.1. 生成StreamGraph中关键类
- StreamNode
StreamNode用来描述流式程序中的operator,并拥有operator所有属性,如operator名字,出、入边信息已经slotSharingGroup,算子的调用类等信息。 - StreamEdge
StreamEdge用来描述 StreamNode(operator) 逻辑的链接边。其关键变量有sourceId、targetId、outputPartitioner等。 - streamNode的构建过程
以org.apache.flink.streaming.examples.socket.SocketWindowWordCount为例,生成StreamGraph的过程在StreamGraphGenerator#generate
方法中。
StreamGraphGenerator#generate //会先生成一个没有operator的StreamGraph
|
|--transform
| //当是常见的Transformation时,会调用translate方法
|----translate
| //针对流式计算,
SimpleTransformationTranslator#translateForStreaming
| //会在该过程中构建StreamNode
StreamGraph#addOperator
至于StreamEdge的构建过程,也是在对应具体的translate中实现的。
SimpleTransformationTranslator#translateForStreaming
|
| //可以以其具体实现OneInputTransformationTranslator跟进去分析
|--translateForStreamingInternal
2.2.2. 生成JobGraph中关键类
JobGraph生成的流程如下:
StreamExecutionEnvironment#execute
|
|--executeAsync
//其核心代码是
CompletableFuture<JobClient> jobClientFuture =
executorFactory
.getExecutor(configuration)
.execute(streamGraph, configuration, userClassloader);
JobGraph的生成主要方法是PipelineExecutorUtils#getJobGraph
,具体实现如下:
public static JobGraph getJobGraph(
@Nonnull final Pipeline pipeline, @Nonnull final Configuration configuration)
throws MalformedURLException {
checkNotNull(pipeline);
checkNotNull(configuration);
final ExecutionConfigAccessor executionConfigAccessor =
ExecutionConfigAccessor.fromConfiguration(configuration);
final JobGraph jobGraph =
FlinkPipelineTranslationUtil.getJobGraph(
pipeline, configuration, executionConfigAccessor.getParallelism());
configuration
.getOptional(PipelineOptionsInternal.PIPELINE_FIXED_JOB_ID)
.ifPresent(strJobID -> jobGraph.setJobID(JobID.fromHexString(strJobID)));
jobGraph.addJars(executionConfigAccessor.getJars());
jobGraph.setClasspaths(executionConfigAccessor.getClasspaths());
jobGraph.setSavepointRestoreSettings(executionConfigAccessor.getSavepointRestoreSettings());
return jobGraph;
}
2.2.3. JobGraph 生成ExecutionGraph
ExecutionGraph是在JobMaster初始化过程中生成的,具体过程如下:
JobMaster#JobMaster //构造函数
|
|-- createScheduler
|
DefaultSchedulerFactory#createInstance
|
SchedulerBase#SchedulerBase
|
|--createAndRestoreExecutionGraph
|----createExecutionGraph
|
ExecutionGraphBuilder#buildGraph
其核心代码如下:
// ExecutionGraphBuilder#buildGraph
// .........
// topologically sort the job vertices and attach the graph to the existing one
// 递归遍历,形成从source到sink的有序list
List<JobVertex> sortedTopology = jobGraph.getVerticesSortedTopologicallyFromSources();
// 可以进入看看释放方式。
executionGraph.attachJobGraph(sortedTopology);
// .........
3. 执行涉及类
3.1. Task类
Task是Flink中执行的基本单元,代表TaskManager中一个并行的subTask,Task封装Flink中的算子并执行算子,提供了必要的服务,如消费数据、输出结果以及和JobMaster交互。其中,Flink算子是AbstractInvokable的子类,仅具备数据的读、写以及事件的回调。Task本身是不知道如何与Task交互的,也不知道自身是第几次执行task,这些信息都在JobManager。
Flink Job的执行的过程可以看做一张DAG图,task是图中的vertex,vertex之间通过数据传递的方式构成了Job的ExecutionGraph。
Task被提交到TaskManager时,是通过RPC调用的TaskExecutor#submitTask
,后走到Task#startTaskThread
,Task是实现了Runnable接口的,startTaskThread方法会调用Task#run,其核心代码如下:
// Task#doRun
// .......
// 初始化执行类,通过反射创建
invokable = loadAndInstantiateInvokable(
userCodeClassLoader.asClassLoader(), nameOfInvokableClass, env);
// .....
// run the invokable
invokable.invoke();
这里的invokable是AbstractInvokable的实例,也就是说task执行的都是实现了AbstractInvokable的。
3.2 StreamTask
StreamTask是所有流式task的基础类,是TaskManager本地执行的基本单元。StreamTask运行构成task的算子(operator)链的一个或多个算子,在一条链上的算子可以在一个线程中执行,因此也是在相同的流式分区中。StreamTask包含了headOperator 和 operatorChain,封装了算子的处理逻辑。
StreamTask的生命周期如下:
* * The life cycle of the task(StreamTask) is set up as follows:
* {@code
* -- setInitialState -> provides state of all operators in the chain
* |
* +----> 重新初始化task的state,并且在如下两种情况下尤为重要:
* | 1. 当任务从故障中恢复并从最后一个成功的checkpoint点重新启动时
* | 2. 从一个保存点恢复时。
* -- invoke()
* |
* +----> Create basic utils (config, etc) and load the chain of operators
* +----> operators.setup() //创建 operatorChain 并设置为 headOperator 的 Output
* --------> openAllOperators()
* +----> task specific init()
* +----> initialize-operator-states()
* +----> open-operators() //执行 operatorChain 中所有 operator 的 open 方法
* +----> run() //runMailboxLoop()方法将一直运行,直到没有更多的输入数据
* --------> mailboxProcessor.runMailboxLoop();
* --------> StreamTask.processInput()
* --------> StreamTask.inputProcessor.processInput()
* --------> 间接调用 operator的processElement()和processWatermark()方法
* +----> close-operators() //执行 operatorChain 中所有 operator 的 close 方法
* +----> dispose-operators()
* +----> common cleanup
* +----> task specific cleanup()
* }
*
StreamTask是AbstractInvokable的子类,在执行流式任务时,在Task中调用invoke方法时,调用StreamTask的Invoke方法,其具体代码如下:
public final void invoke() throws Exception {
try {
beforeInvoke();
// final check to exit early before starting to run
if (canceled) {
throw new CancelTaskException();
}
// let the task do its work
runMailboxLoop();
// if this left the run() method cleanly despite the fact that this was canceled,
// make sure the "clean shutdown" is not attempted
if (canceled) {
throw new CancelTaskException();
}
afterInvoke();
} catch (Throwable invokeException) {
//处理异常
}
cleanUpInvoke();
}