继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Apache Flink源码解析 (四)Stream Operator

幕布斯6054654
关注TA
已关注
手记 1297
粉丝 219
获赞 1011

概述

  • 以Flink算子的视角为入口,解析它们是如何设计和工作的。

  • 重点在AbstractStreamOperator


实现StreamOperator的接口和抽象类

  • 首先是StreamOperator接口,方法1-4是生命周期相关的。方法5-6是容错与状态相关的,方法7-8是负责从StreamRecord中获取Key的信息的,应用需要shuffle的算子中。方法9-10是与flink的Operator Chaining 优化相关设置。方法11是跟监控相关。除此之外,StreamOperator还继承了CheckpointListener接口(notifyCheckpointComplete方法,checkpoint结束之后的回调方法),KeyContext接口(key的getter setter方法)。

webp

StreamOperator

  • 继承了StreamOperator的扩展接口则有OneInputStreamOperator,TwoInputStreamOperator(这两个接口基本上能做到顾名思义)。
    实现了StreamOperator的抽象类有AbstractStreamOperator以及它的子类AbstractStreamUdfOperator。

webp

StreamOperator


OneInputStreamOperator与TwoInputStreamOperator接口

  • 这两个接口非常类似,本质上就是处理流上存在的三种元素StreamRecord,Watermark和LatencyMarker。一个用作单流输入,一个用作双流输入。除了StreamSource以外的所有Stream算子都必须实现并且只能实现其中一个接口。

    • webp

      OneInputStreamOperator

    • webp

      TwoInputStreamOperator


AbstractStreamOperator

  • AbstractStreamOperator抽象类







    • AbstractStreamOperator实现类(没有继承AbstractUdfStreamOperator)

        public void processElement(StreamRecord<IN> element) throws Exception {      for (int i = 0; i < this.numFields; i++) {
                outTuple.setField(((Tuple) element.getValue()).getField(fields[i]), i);
            }
            output.collect(element.replace(outTuple));
        }
    • StreamProject实现了投影的功能,覆盖了了open方法,创建了一个输出Tuple的实例,processElement方法中将需要的field从输入Tuple中提取出来加入输出Tuple,将StreamRecord中的元素替换成输出Tuple,通过AbstractStreamOperator中定义的output发送到下游。

    • 最后,虽然没有实现OneInputStreamOperator或TwoInputStreamOperator接口,但是对processWatermark和ProcessLatencyMarker的方法都做了实现。启动对watermark的处理是通过InternalTimeServiceManager,具体不作展开。

    • 两个setKeyConextElement方法,借助对应的成员变量stateKeySelector将Key从StreamRecord里面提取出来. 这两个方法的调用链为processInput(StreamInputProcessor || StreamTwoInputProcessor) -> streamOperator.setKeyContextElement1. 值得注意的是,在这个方法调用后紧跟着的就是processElement方法。所以这个方法的作用就是在处理Record之前将stateBackend切换到相应的Key的状态。

    • snapshotState的简化的调用链是triggerCheckpoint(CheckpointCoordinater) -> triggerCheckpoint(Execution) -(AkkaMsg: TriggerCheckpoint)-> (triggerCheckpointBarrier)TaskManager->checkpointStreamOperator(StreamTask) -> snapshotState,从这条链路可以看到checkpoint是由Master节点发起,通过akka触发TaskManager对所有StreamTask做checkpoint,然后最后由算子执行snapshot,在这一层将TimerService做备份,防止触发器丢失,并且调用operatorStateBackend和keyedStateBackend的snapshot方法将stateBackend的备份到用户指定的文件系统。

    • initializeState是空方法,留给子类去覆盖,作用是从checkpoint中恢复状态。

    • getPartitionedState(无namespace)方法创建了一个partitioned state的句柄,使得聚合类的方法可以操作状态,并且这些状态会在snapshotState被调用的时候被checkpoint(AbstractKeyedStateBackend的注释里提到)。在该方法中,调用了重载方法(参数有namespace和相应Serializer)并且传入VoidNamespace相应参数。

    • getOrCreateKeyedState,该方法会被WindowOperator调用,由于同一个key不同的window会对应不同的值,所以每个Window就是一个namespace,在处理具体Element前不仅要切换Key,还要切换namespace。本质上与上一个方法的区别就在于取得的state是否有namespace。

    • notifyCheckpointComplete方法调用了keyStateBackend的同名方法,通知其执行checkpoint完成之后的逻辑。

    • 之后是状态与容错相关的方法

    • setup的调用链是invoke(StreamTask) -> constructor(OperatorChain) -> setup。在调用setup方法时,StreamTask已经在各个TaskManager节点上,StreamTask在启动是会创建OperatorChain,OperatorChain会一一调用所包含的算子的setup方法。而在setup方法中,通过StreamTask的Environment和UserCodeClassLoader将上述的大部分成员变量进行初始化。

    • open和close方法是空方法,由继承的类实现

    • dispose方法是算子生命周期的最后一环,当StreamTask被取消或者出现异常时调用,负责释放与operator状态相关的资源。

    • 首先是算子生命周期相关的方法:

    • 在AbstractStreamOperator实现的方法中,有一部分是上述变量的getter, setter,还有就是对StreamOperator的实现。

    • 配置相关的成员变量有chainingStrategy,决定了能否在生成JobGraph时对算子进行Chaining优化。

    • 状态相关的成员变量:对于stateKeySelector1, stateKeySelector2这两个变量,它们是用于将Key从StreamRecord中提取出来,keySelector是否为null取决于这个算子有几个流有key。keyedStateBackend就是用户指定的state backend,是将状态放在Heap里还是RocksDB里。keyedStateStore是在state backend上做一层抽象,使其能通过StateDescriptor直接获取State。operatorStateBackend是算子相关的状态。

    • 运行时相关的成员变量有container,该算子在运行时所属的StreamTask,以及任务配置信息StreamConfig,算子的输出句柄output,还有运行的上下文runtimeContext。

    • 时间相关的成员变量有timeServiceManager,提供了定时触发的服务,在状态清理上有很重要的作用。

    • 监控相关的成员变量有metrics,注册的一些关于算子的监控信息,还有latencyStats,通过LatencyMarker来获取延时信息。

    • 在AbstractStreamOperator中有一些重要的成员变量,总体来说可以分为几类,一类是运行时相关的,一类是状态相关的,一类是配置相关的,一类是时间相关的,还有一类是监控相关的。

  • AbstractUdfStreamOperator抽象类


    • OneInputStreamOperator

        ValueStateDescriptor<IN> stateId = new ValueStateDescriptor<>(STATE_NAME, serializer);
        values = getPartitionedState(stateId);
    • 其中ProcessOperator对ProcessFunction的实现是只支持获取currentProcessingTime和wartermark,不支持Timer的注册。

    • LegacyKeyedProcessOperator对ProcessFunction的实现是支持Timer的注册的。同时它实现了Triggerable接口中的onEventTime和onProcessingTime方法,这两个方法是由InternalTimerService调用的回调方法,在这两个方法中,会去调用用户在ProcessFunction中实现的onTimer方法来实现定时触发的。LegacyKeyedProcessOperator现在被标记为Deprecated,由KeyedProcessOperator类替代,但是由于很多Function是基于ProcessFunction实现的,而KeyedProcessFunction在1.5.2中还没有什么实现类,所以还是会经常被用到。

    • KeyedProcessOperator实现与LegacyKeyedProcessOperator非常类似,不过UDF是KeyedProcessFunction。

    • ProcessOperator, LegacyKeyedProcessOperator提供了对ProcessFunction的支持, KeyedProcessOperator提供了对KeyedProcessFunction的支持。ProcessFunction是比较灵活的UDF,允许用户通过在processElement的时候可以通过传入的Conext(这个Conext是由算子实现的)操作TimerService,注册ProcessingTimeTimer和EventTimeTimer,并且通过实现方法onTimer就可以在Timer被触发的时候执行回调的逻辑。

    • StreamSink除了在processElement方法中调用SinkFunction方法外,还提供了SimpleContext,可以获取processingTime,watermark和element的时间戳。

    • GenericWriteAheadSink提供了一个可以被实现为Exactly once的sink的抽象类,这边不展开研究。

    • AsyncWaitOperator提供了异步处理的能力,是一个比较特殊的算子,对元素的处理和备份恢复都比较特殊。element的输出通过一个Emitter对象来实现。有机会单独针对这个算子写一篇文章。

    • TimestampsAndPeriodicWatermarksOperator与TimestampsAndPunctuatedWatermarksOperator通过TimestampAssigner提取timestamp并生按照规则生成watermark。

    • windowOperator可能是flink实现的最复杂的operator,可以参考vinoyang的Flink流处理之窗口算子分析。后续也有可能针对这个operator写一篇解析。

    • StreamFilter,StreamMap与StreamFlatMap算子在实现的processElement分别调用传入的FilterFunction,MapFunction, FlatMapFunction的udf将element传到下游。其中StreamFlatMap用到了TimestampedCollector,它是output的一层封装,将timestamp加入到StreamRecord中发送到下游。

    • StreamGroupedReduce与StreamGroupedFold算子相似的点是都涉及到了操作状态, 所以在覆盖open方法时通过创建一个状态的描述符以及调用AbstractStreamOperator实现的getPartitionedState方法获取了一个stateBackend的操作句柄。在processElement方法中借助这个句柄获取当前状态值,在用UDF将新的元素聚合进去并更新状态值,最后输出到下游。不同的是Fold的输出类型可能不一样(所以实现了OutputTypeConfigurable接口的setOutputType方法),并且有初始值。

    • TwoInputStreamOperator的实现类相对较少,作用于双流的各种操作。

    • 此外它还实现了ProcessJoinFunction的Conext抽象类,提供了获取左右两个流元素timestamp的功能。关于BufferEntry的实现这里略过。

    • webp

      TwoInputStreamOperatorChildren.jpg

    • CoStreamMap, CoStreamFlatMap基本与单流的逻辑没什么区别,只是针对两个流的Function做类似的处理。

    • IntervalJoinOperator对双流的元素根据提供的ProcessJoinFunction做内连接,并且每个元素都有失效时间。在processElement方法中,每当一个流的元素到达,会将它加入对应流的buffer,并且遍历另一个流的buffer找到所有join的选项。最后再根据失效时间注册一个状态清理的Timer防止buffer无限增长。

    • CoProcessOperator和KeyedCoProcessOperator本质上与单流的处理也没有什么区别,但是提供了双流之间共享状态的可能。CoProcessOperator也被用来实现NonWindowJoin。

    • CoBroadcastWithKeyedOperator和CoBroadcastWithNonKeyedOperator提供了对(Keyed)BroadcastProcessFunction的支持,和CoProcess有一些类似,只是Broadcast的Stream只有读权限,没有写权限。并且可以通过context直接获得BroadcastState。

    • StreamSource因为没有输入,所以没有实现InputStreamOperator的接口。比较特殊的是ChainingStrategy初始化为HEAD。在运行时由SourceStreamTask调用run方法。在run方法中,他首先出事话LatencyMarksEmitter用来产生延迟监控的LatencyMarker,接着根据用户选择的时间模式(EventTime,IngestionTime和ProcessingTime)来生成相应的SourceConext(包含了产生element关联的timestamp的方法和生成watermark的方法)。最后调用SourceFunction的run方法来启动source。

    • AbstractStreamOperator实现类



    • 还是算子生命周期相关的方法:首先调用了前文提到的AbstractStreamOperator相应方法,然后为userFunction提供runtimeContext,Configuration,并且调用function的open和close方法。

    • 状态和checkpoint相关的方法: 首先调用父类相应方法,完成算子状态的别分与恢复,然后调用实现了Checkpoint相关的接口的userFunction的相关方法。

    • AbstractUdfStreamOperator继承了AbstractStreamOperator,对其部分方法做了增强,多了一个成员变量UserFunction,并提供get方法。此外还实现了OutputTypeConfigurable接口的setOutputType方法对输出数据的类型做了设置。


  • 总结

    • 本文从StreamOperator接口开始一层一层往上解析,重点关注了AbstractStreamOperator和AbstractUdfStreamOperator这两个抽象类,覆盖了大部分的算子的功能与设计。

    • 了解算子如何工作可以对运行的任务有更深的了解,借助AbstractUdfStreamOperator,也可以较低成本的定制出特殊的算子。



作者:铛铛铛clark
链接:https://www.jianshu.com/p/2e379f242272


打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP