flink 多窗口分析

窗口分析

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

背景

flink的窗口函数, 是在流的基础上, 在一段时间内进行聚合, 对于eventtime而言, 你需要设置一个watermark进行提取, 最简单的形式如下:

datastream.assignTimestampsAndWatermarks(\your watermark\)
          .map(\map function\)
          .keyby(0)
          .window(TumblingEventTimeWindows.of(Time.seconds(5)))
          .process(\process function\)

这个时候如果要二次聚合的话, 就需要再次keyby一下, 但process只能获取一条数据. 我想获得一批数据的话, 需要打开第二个窗口. 于是就有了以下的代码

datastream.assignTimestampsAndWatermarks(\your watermark\)
          .map(\map function\)
          .keyby(0)
          .window(TumblingEventTimeWindows.of(Time.seconds(5)))
          .process(\process function\)
          .keyby(1)
          .window(TumblingEventTimeWindows.of(Time.seconds(5)))

运行之后, 非常完美, 两个窗口一起触发. 数据没有任何问题. 可我还是有疑惑, window2的5秒是从window1处理完后, 还是从数据从watermark里出来来算?

经过一轮源码观察, 发现以下事实:

  1. 多个window公用一个水位线的时候, 多个window的水位上移同时进行, 但是在一个slot里有一个lock, 所以第二个窗口才会等第一个窗口的数据全部处理完毕, 才进行水位推进.
@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);  
  }  
}
  1. 这个lock只是在单个slot中而言, 如果你重新keyby, 数据转移到其他的slot中, 这个lock数据可能会丢失.
  2. 如果两个window间添加其他高延时操作, 也可能导致数据丢失(比如加一个map thread sleep.)

结论就是, 请避免多窗口聚合, 或者打第二个水位.

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 element) throws Exception {  
    //这里会记录元素的时间戳
    final Collection 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 timer;  
  
 while ((timer = eventTimeTimersQueue.peek()) != null && timer.getTimestamp() <= time) {  
      eventTimeTimersQueue.poll();  
  keyContext.setCurrentKey(timer.getKey()); 
  //这里 触发了水位推动 
  triggerTarget.onEventTime(timer);  
  }  
}

在WindowOperator中, 水位终于推动了, 开始进行数据处理了.

@Override  
public void onEventTime(InternalTimer timer) throws Exception {  
    ...
    if (triggerResult.isFire()) {  
       ACC contents = windowState.get();  
     if (contents != null) { 
          //推动了 
          emitWindowContents(triggerContext.window, contents);  
      }  
    }
    ... 
}

之后就会进入你自己的后续task中了.

你可能感兴趣的:(flink)