Session Window 概述
Seesion Windows Assigner 按活动的会话对元素进行分组。Session Window与滚动窗口和滑动窗口相比,会话窗口不重叠,也没有固定的开始和结束时间。相反,当会话窗口在一段时间内没有接收到元素时,即发生了不活动间隔时,它将关闭。可以使用静态会话间隔或与会话间隙提取器功能配置会话窗口分配器,来定义了不活动期的时间长度。当此时间段到期时,当前会话关闭并将后续元素分配给新会话窗口。此时间段到期后,当前会话将关闭,后续元素将分配给新的会话窗口。
到此我们就能明白了,Session Window 的窗口的几大特性:
1、窗口的开始时间和结束时间不固定
2、当间隔时间到了,窗口会触发计算
3、Session Window 基于KeyBy
4、可以自定义静态的元素时间间隔,也可以通过一种提取器来进行设定
Session Winodw Join
通过上面我们所了解到的Session Window,我们接下来就接着继续探讨一番Session Winodw下的Join。
我想大家对于Flink 流流是不陌生的,如大家所指的Tumbling Window Join、Sliding Window Join、以及Interval Join。那么我们所熟知的Join,肯定要有两个key 来进行负责进行Join。也就是唯一键,或者是1对多的或者多对多的关系键,在这个情况下,在我们印象中的Join 应当就是这样的。
这个时候我们看到两个流进入了Join算子,这个时候,他们在进入之前还会做一步操作,也就是map将我们的元素进行打上tag,用以区分谁是左流,谁是右流,然后将两个流union起来,然后进入Join算子中来进行计算。
我们数据流入了我们的算子,那么他会怎么做呢?
@Override
public void processElement(StreamRecord<IN> element) throws Exception {
//为元素分配一个窗口,Session Window的话,分配的窗口大小为 [element 时间戳,最大间隔时间+element 时间戳]
final Collection<W> elementWindows = windowAssigner.assignWindows(
element.getValue(), element.getTimestamp(), windowAssignerContext);
//如果元素 可以分配窗口的话,那么这个值有用,否则就没用
boolean isSkippedElement = true;
//获取当前 key
final K key = this.<K>getKeyedStateBackend().getCurrentKey();
//判断是否是Session Window ,因为Session Window和其他window 多了一个合并窗口的操作,也就是假如来同一个键,但是他时间戳不停变大的话,只要符合间隔,窗口会无休止的变大。
if (windowAssigner instanceof MergingWindowAssigner) {
//第一次相同key的元素过来,是拿不到的,是一个空集合
//第二次进来,会放上一个窗口的映射关系。
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 {
// 将合并好的窗口,注册计时器,当watermark超过窗口最大大小-1ms 后执行,窗口触发操作
triggerContext.key = key;
triggerContext.window = mergeResult;
//如果水印尚未超过合并窗口的末尾,则仅注册一个计时器,这符合 onElement() 中的逻辑。
triggerContext.onMerge(mergedWindows);
//清理合并以前的window,并且清除 计时器
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);
}
});
// 是否有过期的 窗口,过去就删除
//(是不是EventTime 时间语义 && (清理时间 = 窗口最大时间-1ms <= watermark));
if (isWindowLate(actualWindow)) {
mergingWindows.retireWindow(actualWindow);
continue;
}
//说明被分配了窗口,故而设置成false
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());
//ACC acc = windowState.get();
//ACC internal = windowState.getInternal();
triggerContext.key = key;
triggerContext.window = actualWindow;
// 其实传递element 是没什么用处的 如果 watermark >= 当前窗口最大时间,将状态修改为Fire 否则就是 继续 不触发窗口
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 {
//不是Session Window的逻辑 不需要看
}
}
在Session Window 执行的时候步骤如下
1. 为左流或者右流的数据,分配一个窗口,这个窗口的大小为当前 [当前元素时间戳,间隔时间+当前元素时间戳)
2、然后判断是否有可以合并的窗口,第一条数据进来,是没有可以合并的窗口,第二条数据进来的时候,执行窗口合并操作,
然后窗口大小变化为 {两个窗口(Min(左边界1,左边界2),max(右边界1,右边界2))注意元素的时间戳要在窗口中才可以},也就是你的时间戳不可以跨窗口,同时会清理之前窗口的定时器,然后合并两个窗口的状态数据,也就是窗口中的元素
3、确保有状态,往状态里面添加值,状态存储了左流和右流共同的值,其中用一个tag 来进行分辨 one 和two。
4、判断是否触发窗口,触发修改状态,然后执行触发操作,循环遍历每个key 和他的window 状态,然后交给coGroup执行操作。清空这个窗口的值以及清理窗口。
这个时候我们也就知道了,为什么Session Window 是可以进行扩大窗口的概念了。
谢谢大家的观看。