窗口分析

由于数据存在倾斜, 需要实现两阶段聚合, 这个时候萌生了连续使用eventtime window进行聚合的想法, 于是开始了以下的源码分析.

之前分析结论基本全错(精彩. 在此勘正下.

  1. 在flink里如果只打一个水位的话, 多个窗口是同时触发的. 所以你的代码如果都是5s的窗口, 窗口2会在10s的时候第一次处理数据, 因为第一次数据在窗口1中.
  2. 多个窗口之间不会丢失数据. 因为flink是存在barrier机制, 一批数据要走完窗口1和2才会接手下一批数据, 就算窗口1延迟过分大, 窗口也会在接到数据的时候再推动水位.
  3. 多次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中了.


JhonSmith
45 声望7 粉丝

这个人很懒, 什么也留不下.


« 上一篇
golang使用grpc
下一篇 »
superset使用odps