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 构造方法也较为简单,就是检查一些参数是否合法,然后为其属性赋初值。
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 计算触发时候的逻辑。
- 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 代码几乎一样,只是触发条件不同。