Apache Flink官网的描述:
Apache Flink is a framework and distributed processing engine for stateful computations over unbounded and bounded data streams. Flink has been designed to run in all common cluster environments, perform computations at in-memory speed and at any scale.
翻译成中文就是说:Apache Flink是一个对有界或无界数据流进行有状态计算的分布式处理引擎框架。
1、基本组件栈
从下至上:
2、基本架构
Flink架构也遵循了Master-Slave架构设计原则,主要由两个组件构成:JobManager和TaskManager。JobManager为Master节点,TaskManager为Worker节点(slave).。所有组件之间的通信都是借助于Akka Framework。
1、Flink根据数据集类型的不同将核心数据处理接口分为两大类:DataStream API与DataSet API。同时Flink将数据处理接口抽象成四层(如下图),用户可以根据需要选择任意一层抽象接口来开发Flink应用。
2、Flink应用程序结构与数据流结构
Flink 应用程序结构就是如上图所示:
1、编写Flink程序:当我们要实现一个Flink Job时,需要先把我们的业务抽象成分布式计算模型,然后使用Stream API按照上述的编程结构进行编程实现:
2、编写好的Flink程序打包提交到客户端执行,在客户端顺序执行我们编写的这段程序时:
结:从Source到Sink中间的处理阶段,所用到的这些算子或者说所有的算子可以分为两种类型:处理数据的算子、不处理数据的算子。这种分法不是官方的。为什么这么分,因为例如flatMap中间是有数据处理逻辑的,将一行数据扁平化为多行,但是像keyBy,equalTo,partition,rebanlance等方法,其实是没有对数据进行修改的,起的是一个按字段匹配,分发的作用,这些算子的transformation不会被放入到StreamExecutionEnvironment的transformations集合中,而是作为输入存入下游transformation的input对象中。所以为啥不处理数据的算子不存入transformations集合中,因为它的下一步一定是处理数据的算子,它可以通过input对象存在于transformation当中(如何判断一个算子是否是处理数据的算子,只要看该算子底层实现中是否执行DataStream的doTransform()方法即可)。
概念 | 说明 |
---|---|
TypeExtractor | DataStream应用所处理的事件会以数据对象的形式存在,在Flink内部出于网络传输、读写状态、检查点和保存点的目的,需要对这些输入输出对象进行序列化或反序列化。为了提高上述过程的效率,Flink对这些数据进行重新包装,使用TypeInformation类型信息来表示这些数据的类型。而TypeExtractor是flink提供的一个类型提取系统,它可以通过函数的输入输出类型自动获取他们的类型信息,继而得到他们相应的序列化器与反序列化器,但是Flink支持的Java相关的数据类型只有基本类型,数组类型,常用类型,POJO类,Tuple类等,用户自定义的类很多情况下Flink无法自动识别,即提取器失效。 |
Function | 处理函数,流数据的基本处理单元,数据处理逻辑封装在function中,例如:SourceFunction封装Source中用来获取数据的代码,MapFunction封装了处理Map类型数据逻辑的代码。 |
Operator | 算子,将一个Function封装为不同类型的Operator,例如:单输出Operator OneInputStreamOperator ,双输出Operator TwoInputStreamOperator等,是function的不同输出类型的处理逻辑封装。 |
Transformation | 转换,将Operator封装为Transformation。用于形容一组类型的输入,经过Transformation以后,出来一组不同类型的输出。是Operator的更高级封装,也可以分为单输出,双输出,Source,Sink等多种Transformation。(transformation<-Operator<-function,装饰者模式) |
DataStream | DataStream代表一个数据流,里面封装transformation,表示流的处理逻辑。还有若干基于该流往下游的方法。例如map,flatmap。 |
SourceFunction | Function的一种封装,负责Source处理逻辑的Function,例如FromElementsFunction,看了一下,SourceFunction的实现,flink源码中就有22种 |
SourceOperator | Operator的一种封装,封装SourceFunction的Operator,可以封装不同的SourceFunction |
SourceTransformation | Transformation的一种实现,负责封装不同类别的SourceOperator |
DataStreamSource | DataStream的一种实现,表示数据流计算的起点,里面封装了SourceTransformation |
d
3、DataFlowGraph转换(四层图结构)
转换过程说明:
1、转换过程: StreamExecutionEnvironment中存放的transformations->StreamGraph->JobGraph->ExecutionGraph->物理执行图;
2、transformations->StreamGraph->JobGraph在客户端完成,然后提交JobGraph到JobManager;
3、JobManager的主节点JobMaster,将JobGraph转化为ExecutionGraph,然后发送到不同的taskManager,在taskManager上实际的执行过程就是物理执行图。
env.execute("myDemo start =====================");
该段代码执行时做了两件事:把Transformation集合转换成StreamGraph、把StreamGraph按照一定规则切分成JobGraph——逻辑上的DAG。
要介绍StreamGraph首先我们需要先了解两个概念:StreamNode——DAG图的顶点、StreamEdge——DAG图的边(带箭头的直线)。
public class StreamGraph implements Pipeline {
//记录jobName,ExecutionConfig、CheckpointConfig、SavepointRestoreSettings、scheduleMode、TimeCharacteristic 等配置
private boolean chaining;
private Collection> userArtifacts;
/**
* If there are some stream edges that can not be chained and the shuffle mode of edge is not
* specified, translate these edges into {@code BLOCKING} result partition type.
*/
private boolean blockingConnectionsBetweenChains;
/** Flag to indicate whether to put all vertices into the same slot sharing group by default. */
private boolean allVerticesInSameSlotSharingGroupByDefault = true;
//记录StreamNode节点,使用id作为key
private Map streamNodes;
//记录Source
private Set sources;
//记录Sink
private Set sinks;
//记录Select 节点,该节点是虚拟的,需要处理,但是不放入Streamgraph中。后面会被优化掉
private Map>> virtualSelectNodes;
//记录 side output 节点,该节点是虚拟的,需要处理,但是不放入Streamgraph中。后面会被优化掉
private Map> virtualSideOutputNodes;
//记录 partition 节点,该节点是虚拟的,需要处理,但是不放入Streamgraph中。后面会被优化掉
private Map, ShuffleMode>> virtualPartitionNodes;
protected Map vertexIDtoBrokerID;
protected Map vertexIDtoLoopTimeout;
private StateBackend stateBackend;
private Set> iterationSourceSinkPairs;
Flink Client会遍历StreamExecutionEnvironment中的transformations集合,按照用户定义构建上面的DAG图。StreamGraph中,比较重要的是streamNode(transformation对应的节点),source(数据源),sinks(输出),然后几个虚拟的节点(sideout、select、partition分发等,都会作为虚拟节点生成,最后被优化掉)。StreamGraph没有记录边,只记录了节点,边存在于节点中。
StreamNode:里面存储了用户的udf(user-defined-function)对象、statePartitioner分区器、输入输出的序列化方法、所有输入和输出的StreamEdge对象、该StreamNode对应的transformationID等信息。
StreamEdge:存储了上游的节点id、下游节点id还有自身的一个edgeId、outputTag(SideOutputTransformation当中用户指定的OutputTag),outputPartitioner(默认ForwardPartitioner,可由PartitionTransformation指定)。
从StreamGraph到JobGraph
上文提到过,StreamGraph会被切分成JobGraph,这里来介绍一下:首先,StreamGraph和JobGraph有什么区别,StreamGraph是逻辑上的DAG图,不需要关心jobManager怎样去调度每个Operator的调度和执行;JobGraph是对StreamGraph进行切分,因为有些节点可以打包放在一起被JobManage安排调度(为何被打包放在一起点这里),因此JobGraph的DAG每一个顶点就是JobManger的一个调度单位(任务task,假如该Task并行度为3,则,该Task有3个SubTask)。假如StreamGraph切分如下图:
那么JobGraph的DAG如下图,绿色实心的两个顶点是上图打包在一起的StreamNode:
至此Flink客户端的工作基本完成,Flink客户端通过Akka把生成的JobGraph提交给JobManager,JobManager开始根据JobGraph部署工作,接下来详细介绍下该过程。
从JobGraph到ExecutionGraph
上文提到,在客户端完成JobGraph的构建之后,客户端通过akka把它提交给JobManager。JobManager收到客户端提交的JobGraph之后,会构建ExecutionGraph;JobGraph并行化之后就是ExecutionGraph,拓扑结构其实是一样的。其中按照JobVertex将顶点分装成ExecutionJobVertex,按照JobEdge将边封装成ExecutionEdge,还构建IntermediateResult(中间数据)用来描述节点之间的Data shuffle 。如下图:
上游节点会把产生的数据写到IntermediateReslut(下文称:中间数据集)中,是中间数据集的生产者;下游节点会处理中间数据集产生的数据,是中间数据集的消费者,具体的可能有下面两种情况:
ExecutionJobGraph有下面几个特点:
本内容是通读书籍和网上各个渠道总结再加上自己的理解而来,可能会有一些我没注意的纰漏,各位看官老爷如有疑问或指正,在下在此感激不尽。