原文链接:https://blog.csdn.net/m0_49826240/article/details/117296559
先说结论吧:
Flink 使用 timeWindow,如何使用周级别的滚动窗口
不需要自定义窗口分配器!不需要自定义窗口分配器!
第一种:开始时间为周一 00:00:00,结束时间为 周日 23:59:59
.window(TumblingEventTimeWindows.of(Time.days(7), Time.hours(-80)));
第二种:开始时间为周天 00:00:00,结束时间为 周六 23:59:59
.window(TumblingEventTimeWindows.of(Time.days(7), Time.hours(-104)));
至于为什么,往下看。阅读源码来解释
接到一个实时数据的需求,大致的内容就是实时统计每个店铺的一周GMV。
分析这个需求,明确的点:
1.周期长度为1周,也就是7天,7 x 24 个小时。
2.每个周期的开始时间:周一 00:00:00,结束时间:周日 00:00:00
按照日历中的划分,每周的开始是周日,结束是周六。这个我们不需要纠结这么多。
这个需要可以用两种方式来做
1.使用 keyby-process,对商家ID做key by,然后在process中做每周的GMV统计即可。
2.使用 timeWindow,使用支付时间开窗,累计一周的时间。
此处我们主要探讨第二种方案。
本文提及的这个需求,更实用使用滚动窗口计算,下面这行代码就是平时实时的写法。
.window(TumblingEventTimeWindows.of(Time.days(7), Time.hours(-8)));
此时可以发现,Time.day() 已经是提供的最大时间单位了,并没有想要使用 Time.week()。那么使用 Time.day(7)呢?
行不行,试一下就知道了,做个小实验,读取端口 9910 数据,使用 TumblingEventTimeWindows.of(Time.days(7), Time.hours(-8))
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
DataStreamSource localhost = env.socketTextStream("localhost", 9910, "\n");
SingleOutputStreamOperator d1 = localhost
.flatMap(new FlatMapFunction() {
@Override
public void flatMap(String value, Collector out) throws Exception {
try {
DataEntity entity = new DataEntity();
entity.setKey(value);
entity.setCount(1L);
entity.setTime(System.currentTimeMillis());
out.collect(entity);
} catch (Exception ignored) {
}
}
});
SingleOutputStreamOperator reduce = d1
.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor(Time.seconds(2)) {
@Override
public long extractTimestamp(DataEntity element) {
return element.getTime();
}
})
.keyBy(DataEntity::getKey)
.window(TumblingEventTimeWindows.of(Time.days(7), Time.hours(-8)))
.trigger(CountTrigger.of(1L))
.process(new ProcessWindowFunction() {
@Override
public void process(String s, Context context, Iterable elements, Collector out) throws Exception {
long start = context.window().getStart();
long end = context.window().getEnd();
String ss = TimeTranUtils.tranTimeToTime(start + "");
String ee = TimeTranUtils.tranTimeToTime(end + "");
for (DataEntity e: elements) {
System.out.println(e.getKey() + ", " + e.getCount() + ", " + ss +" - " + ee);
}
}
});
env.execute("tttttt");
查看控制台打印的数据,窗口开始时间是 2021-05-23 00:00:00,也就是今天的 00:00:00 时刻,结束时间是7日后。所以这个是不可行。周窗口不能以当前时间为开始时间。
这样不行,那我们想想看,如何改下窗口分配器了。(窗口分配器,就是将每条数据根据时间分配到对应的窗口上)
既然都是滚动窗口,那么看下官方的提供的分配器吧。找到 start 取值那一行,这个就是该数据所属窗口的开始时间。
public class TumblingEventTimeWindows extends WindowAssigner
找到了这个start,start + size 就是 endTime 了。
那,我们将我们自己的数据,每一条都计算到所属的周一 00:00:00 的时间戳,那么是不是就可以得到 startTime 了?
网上有很多自定义分配器的方式,思路都差不多,但是都是通过转换到 Date 对象一类来做的。有没有简捷方式呢?比如直接通过简单算术计算?
那肯定是有的,一周是 604800000 毫秒,时间戳 = 0 的时间是 1970-01-01 08:00:00,星期四早上8点,离本周一 凌晨00:00:00 的偏移量是 3 * 24 * 3600000 + 28800000 毫秒。
那么根据当前时间的时间戳计算本周一 00:00:00 的时间戳的公式为:
mon =(currentTime + (3 * 24 * 3600000 + 28800000) / 604800000 ) * 604800000,各位看官可以试下这个公式,currentTime 可以是任意时间,都可以得到本周的周一开始时间。
mon 就是 startTime,endTime = mon + 604800000;
如果这就完了,那就是没有认真思考。
官方提供了这个计算公式:
// 计算 startTime
long start = TimeWindow.getWindowStartWithOffset(timestamp, offset, size);
// 看下 getWindowStartWithOffset
/**
* Method to get the window start for a timestamp.
*
* @param timestamp epoch millisecond to get the window start.
* @param offset The offset which window start would be shifted by.
* @param windowSize The size of the generated windows.
* @return window start
*/
public static long getWindowStartWithOffset(long timestamp, long offset, long windowSize) {
return timestamp - (timestamp - offset + windowSize) % windowSize;
}
其实和我的计算结果是一样的,只不过人家是可以根据提供的 offset 和 windwiSIze 来确定 startTime。
本想着是自己封住一个分配器,但是这个地方真的启示我了。
我的周窗口,size = 7day,offset = 3 * 86400000 + 28800000 = 80 hour
那么现在直接改 TumblingEventTimeWindows 参数就可以,
.window(TumblingEventTimeWindows.of(Time.days(7), Time.hours(-80)));
测试很完美。
还是需要看源码