前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Flink 作业生成①:生成 StreamGraph

Flink 作业生成①:生成 StreamGraph

作者头像
codingforfun
发布2021-05-17 14:17:34
7740
发布2021-05-17 14:17:34
举报

一、作业生成及提交整体流程

上图为一个 Flink 作业的提交流程,主要可以分为以下几个步骤:

  1. Client 将作业 code 生成 StreamGraph(在 Batch 模式下,生成的是一个 OptimizedPlan,这里暂不展开):StreamGraph 描述了算子和算子之间逻辑上的拓扑关系
  2. Client 将 StreamGraph 转换为 JobGraph:Operator chain,
    • 将并不涉及到 shuffle 的算子进行合并
    • 对于同一个 operator chain 里面的多个算子,会在同一个 task 中执行
    • 对于不在同一个 operator chain 里的算子,会在不同的 task 中执行
  3. Client 中的 ClusterClient 将 JobGraph 提交给 Dispatcher,Dispatcher 根据 JobGraph 创建相应的 JobManager(代码上是 JobMaster)并运行起来
  4. JobManager 将 JobGraph 转换为 ExecutionGraph
  5. JobManager 将 ExecutionGraph 转换为物理执行计划(可执行)

接下来我们以如下例子来剖析各个步骤具体的执行流程:

代码语言:javascript
复制
public static void main(String[] args) throws Exception {

        // 创建 execution environment
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        // 设置 data source
        DataStream<TaxiRide> rides = env.addSource(new TaxiRideGenerator());

        // 将每个 ride 转为 Tuple2(driverId, 1)
        DataStream<Tuple2<Long, Long>> tuples = rides.map(new MapFunction<TaxiRide, Tuple2<Long, Long>>() {
                    @Override
                    public Tuple2<Long, Long> map(TaxiRide ride) {
                        return Tuple2.of(ride.driverId, 1L);
                    }
        });

    // 对 stream 根据 driverId 进行重新分区
        KeyedStream<Tuple2<Long, Long>, Long> keyedByDriverId = tuples.keyBy(t -> t.f0);

        // 计算每个司机的 ride 数
        DataStream<Tuple2<Long, Long>> rideCounts = keyedByDriverId.sum(1);

        // 将结果进行打印
        rideCounts.print();

        // 开始运行
        env.execute("Ride Count");
}

二、执行 Transformations

该过程发生在 Client 端

上述代码可以分为三个部分,即:

  • 输入的数据源是谁?用一个 DataStreamSource 表示,是该拓扑的头结点
  • 对数据进行了哪些操作?用一个 List<Transformation<?>> transformations 表示
  • 最终计算的结果输出到哪里?用一个 DataStreamSink 表示,是该拓扑的尾结点

我们分别就这三部分进行说明

2.1、输入的数据源是谁

add Source 相关的调用如下:

代码语言:javascript
复制
DataStream<TaxiRide> rides = env.addSource(new TaxiRideGenerator());

如上,通过 DataStreamSource<OUT> StreamExecutionEnvironment#addSource(SourceFunction<OUT> function) 设置 SourceFunction 并返回一个 DataStreamSource。关于 SourceFunction、SourceContext 等请查看

SourceFunction & SourceContext & StreamSource

  • DataStreamSource:DataStream 子类,DataStream 的起点 。详见 DataStream
  • OUT:数据源中每条数据经过 SourceFunction 转换后的具体类型(说白了就是有哪些字段、字段名和类型分别是什么)

设置输入源env.addSource(new TaxiRideGenerator()) 的流程如下:

总结一下,addSource 的主要流程如下:

  • 使用 SourceFunction(包含 run 方法开始接受/拉取数据、cancel 方法停止接收/拉取数据) 创建 StreamSource(StreamOperator 子类)
  • 使用 StreamSource 创建 SourceTransformation
  • 使用 SourceTransformation 创建 DataStreamSource(表示输入源的 DataStream)

2.2、对数据进行了哪些操作

以 map 操作(如下)为例,来说明 transformation 具体流程。在这里,map 将 TaxiRide 转换为 (ride.driverId, 1L) 的二元组

代码语言:javascript
复制
DataStream<Tuple2<Long, Long>> tuples = rides.map(new MapFunction<TaxiRide, Tuple2<Long, Long>>() {
         @Override
         public Tuple2<Long, Long> map(TaxiRide ride) {
            return Tuple2.of(ride.driverId, 1L);
         }
});

主要流程如下:

map 之后的 keyBysummap 有类似的过程:

  1. 获取该 transformation 的 return type info(即各个字段名和类型)
  2. 分为两种情况:
    • 若该 transformation 会对 elements 进行转换,则会
      • 使用相应的 Function 构造相应的 AbstractUdfStreamOperator;
      • 使用 return type info 和相应的 AbstractUdfStreamOperator构造 resultTransformation
      • 将 resultTransformation 添加到 env 的 List<Transformation<?>> transformations
    • 否则,创建相应的 resultTransformation(非 AbstractUdfStreamOperator 子类)
  3. 使用 resultTransformation 构造 returenDataStream: DataStream

2.3、最终计算的结果输出到哪里

最后的 rideCounts.print() 会触发 DataStream.addSink(...),其流程也如上面的几步,只是最终返回的类型是 DataStreamSink,并没有直接继承 DataStream,包含一个 transformation 。我们以下图来看 DataStream 从创建、转换、输出的过程(每个 Transformation 都有 id,从 1 开始,下面 5 个 Transformations 的 id 分别是 1、2、3、4、5):

  • 除了 SourceTransformation 外,其他类型 Transformation 都有 inputs 来指向其输入的 List<Transformation> 。如上图中红线所示
  • DataStream 上的转换调用会最终根据调用顺序依次添加到 StreamExecutionEnvironmentList<Transformation<?>> #transformations 中;但并不需要 DataStream 的每一个转换调用都添加一个 Transformation 到该 list 中,根据上面提到的 input 指向关系,即使不添加不包含 Function 的 Transformation 也不会丢失任何一个 Transformation

三、生成 StreamGraph

env.execute("Ride Count") 会触发真正的执行,其内部主要分为两步:

  1. 生成 StreamGraph:通过创建 StreamGraphGenerator 并调用 generate() 方法生成 StreamGraph
  2. execute StreamGraph 返回 JobExecutionResult

我们先来看看 StreamGraph 和 StreamGraphGenerator 的一些概况,再来详细看看 StreamGraphGenerator 是如何生成 StreamGraph 的

3.1、StreamGraphGenerator 生成 StreamGraph

StreamGraphGenerator#generate() 生成 StreamGraph

如上流程图,关键的地方是看 translator 对 transformation 是如何 translateForStreaming 的,我们来看看 OneInputTransformationTranslator#translateForStreaming,这也是 map、filter 操作对应的 Translator,如下:

最终,上例中的代码生成的 StreamGraph 可以用下面这个图来表示:

在上图中:

  • 每个灰色底的大框都是一个 StreamNode
  • 蓝色字(如 Source: Custom Source,Map 等)为 node name,其后的数字为 node id
  • 每个 StreamNode 主要包含:
    • 输入边(可能是多个),输出边(可能是多个);边类型为 StreamEdge:包含 sourceVertex、targetVertex 来说明边是由哪个节点指向哪个节点、StreamPartitioner outputPartitioner 表示 source 节点的数据到 target 节点的是如何分区的
    • jobVertexClass:即具体的 StreamTask 具体类型;StreamTask 是不熟给 TaskManagers 用来执行的
    • statePartitioners:
    • operator:包含具体处理一行行数据的 Function
    • parallelism:并发度,并发度是怎么计算的?优先使用用户代码指定的,若无,则使用 env 中默认的并发度,如这里是 12

对于 StreamGraph 及其生成,以下是一些 QA

Q:为什么上图中没有 id 为 3 的节点 ?

A:有些 transform 操作并不会生成 StreamNode。virtualSideoutputNodes、virtualPartitionNodes 这几类 transform 都会被处理成虚拟节点,当下游生成 StreamNode 后,发现上游为虚拟节点会找到虚拟节点的上游,并创建 StreamEdge 与虚拟节点上游的 transform 进行连接,并把虚拟节点信息写到 StreamEdge 中

  • PartitionTransformtion,表示对数据重新分区,会生成个虚拟节点,会添加到 Map<Integer, Tuple3<Integer, StreamPartitioner<?>, ShuffleMode>> virtualPartitionNodes
  • SideOutputTransformation:表示对上游数据根据 OutoutTag 做一个 selection

Q:StreamGraph 还包含哪些需要注意的东西(除了上述的拓扑关系)?

A:主要有:

  • StateBackend stateBackend:表示要使用哪种类型的 StateBackend,是 memory 的还是 rocksdb 的
  • Collection<Tuple2<String, DistributedCacheEntry>> userArtifacts:job 运行依赖的 jar、文件等信息
  • 配置信息:savePoint、checkPoint、execution 的配置信息
  • 时间特性:使用 event time 还是 process time
  • Set<Integer> sources, Set<Integer> sinks:表示输入(头结点)、输出(尾节点)的 ids
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、作业生成及提交整体流程
  • 二、执行 Transformations
    • 2.1、输入的数据源是谁
      • 2.2、对数据进行了哪些操作
        • 2.3、最终计算的结果输出到哪里
        • 三、生成 StreamGraph
          • 3.1、StreamGraphGenerator 生成 StreamGraph
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档