flink根据上游数据集是否为KeyedStream类型(将数据集按照Key分区),对应的Windows Assigner也会有所不同。上游数据集如果是KeyedStream类型,则调用DataStream API的windows()方法来指定Windows Assigner,数据会根据Key在不同的Task实例中并行分别计算,最后得出针对每个Key的统计结果。如果是Non-Keyed类型,则调用WindowsAll()方法来指定Windows Assigner,所有的数据都会在窗口算子中路由到一个Task中计算,并得到全局统计结果。
flink支持两种类型的窗口,一种是基于时间的窗口,一种是基于数量的窗口。
基于时间的窗口可分为四大类:滚动窗口(Tumbling Windows)、滑动窗口(Sliding Windows)、会话窗口(Session Windows)、全局窗口(Global Windows)。
(1)滚动窗口:根据固定时间或大小进行切分,且窗口和窗口之间的元素互不重叠。
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
Properties props = new Properties();
props.put("bootstrap.servers", "kafka:9092");
props.put("group.id", "test_flink");
DataStream<String> input = env.addSource(new FlinkKafkaConsumer010<String>
("test", new SimpleStringSchema(), props));
SingleOutputStreamOperator<Tuple2<String, String>> map = input.map(new MapFunction<String, Tuple2<String, String>>() {
@Override
public Tuple2<String, String> map(String value) throws Exception {
String[] strs = value.split(",");
return new Tuple2<>(strs[0], strs[1]);
}
});
WindowedStream<Tuple2<String, String>, Tuple, TimeWindow> window = map.keyBy(0).window(TumblingProcessingTimeWindows.of(Time.seconds(10)));
SingleOutputStreamOperator<Tuple2<String, String>> process = window.process(new ProcessWindowFunction<Tuple2<String, String>, Tuple2<String, String>, Tuple, TimeWindow>() {
@Override
public void process(Tuple tuple, Context context, Iterable<Tuple2<String, String>> elements, Collector<Tuple2<String, String>> out) throws Exception {
Iterator<Tuple2<String, String>> iterator = elements.iterator();
StringBuffer sb = new StringBuffer();
String key = null;
while (iterator.hasNext()){
Tuple2<String, String> ele = iterator.next();
if (key == null){
key = ele.f0;
}
sb.append(ele.f1);
}
out.collect(new Tuple2<String,String>(key,sb.toString()));
}
});
process.print();
kafka输入:
程序输出:
上面是用的TumblingProcessTimeWindows类,也可以
env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime);
WindowedStream<Tuple2<String, String>, Tuple, TimeWindow> window = map.keyBy(0).timeWindow(Time.seconds(10));
效果是一样的。
如果想使用事件时间,需要指定watermark和timestamp。这篇文章解释的非常清楚。感谢这位作者。
https://blog.csdn.net/xu470438000/article/details/83271123
除了指定watermark和timestamp之外,其他逻辑和上面的代码基本相同。
(2)滑动窗口:允许窗口数据发横重叠。滚动窗口可以看做是窗口大小与滑动步长相等的特殊滑动窗口。
WindowedStream<Tuple2<String, String>, Tuple, TimeWindow> window = map.keyBy(0).window(SlidingProcessingTimeWindows.of(Time.seconds(10),Time.seconds(5)));
(3)会话窗口:主要用于将某段时间内活跃度较高的数据聚合成一个窗口进行计算。窗口的触发条件是Session Gap,是指在规定的时间内如果没有数据进入窗口,那么就认为窗口结束,触发窗口进行计算。
WindowedStream<Tuple2<String, String>, Tuple, TimeWindow> window = map.keyBy(0).window(ProcessingTimeSessionWindows.withGap(Time.seconds(10)));
(4)全局窗口:窗口没有起始时间与结束时间,需要借助Triger来触发计算。
基于数量的窗口包括滚动窗口和滑动窗口。
滚动窗口:
WindowedStream<Tuple2<String, String>, Tuple, GlobalWindow> window = map.keyBy(0).countWindow(5);
滑动窗口:
WindowedStream<Tuple2<String, String>, Tuple, GlobalWindow> window = map.keyBy(0).countWindow(5,2);
基于数量的窗口本质上是一种全局窗口,触发条件就是窗口中的元素数量是否等于用户传入的参数。