Flink window 源码分析3:WindowOperator

Flink window 源码分析1:窗口整体执行流程
Flink window 源码分析2:Window 的主要组件
Flink window 源码分析3:WindowOperator
Flink window 源码分析4:WindowState
本文分析的源码为flink 1.18.0_scala2.12版本。

WindowOperator 是真正负责 window 中元素存储和计算流程的核心类。
大致看一下其定义代码(如下),WindowOperator 继承了 AbstractUdfStreamOperator (所以的 operator 都会继承自它,该类处理用户定义函数的打开和关闭。),然后 WindowOperator 实现了OneInputStreamOperator 接口,AbstractUdfStreamOperator 又继承了 AbstractStreamOperator,OneInputStreamOperator 接口继承了StreamOperator 这个接口,AbstractStreamOperator 也实现了 StreamOperator 接口。

public class WindowOperator<K, IN, ACC, OUT, W extends Window>  
        extends AbstractUdfStreamOperator<OUT, InternalWindowFunction<ACC, OUT, K, W>>  
        implements OneInputStreamOperator<IN, OUT>, Triggerable<K, W>

WindowOperator 的继承关系.png
WindowOperator 构造方法也较为简单,就是检查一些参数是否合法,然后为其属性赋初值。

public WindowOperator(  
        WindowAssigner<? super IN, W> windowAssigner,  
        TypeSerializer<W> windowSerializer,  
        KeySelector<IN, K> keySelector,  
        TypeSerializer<K> keySerializer,  
        StateDescriptor<? extends AppendingState<IN, ACC>, ?> windowStateDescriptor,  
        InternalWindowFunction<ACC, OUT, K, W> windowFunction,  
        Trigger<? super IN, ? super W> trigger,  
        long allowedLateness,  
        OutputTag<IN> lateDataOutputTag) {  
  
    super(windowFunction);  
  
    checkArgument(allowedLateness >= 0);  
  
    checkArgument(  
            windowStateDescriptor == null || windowStateDescriptor.isSerializerInitialized(),  
            "window state serializer is not properly initialized");  
  
    this.windowAssigner = checkNotNull(windowAssigner);  
    this.windowSerializer = checkNotNull(windowSerializer);  
    this.keySelector = checkNotNull(keySelector);  
    this.keySerializer = checkNotNull(keySerializer);  
    this.windowStateDescriptor = windowStateDescriptor;  
    this.trigger = checkNotNull(trigger);  
    this.allowedLateness = allowedLateness;  
    this.lateDataOutputTag = lateDataOutputTag;  
  
    setChainingStrategy(ChainingStrategy.ALWAYS);  
}

然后了解一下几个重要的方法:
WindowOperator包含如下几个重要方法:

  • open:operator 初始化的逻辑;
  • processElement:新元素进入 window 的时候调用;
  • onEventTime:event time 计算触发时候的逻辑;
  • onProcessingTime:processing time 计算触发时候的逻辑。
  1. open:operator 初始化的逻辑
@Override  
public void open() throws Exception {  
    super.open();  
	
	// 使用 metrics 测量迟到的记录数量
    this.numLateRecordsDropped = metrics.counter(LATE_ELEMENTS_DROPPED_METRIC_NAME);  
    timestampedCollector = new TimestampedCollector<>(output);  
  
    internalTimerService = getInternalTimerService("window-timers", windowSerializer, this);  
  
    triggerContext = new Context(null, null);  
    processContext = new WindowContext(null);  

	// WindowAssigner.WindowAssignerContext() 是抽象类,所以这里初始化需要实现一下
    windowAssignerContext =  
            new WindowAssigner.WindowAssignerContext() {  
                @Override  
                public long getCurrentProcessingTime() {  
                    return internalTimerService.currentProcessingTime();  
                }  
            };  
   
    // 创建(或恢复)保存实际窗口内容的 state
    // 注意--在重写 evicting window operator 的情况下,状态可能为空
    if (windowStateDescriptor != null) {  
        windowState =  
                (InternalAppendingState<K, W, IN, ACC, ACC>)  
                        getOrCreateKeyedState(windowSerializer, windowStateDescriptor);  
    }  
  
    // create the typed and helper states for merging windows  
    if (windowAssigner instanceof MergingWindowAssigner) {  
  
        // store a typed reference for the state of merging windows - sanity check  
        if (windowState instanceof InternalMergingState) {  
            windowMergingState = (InternalMergingState<K, W, IN, ACC, ACC>) windowState;  
        }  
        // TODO this sanity check should be here, but is prevented by an incorrect test (pending  
        // validation)        // TODO see WindowOperatorTest.testCleanupTimerWithEmptyFoldingStateForSessionWindows()        // TODO activate the sanity check once resolved        //        else if (windowState != null) {        //           throw new IllegalStateException(        //                 "The window uses a merging assigner, but the window state is not mergeable.");        //        }  
        @SuppressWarnings("unchecked")  
        final Class<Tuple2<W, W>> typedTuple = (Class<Tuple2<W, W>>) (Class<?>) Tuple2.class;  
  
        final TupleSerializer<Tuple2<W, W>> tupleSerializer =  
                new TupleSerializer<>(  
                        typedTuple, new TypeSerializer[] {windowSerializer, windowSerializer});  
  
        final ListStateDescriptor<Tuple2<W, W>> mergingSetsStateDescriptor =  
                new ListStateDescriptor<>("merging-window-set", tupleSerializer);  
  
        // get the state that stores the merging sets  
        mergingSetsState =  
                (InternalListState<K, VoidNamespace, Tuple2<W, W>>)  
                        getOrCreateKeyedState(  
                                VoidNamespaceSerializer.INSTANCE, mergingSetsStateDescriptor);  
        mergingSetsState.setCurrentNamespace(VoidNamespace.INSTANCE);  
    }  
}

open方法里面主要就是初始化窗口的状态。如果是session window (即windowAssigner instanceof MergingWindowAssigner)的话,会多初始化一个关于合并窗口的状态。
关于窗口状态的机制,请看笔记:WindowState。
2. processElement:新元素进入 window 的时候调用

@Override  
public void processElement(StreamRecord<IN> element) throws Exception {  
	// 获取当前元素所在的所有窗口
    final Collection<W> elementWindows =  
            windowAssigner.assignWindows(  
                    element.getValue(), element.getTimestamp(), windowAssignerContext);  
  
    // 若处理完isSkippedElement依然是true,则说明该元素没有被任何窗口处理  
    boolean isSkippedElement = true;  

	// 获取待处理元素的key
    final K key = this.<K>getKeyedStateBackend().getCurrentKey();  

	// 判断是不是会话窗口
    if (windowAssigner instanceof MergingWindowAssigner) {  
        MergingWindowSet<W> mergingWindows = getMergingWindowSet();  
  
        for (W window : elementWindows) {  
  
            // 添加新窗口可能会导致合并,
            // 在这种情况下,actualWindow 就是合并后的窗口,我们将使用它。
            // 如果不合并,那么 actualWindow == window          
            W actualWindow =  
                    mergingWindows.addWindow(  
                            window,  
                            new MergingWindowSet.MergeFunction<W>() {  
                                @Override  
                                public void merge(  
                                        W mergeResult,  
                                        Collection<W> mergedWindows,  
                                        W stateWindowResult,  
                                        Collection<W> mergedStateWindows)  
                                        throws Exception {  
  
                                    if ((windowAssigner.isEventTime()  
                                            && mergeResult.maxTimestamp() + allowedLateness  
                                                    <= internalTimerService  
                                                            .currentWatermark())) {  
                                        throw new UnsupportedOperationException(  
                                                "The end timestamp of an "  
                                                        + "event-time window cannot become earlier than the current watermark "  
                                                        + "by merging. Current watermark: "  
                                                        + internalTimerService  
                                                                .currentWatermark()  
                                                        + " window: "  
                                                        + mergeResult);  
                                    } else if (!windowAssigner.isEventTime()) {  
                                        long currentProcessingTime =  
                                                internalTimerService.currentProcessingTime();  
                                        if (mergeResult.maxTimestamp()  
                                                <= currentProcessingTime) {  
                                            throw new UnsupportedOperationException(  
                                                    "The end timestamp of a "  
                                                            + "processing-time window cannot become earlier than the current processing time "  
                                                            + "by merging. Current processing time: "  
                                                            + currentProcessingTime  
                                                            + " window: "  
                                                            + mergeResult);  
                                        }  
                                    }  
  
                                    triggerContext.key = key;  
                                    triggerContext.window = mergeResult;  
  
                                    triggerContext.onMerge(mergedWindows);  
  
                                    for (W m : mergedWindows) {  
                                        triggerContext.window = m;  
                                        triggerContext.clear();  
                                        deleteCleanupTimer(m);  
                                    }  
  
                                    // merge the merged state windows into the newly resulting  
                                    // state window                                    windowMergingState.mergeNamespaces(  
                                            stateWindowResult, mergedStateWindows);  
                                }  
                            });  
  
            // drop if the window is already late  
            if (isWindowLate(actualWindow)) {  
                mergingWindows.retireWindow(actualWindow);  
                continue;  
            }  
            isSkippedElement = false;  
  
            W stateWindow = mergingWindows.getStateWindow(actualWindow);  
            if (stateWindow == null) {  
                throw new IllegalStateException(  
                        "Window " + window + " is not in in-flight window set.");  
            }  
  
            windowState.setCurrentNamespace(stateWindow);  
            // 数据添加到状态中存储下来
            windowState.add(element.getValue());  
  
            triggerContext.key = key;  
            triggerContext.window = actualWindow;  

            TriggerResult triggerResult = triggerContext.onElement(element);  
  
            if (triggerResult.isFire()) {  
                ACC contents = windowState.get();  
                if (contents == null) {  
                    continue;  
                }  
                // 处理元素
                emitWindowContents(actualWindow, contents);  
            }  
  
            if (triggerResult.isPurge()) {  
                windowState.clear();  
            }  
            registerCleanupTimer(actualWindow);  
        }  
  
        // need to make sure to update the merging state in state  
        mergingWindows.persist();  
    } else {  
        for (W window : elementWindows) {  
  
            // 如果数据迟到了,则不处理  
            if (isWindowLate(window)) {  
                continue;  
            }  
            isSkippedElement = false;  
  
            windowState.setCurrentNamespace(window);  
            // 数据添加到状态中存储下来
            windowState.add(element.getValue());  
  
            triggerContext.key = key;  
            triggerContext.window = window;  

			// 判断当前窗口是否需要触发
            TriggerResult triggerResult = triggerContext.onElement(element);  

			// 窗口将被评估并输出结果。窗口不会被清除,但会保留所有元素。
            if (triggerResult.isFire()) {  
	            // 从状态里面把数据拿出来
                ACC contents = windowState.get();  
                if (contents == null) {  
                    continue;  
                }  
                // 处理元素
                emitWindowContents(window, contents);  
            }  

			// 窗口中的所有元素都会被清除,窗口也会被丢弃,但不会评估窗口函数,也不会发出任何元素。
            if (triggerResult.isPurge()) {  
                windowState.clear();  
            }  
            // 注册一个计时器来清理要丢弃其状态的窗口
            registerCleanupTimer(window);  
        }  
    }  
  
    // 如果元素没被窗口处理
    // windowAssigner 是事件时间
    // 当前时间戳+允许延迟不小于元素时间戳
    // 且 lateDataOutputTag 已被设置
    // 则从侧输出流输出元素
    if (isSkippedElement && isElementLate(element)) {  
        if (lateDataOutputTag != null) {  
            sideOutput(element);  
        } else {  
            this.numLateRecordsDropped.inc();  
        }  
    }  
}

先是 windowAssigner 把数据分配到不同的窗口中,然后获取当前的 key,这个key就是 keyby 里面的那个key。
然后又判断是否是 session window 分别走两个不同的处理逻辑,因为 session window 和其他的 window 的逻辑是不一样的,这里我们主要是分析不是session window的情况,也就是上面else里面的逻辑,循环处理每一个 window,如果是迟到的窗口会直接忽略,设置当前窗口的namespace,把数据先保存到 windowstate 里面。
判断窗口是否触发,即 triggerResult.isFire() 是否为真,如果触发就调用 emitWindowContents 函数,发送数据到我们定义的 function 里面,触发窗口的计算逻辑 。如果触发了purge操作,则清空window中的内容。
最后注册一个 timer 去删除窗口里面的数据。
循环处理完后 判断数据是否晚到,并且在晚到时间内数据达到,如果定义了测流输出就把数据用测流输出 否则就删除晚到的数据。
session window 的逻辑和这个差不多,但是会有合并状态的过程。
3. onEventTime:event time 计算触发时候的逻辑

@Override  
public void onEventTime(InternalTimer<K, W> timer) throws Exception {  
    triggerContext.key = timer.getKey();  
    triggerContext.window = timer.getNamespace();  
  
    MergingWindowSet<W> mergingWindows;  
	
	// 判断是否是 session 窗口
    if (windowAssigner instanceof MergingWindowAssigner) {  
        // 窗口合并
        mergingWindows = getMergingWindowSet();  
        W stateWindow = mergingWindows.getStateWindow(triggerContext.window);  
        if (stateWindow == null) {    
            // 定时器在不存在的窗口中启动,
            // 这只有在触发器没有清理定时器的情况下才会发生。
            // 不过我们已经清除了合并窗口,因此也清除了触发器状态,所以没什么可做的。        
            return;  
        } else {  
            windowState.setCurrentNamespace(stateWindow);  
        }  
    } else {  
        windowState.setCurrentNamespace(triggerContext.window);  
        mergingWindows = null;  
    }  

	// 判断当前窗口是否需要触发
    TriggerResult triggerResult = triggerContext.onEventTime(timer.getTimestamp());  

	// 窗口将被评估并输出结果。窗口不会被清除,但会保留所有元素。
    if (triggerResult.isFire()) {  
	    // 从状态里面把数据拿出来
        ACC contents = windowState.get();  
        if (contents != null) {  
	        // 处理元素
            emitWindowContents(triggerContext.window, contents);  
        }  
    }  

	// 窗口中的所有元素都会被清除,窗口也会被丢弃,但不会评估窗口函数,也不会发出任何元素。
    if (triggerResult.isPurge()) {  
        windowState.clear();  
    }  

	// 如果是event time类型,并且定时器触发时间是window的cleanup时间的时候,
	// 意味着该窗口的数据已经处理完毕,需要清除该窗口的所有状态
    if (windowAssigner.isEventTime()  
            && isCleanupTime(triggerContext.window, timer.getTimestamp())) {  
        clearAllState(triggerContext.window, windowState, mergingWindows);  
    }  

	// 持久化合并窗口的状态
    if (mergingWindows != null) {  
        // need to make sure to update the merging state in state  
        mergingWindows.persist();  
    }  
}

onEventTime 中使用的函数在 processElement 中几乎也有使用,两者元素处理过程差不多。
4. onProcessingTime:processing time 计算触发时候的逻辑
onProcessingTime 和 onEventTime 代码几乎一样,只是触发条件不同。

  • 21
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Windows 2000,原名Windows NT 5.0。它结合了Windows 98和Windows NT 4.0的很多优良的功能/性能与一身,超越了Windows NT的原来含义。   Windows 2000系列分成四个产品:Windows 2000 Professional, Windows 2000 Server, Windows 2000 Advanced Server, Windows 2000 Datacenter Server。 Windows 2000 Professional 是一个商业用户的桌面操作系统,也适合移动用户,是Windows NT Workstation 4.0的升级。Windows 2000 Server和Advanced Server分别是Windows NT Server 4.0及其企业版的升级产品。Windows 2000 Datacenter Server是一个新的品种,主要通过OEM的方式销售,是,支持32个以上的CPU和64GB的内存,以及4个节点的集群服务。 Windows 2000平台包括了Windows 2000 Professional 和Windows 2000 Server前后台的集成,下面仅从五个方面简要地介绍一下它的新特性和新功能。   一、活动目录   Windows 2000 Server在Windows NT Server 4.0的基础上,进一步发展了“活动目录(Active Directory)”。活动目录是从一个数据存储开始的。它采用了类似Exchange Server的数据存储,称为:Extensible Storage Service (ESS)。其特点是不需要事先定义数据库的参数,可以做到动态地增长,性能非常优良。这个数据存储之上已建立索引的,可以方便快速地搜索和定位。活动目录的分区是“域(Domain)”,一个域可以存储上百万的对象。域之间还有层次关系,可以建立域树和域森林,无限地扩展。   在数据存储之上,微软建立了一个对象模型,以构成活动目录。这一对象模型对LDAP有纯粹的支持,还可以管理和修改Schema。Schema包括了在活动目录中的计算机、用户和打印机等所有对象的定义,其本身也是活动目录的内容之一,在整个域森林中是唯一的。通过修改Schema的工具,用户或开发人员可以自己定义特殊的类和属性,来创建所需要的对象和对象属性。   活动目录包括两个方面:一个目录和与目录相关的服务。目录是存储各种对象的一个物理上的容器;而目录服务是使目录中所有信息和资源发挥作用的服务。活动目录是一个分布式的目录服务。信息可以分散在多台不同的计算机上,保证快速访问和容错;同时不管用户从何处访问或信息处在何处,都对用户提供统一的视图。 活动目录充分体现了微软产品的“ICE”,即集成性(Integration),深入性(Comprehensive),和易用性(Ease of Use)等优点。活动目录是一个完全可扩展,可伸缩的目录服务,既能满足商业ISP的需要,又能满足企业内部网和外联网的需要 最近在网上游荡的时候发现msdos和windows 2000的原代码 ,不敢独享,所以分享给大家
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值