一文搞懂 Flink window 元素的顺序问题


1. 起因

在我们使用 evictor 算子的时候,官网有这样的一句话:

Flink provides no guarantees about the order of the elements within a window. This implies that although an evictor may remove elements from the beginning of the window, these are not necessarily the ones that arrive first or last

大概的意思就是说,evictor 尽管可以从窗口开始时移除元素但是并不保证,这个元素是第一个或最后一个到达窗口的。 why?

2. 解释

env.addSource(consumer).uid("orderAndRegisterUserIdSource")
				
				.keyBy(new KeySelector<String, String>() {
					@Override
					public String getKey(String value) throws Exception {
						return "a";
					}
				})
				.timeWindow(Time.seconds(1000))
			.evictor(new Evictor<String, TimeWindow>() {
				@Override
				public void evictBefore(Iterable<TimestampedValue<String>> elements, int size, TimeWindow window, EvictorContext evictorContext) {
					System.out.println("evictBefore");
				}

				@Override
				public void evictAfter(Iterable<TimestampedValue<String>> elements, int size, TimeWindow window, EvictorContext evictorContext) {
					System.out.println("evictAfter");
				}
			})
				.trigger(new CountAndTimeTrigger(2L))
				.process(new ProcessWindowFunctionImp()).uid("process");

通常我们会写出这样的代码,但当我们在 evictBefore 或者 evictAfter输出第一个元素时,它竟然保证是第一个或者最后一个进入 window 的。

我们一起来看一下源码,进入 EvictingWindowOperator

public void processElement(StreamRecord<IN> element) throws Exception {
		final Collection<W> elementWindows = windowAssigner.assignWindows(
				element.getValue(), element.getTimestamp(), windowAssignerContext);

		//if element is handled by none of assigned elementWindows
		boolean isSkippedElement = true;

		final K key = this.<K>getKeyedStateBackend().getCurrentKey();

		if (windowAssigner instanceof MergingWindowAssigner) {
			MergingWindowSet<W> mergingWindows = getMergingWindowSet();

			for (W window : elementWindows) {

				// adding the new window might result in a merge, in that case the actualWindow
				// is the merged window and we work with that. If we don't merge then
				// 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() && mergeResult.maxTimestamp() <= internalTimerService.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: " + internalTimerService.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
								evictingWindowState.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.");
				}

				evictingWindowState.setCurrentNamespace(stateWindow);
				evictingWindowState.add(element);

				triggerContext.key = key;
				triggerContext.window = actualWindow;
				evictorContext.key = key;
				evictorContext.window = actualWindow;

				TriggerResult triggerResult = triggerContext.onElement(element);

				if (triggerResult.isFire()) {
					Iterable<StreamRecord<IN>> contents = evictingWindowState.get();
					if (contents == null) {
						// if we have no state, there is nothing to do
						continue;
					}
					emitWindowContents(actualWindow, contents, evictingWindowState);
				}

				if (triggerResult.isPurge()) {
					evictingWindowState.clear();
				}
				registerCleanupTimer(actualWindow);
			}

			// need to make sure to update the merging state in state
			mergingWindows.persist();
		} else {
			for (W window : elementWindows) {

				// check if the window is already inactive
				if (isWindowLate(window)) {
					continue;
				}
				isSkippedElement = false;

				evictingWindowState.setCurrentNamespace(window);
				evictingWindowState.add(element);

				triggerContext.key = key;
				triggerContext.window = window;
				evictorContext.key = key;
				evictorContext.window = window;

				TriggerResult triggerResult = triggerContext.onElement(element);

				if (triggerResult.isFire()) {
					Iterable<StreamRecord<IN>> contents = evictingWindowState.get();
					if (contents == null) {
						// if we have no state, there is nothing to do
						continue;
					}
					emitWindowContents(window, contents, evictingWindowState);
				}

				if (triggerResult.isPurge()) {
					evictingWindowState.clear();
				}
				registerCleanupTimer(window);
			}
		}

		// side output input event if
		// element not handled by any window
		// late arriving tag has been set
		// windowAssigner is event time and current timestamp + allowed lateness no less than element timestamp
		if (isSkippedElement && isElementLate(element)) {
			if (lateDataOutputTag != null){
				sideOutput(element);
			} else {
				this.numLateRecordsDropped.inc();
			}
		}
	}

这个里的 evictingWindowState 是一个 RocksDBListState。
Flink key state 为何仅与 key 有关的 ,我们知道 evictingWindowState.get 时也仅仅会得到当前 key 对应的值 。

当窗口触发时,传递给 emitWindowContents 时,也仅仅是当前 key 的值。( 对于同一个 key 而言是可以保证顺序的 )。故当 evictor 处理数据时也仅仅是当前 key 的值,而非整个 window 的值。故 evictor 处理的第一个数据不一定是 第一个或最后一个到达 window 的。而 window 也不保证元素顺序(进入window 窗口的顺序)

Flink中的窗口window)是在流处理过程中对数据进行分组和聚合的一种机制。窗口将流数据划分为有限大小的数据块,然后对每个窗口中的数据进行处理和计算。 在Flink中,窗口有两种类型:时间窗口和计数窗口。时间窗口根据事件发生的时间范围对数据进行划分,而计数窗口根据事件发生的次数对数据进行划分。 根据时间的划分方式,时间窗口可以分为滚动窗口和滑动窗口。滚动窗口将数据按照固定长度的时间间隔进行划分,比如每5分钟划分一个窗口。滑动窗口则以固定的时间间隔进行滑动,比如每隔1分钟滑动一次窗口。 对于计数窗口,可以定义固定数量的事件来进行划分,比如每10个事件划分一个窗口窗口操作可以包括聚合、计数、求和、最大值、最小值等操作。在窗口操作中,Flink提供了丰富的函数和操作符来实现不同的聚合和计算需求。 窗口操作可以通过窗口函数(window function)实现,窗口函数定义了对窗口中的数据进行聚合和处理的逻辑。 使用窗口操作可以提高流处理的性能和效率,通过将连续的数据划分为有限的窗口,可以减少计算的复杂性。同时,窗口操作也可以使得流处理应用更具可控性和灵活性。 在Flink中,窗口操作广泛应用于各种实时数据分析、实时计算和数据流处理的场景,如实时监测、实时查询、实时报警等。通过合理设置窗口大小和窗口滑动间隔,可以根据实际需求来进行数据处理和聚合,以满足不同的业务需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shengjk1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值