窗口分析
由于数据存在倾斜, 需要实现两阶段聚合, 这个时候萌生了连续使用eventtime window进行聚合的想法, 于是开始了以下的源码分析.
之前分析结论基本全错(精彩. 在此勘正下.
- 在flink里如果只打一个水位的话, 多个窗口是同时触发的.
所以你的代码如果都是5s的窗口, 窗口2会在10s的时候第一次处理数据, 因为第一次数据在窗口1中
. - 多个窗口之间不会丢失数据. 因为flink是存在barrier机制, 一批数据要走完窗口1和2才会接手下一批数据, 就算窗口1延迟过分大, 窗口也会在接到数据的时候再推动水位.
- 多次keyby也不会导致数据丢失,
首先, flink在并行间处理的时候, 窗口水位的推动, 是根据并行间最低水位来进行推动的. 窗口1如果fire, 证明最小水位已经达标, 就算你keyby到再多分区, 窗口2汇总水位的时候, 也会立刻fire
.
window运算流程
流水账, 个人记录用
先来说说一下window窗口的运算流程
当你打了window函数后, 便有了一个windowOperator
对象, 它会持有一个internalTimerService
成员变量, 用来处理window相关的事情.
@Override
public void open() throws Exception {
super.open();
this.numLateRecordsDropped = metrics.counter(LATE_ELEMENTS_DROPPED_METRIC_NAME);
timestampedCollector = new TimestampedCollector<>(output);
internalTimerService =
getInternalTimerService("window-timers", windowSerializer, this);
...
一条数据过来的时候, 会走到WindowOperator的processElement中
@Override
public void processElement(StreamRecord<IN> element) throws Exception {
//这里会记录元素的时间戳
final Collection<W> elementWindows = windowAssigner.assignWindows(
element.getValue(), element.getTimestamp(), windowAssignerContext);
...
//这里会去trigger里去判断元素是否触发trigger
TriggerResult triggerResult = triggerContext.onElement(element);
...
}
之后会走进EventTimeTrigger的onElement中
@Override
public TriggerResult onElement(Object element, long timestamp, TimeWindow window, TriggerContext ctx) throws Exception {
if (window.maxTimestamp() <= ctx.getCurrentWatermark()) {
// if the watermark is already past the window fire immediately
return TriggerResult.FIRE;
} else {
//正常情况下, 都会走到这里, 进行数据时间的记录.
ctx.registerEventTimeTimer(window.maxTimestamp());
return TriggerResult.CONTINUE;
}
}
在eventtime时间模式, 水位会以200毫秒的方式进行触发推动, 经过一些流程后, watermark会走到StausWatermarkValve中
private void findAndOutputNewMinWatermarkAcrossAlignedChannels() {
long newMinWatermark = Long.MAX_VALUE;
boolean hasAlignedChannels = false;
// determine new overall watermark by considering only watermark-aligned channels across all channels
for (InputChannelStatus channelStatus : channelStatuses) {
if (channelStatus.isWatermarkAligned) {
hasAlignedChannels = true;
newMinWatermark = Math.min(channelStatus.watermark, newMinWatermark);
}
}
// we acknowledge and output the new overall watermark if it really is aggregated
// from some remaining aligned channel, and is also larger than the last output watermark if (hasAlignedChannels && newMinWatermark > lastOutputWatermark) {
lastOutputWatermark = newMinWatermark;
//他会取所有channel(可以理解成并行)中的最低水位, 去进行水位推进的操作
outputHandler.handleWatermark(new Watermark(lastOutputWatermark));
}
}
之后进入到StreamInputProcessor, 这里加了把锁, 保证多个窗口顺序执行水位操作.
@Override
public void handleWatermark(Watermark watermark) {
try {
synchronized (lock) {
watermarkGauge.setCurrentWatermark(watermark.getTimestamp());
operator.processWatermark(watermark);
}
} catch (Exception e) {
throw new RuntimeException("Exception occurred while processing valve output watermark: ", e);
}
}
processWatermark之后会进入InternalTimerServiceImpl中, 执行advanceWatermark.
public void advanceWatermark(long time) throws Exception {
currentWatermark = time;
InternalTimer<K, N> timer;
while ((timer = eventTimeTimersQueue.peek()) != null && timer.getTimestamp() <= time) {
eventTimeTimersQueue.poll();
keyContext.setCurrentKey(timer.getKey());
//这里 触发了水位推动
triggerTarget.onEventTime(timer);
}
}
在WindowOperator中, 水位终于推动了, 开始进行数据处理了.
@Override
public void onEventTime(InternalTimer<K, W> timer) throws Exception {
...
if (triggerResult.isFire()) {
ACC contents = windowState.get();
if (contents != null) {
//推动了
emitWindowContents(triggerContext.window, contents);
}
}
...
}
之后就会进入你自己的后续task中了.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。