Flink_窗口的底层实现逻辑

目的

写这篇文目的是为了加深对窗口和 watermark 的理解。
先感谢这位博主的辛勤劳动。我做的分析就是基于这位大侠做的。

下面上正题。

正题

窗口总体流程

窗口是用来切割无线流的,它把无线流切分成有限个碎片,通过计算碎片来计算流的某些性质。就像积分计算求球的体积。它将从球新到表面扇柱体是一个正方体,然后使用极限的思路,然后就计算出球体的体积。
在这里插入图片描述
根据不同的需求,我们有下面几种窗口类型。

  1. 数据流是无限的,我们可以统计每 n 个单位时间内的一些统计值。这就是 Tumbling 窗口。
  2. 我们也可以每隔 30 秒,统计一下前1 分钟内的窗口中数据的一些统计统计值。着就是 sliding 窗口。
  3. 我们还可以当每有车的时候不统计,有车的时候才统计。这就是 session 窗口。其中关键的概念就是 gap ,时间间隔超过了 gap ,那么就会触发。

弄清楚,tumbling session count sliding 在 Flink 的实现逻辑,就先要弄清楚 WindowAssigner 抽象类以及它下面的实现类。

先来理解一下 window assigner(WindowAssigner) 这个类。它负责为每个窗口来分发 record 。一个 record 可以到一个或者多个窗口中去。

先来看看它有那些方法:

  1. assignerWindow( T , long ,WindowAssignerContent) : 返回一个集合,这个集合包含的元素是应该分发给某些窗口的。
  2. getDefualtTrigger():获取默认的 trigger 。

下面来具体的分析一下 WindowAssigner 下面的实现类。

TumblingEventTimeWindowAssigner 的窗口划分规则

第一个就是我们熟悉的 TumblingEventWindowAssigner ,然后关键的代码如下所示,

在这里插入图片描述
我们得到的结论:

  1. TumblingEventTimeWindowAssigner 是根据每个元素的 timestamp 来找到它对应的窗口。
  2. 找窗口的算法在 TimeWindow.getWindowStartWithoffset() 里面实现的。
  3. 算法是这样的,record 里面的 event time - ( eventtime - offset + 窗口大小 )%窗口的大小。
    我们将变量命名简化:
    record 里面的 event time :timestampt
    offset:偏移量。
    窗口大小:winSize
    所以上面的公式可以写成:timestamp - (timestamp - offset + winSize)%winSize。
    我们知道,timestamp 是 long 的整数,这个数是从计算机元年开始计算的一个正整数。
    我们可以将 timestamp 写成 n*winSize + m 这种写法,其中,n 是 timiestamp 里面有多少个
    winSize,m 是余数,是减掉 n 个 winSize 后,timestamp 余多少。下面的图示:

在这里插入图片描述

假设,offset 先为 0,公式就变成了 start = timestamp - ( timestamp + winSize )%winSize
那么,公式就变成了:

start = n*winSize + m - ( n*winSize + m + winSize )%winSize = n*winSize + m - ( (n+1)/winSize + m )%winSize

按照 % 取余这种运算逻辑,( (n+1)/winSize + m )%winSize= m ,最后,上面的公式为:

start = n*winSize,可见当没有 offset 的时候。start 是从整数开始的。举个例子,

1461756862000 这个是例子中的第一个 record 的时间戳。我们来看一下它是几点。我设置的 tumbling 时间是 3 s ,60%3 = 0 ,所以 3 是可以背 60 整除的,所以窗口的切割如下所示:

[ 0 , 3 ) , [ 3 , 6 ) , [ 6 , 9 ) ,[ 9 , 12 ), ... , [ n*3 , 3 + n*3 ] , 其中,n >= 0 .

timestamp 的整数不是落到那个区间的开始位置,这个元素就在这个区间。

然后,如果 offset 不等于 0 呢?不等于 0 ,不等于 0 的话,根据公式的推导,如下所示

 start = timestamp - ( timestamp + winSize )%winSize
         = n*winSize + m - ( n*winSize + m - offset + winSize )%winSize
         = n*winSize + m - ( (n+1)*winSize + m - offset )%winSize 
         = n*winSize + m - ( m - offset)
         = n*winSize + offset  

也就是如果我还是以 3 为窗口的大小,offset = 1 , 则得到的窗口划分如下所示:

[ 0 , 3 ) , [ 3 , 6 ) , [ 6 , 9 ) ,[ 9 , 12 ), ... , [ n*3 , 3 + n*3 ] , 其中,n >= 0 .

当 offset < winSize 的时候,window 的不同区间为在各个区间的开始和结束哪里加 1 .

[ 1 , 4 ) , [ 4 , 7 ) , [ 7 , 10 ) ,[ 10 , 13 ), ... , [ 1 + n*3 , 4 + n*3 ] , 其中,n >= 0 .

如果 offset > winSize ,则我们可以将 offset 分解为 offset = k*winSize + i ; i 为余数。则,根据上面的公式,假如 offset 为 4, 则 i = 1 ,k = 1 ,则 window 的不同分区为,

[ 4 , 7 ) , [ 7 , 10 ) , [ 10 , 13 ) ,[ 13 , 16 ), ... , [ 4 + n*3 , 7 + n*3 ] , 其中,n >= 0 .

ok 先把的窗口的划分讲清楚了,接下来在讲讲 trigger 的时机。这就和 watermark 相关了。

TumblingProcessingTimeWindowAssigner 的窗口划分规则

TumblingProcessingTimeWindowAssiger 的规则和 event-time 是相同的,不同的是 timestamp 是服务器时间。也就根据 record 到达窗口的时间来划分,窗口,如果 winSize 还是 3。第一个 record 到达窗口的时间为 currrentTimeStamp , 那么下一个 3 秒钟,不管之后来多少个 record ,0 或者更多把,都会触发的。

SlidingEventTimeWindowAssigner 的窗口划分规则

指定窗口的代码片段为:

long lastStart = TimeWindow.getWindowStartWithOffset(timestamp, offset, slide);
for (long start = lastStart;
	start > timestamp - size;
	start -= slide) {
	windows.add(new TimeWindow(start, start + size));
}
return windows;

从代码中可以看到,也是使用了 TimeWindow.getWindowStartWithOffset 这个方法,只是这个返回的就不是一个窗口了,是多个窗口。这也是合理的,滑动窗口吗?每过一个时间长度,就统计前 n 个时间长度。下面我们来讨论一下,一个 record 要经理多少个窗口呢?

假设,窗口的大小是 N ,每次滑动的大小是 M ,那 滑动 N/M 次,窗口就不能覆盖到 record 了。所以在计算 record 的窗口的时候,先使用 TimeWindow.getWindowStartWithOffset 计算最后一次窗口的位置,使用滑动

在这里插入图片描述
上面的图最终的效果,下面的图才是,Flink 底层数据结构的划定 slide window 使用的数据结构。和算法。

在这里插入图片描述

EventTimeSessionWindows assigner 的窗口指定规则 标题

EventTimeSessionWindows 这个类是继承自 MergingWindowAssigner 。实际上他就一个可以合并的窗口,所以这里我要重点的看看合并的逻辑。

EventTimeSessiongWindow 中的 mergeWindows 这个方法里面使用的是 TimeWindow.mergeWindow 这个方法所以重点看这个方法里面的逻辑。

 先按照窗口的开始时间戳为 window 排个序。
 Collections.sort(sortedWindows, new Comparator<TimeWindow>() {
		@Override
		public int compare(TimeWindow o1, TimeWindow o2) {
			return Long.compare(o1.getStart(), o2.getStart());
		}
	});
        // 这里开始合并窗口。
	for (TimeWindow candidate: sortedWindows) {
		if (currentMerge == null) {
			currentMerge = new Tuple2<>();
			currentMerge.f0 = candidate;
			currentMerge.f1 = new HashSet<>();
			currentMerge.f1.add(candidate);
            // currentMerge.f0.intersects(candidate) 
            // intersects 这个方法判断 currentMerge 窗口是否包含
            // candidate 这个窗口,具体的实现,请看下面的代码
		} else if (currentMerge.f0.intersects(candidate)) {
			currentMerge.f0 = currentMerge.f0.cover(candidate);
			currentMerge.f1.add(candidate);
		} else {
               // 出现一个 gap 后,就讲 currentMerge 放如到
               // merged list 里面。然后进入下一轮的合并。
			merged.add(currentMerge);
			currentMerge = new Tuple2<>();
			currentMerge.f0 = candidate;
			currentMerge.f1 = new HashSet<>();
			currentMerge.f1.add(candidate);
		}
	}
        // 最后再将最后一个合并好的窗口放如到 merged list 里面。
 		if (currentMerge != null) {
		merged.add(currentMerge);
	}
      // 最后,将 Tuple 里面 f1 和 f2 合并。
 		for (Tuple2<TimeWindow, Set<TimeWindow>> m: merged) {
		if (m.f1.size() > 1) {
			c.merge(m.f1, m.f0);
		}
	}

intersects 方法的具体实现如下所示:

public boolean intersects(TimeWindow other) {
	return this.start <= other.end && this.end >= other.start;
}

cover 方法的具体逻辑:

public TimeWindow cover(TimeWindow other) {
	return new TimeWindow(Math.min(start, other.start), Math.max(end, other.end));
}

ProcessingTimeSessionAssigner 的逻辑和 event-time 相同。

DynamicEventTimeSessionWindows assigner 指定窗口的规则

这个是在 event-time assigner 的基础上,从 record 中取出 gap 值得,请看下面得代码:

public Collection<TimeWindow> assignWindows(T element, long timestamp, WindowAssignerContext context) {
	long sessionTimeout = sessionWindowTimeGapExtractor.extract(element);
	if (sessionTimeout <= 0) {
		throw new IllegalArgumentException("Dynamic session time gap must satisfy 0 < gap");
	}
	return Collections.singletonList(new TimeWindow(timestamp, timestamp + sessionTimeout));
}

sessionWindowTimeGapExtractor.extract 这个接口啊,和奇怪,我们没有找到它的实现类,接口对 extract 方法的描述如下所示:

Extracts the session time gap.

看这意思是是要,我们程序员来指定在哪里取出 gap 了。
DynamicProcessingTimeSessionWindows assigner ,和 event-time 的逻辑是一样的。

GlobalWindow assigner 的指定规则标题

这个逻辑简单,就是所有的 record 都放 GlobalWindow 这个里面来放。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值