Window概念
Window是无限数据流处理的核心,Window将一个无限的数据流拆分成有限大小的buckets桶,我们可以在这些桶上做计算操作。
Window可以分成两类:
- 时间窗口(Time Window):按照时间生成Window。
- 计数窗口(Count Window):按照指定的数据条数生成Window,与时间无关。
对于时间窗口,可以根据窗口实现原理的不同分成三类:
- 滚动窗口(Tumbling Window):将数据依据固定的窗口长度对数据进行切片。
特点:时间对齐,窗口长度固定,没有重叠。
- 滑动窗口(Sliding Window):滑动窗口是固定窗口的更广义的一种形式,滑动窗口由固定的窗口长度和滑动间隔组成。
特点:时间对齐,窗口长度固定,可以有重叠。
- 会话窗口(Session Window):由一系列事件组合一个指定时间长度的timeout间隙组成,类似于web应用的session,也就是一段时间没有接收到新数据就会生成新的窗口。
特点:时间无对齐。
时间窗口
package api.window;
import api.pojo.SensorReading;
import org.apache.commons.collections.IteratorUtils;
import org.apache.flink.api.common.functions.AggregateFunction;
import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.api.common.functions.ReduceFunction;
import org.apache.flink.api.java.tuple.Tuple;
import org.apache.flink.api.java.tuple.Tuple3;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.windowing.ProcessWindowFunction;
import org.apache.flink.streaming.api.functions.windowing.WindowFunction;
import org.apache.flink.streaming.api.windowing.assigners.EventTimeSessionWindows;
import org.apache.flink.streaming.api.windowing.assigners.SlidingProcessingTimeWindows;
import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows;
import org.apache.flink.streaming.api.windowing.assigners.TumblingProcessingTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
import org.apache.flink.util.Collector;
/**
* 时间窗口
*/
public class Window1_TimeWindow {
public static void main(String[] args) throws Exception {
// 创建执行环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 设置并行度为1
env.setParallelism(1);
// 从Socket读取数据
DataStream<String> inputStream = env.socketTextStream("localhost",9999);
// 转换成SensorReading类型
DataStream<SensorReading> dataStream = inputStream.map(new MapFunction<String, SensorReading>() {
@Override
public SensorReading map(String value) throws Exception {
String[] fields = value.split(",");
return new SensorReading(fields[0], new Long(fields[1]), new Double(fields[2]));
}
});
// 增量聚合函数:窗口不维护原始数据,只维护中间结果,每条增量数据到来就基于中间结果进行聚合计算
DataStream<Integer> incrementalStream = dataStream.keyBy("id")
// 窗口分配器
.timeWindow(Time.seconds(15))
// 滚动时间窗口:窗口长度为15秒
// .timeWindow(Time.seconds(15))
// .window(TumblingProcessingTimeWindows.of(Time.seconds(15)))
// 滑动时间窗口:窗口长度为15秒,滑动间隔为5秒
// .timeWindow(Time.seconds(15), Time.seconds(5))
// .window(SlidingProcessingTimeWindows.of(Time.seconds(15), Time.seconds(5)))
// 会话窗口:一段时间没有接收到新数据就会生成新的窗口,窗口长度为1分钟
// .window(EventTimeSessionWindows.withGap(Time.minutes(1)))
// 窗口函数:ReduceFunction,AggregateFunction
// ReduceFunction:让两个元素结合起来,产生一个相同类型的元素
// .reduce(new ReduceFunction<SensorReading>() {
// 与Reduce聚合中的ReduceFunction类似
// });
// AggregateFunction:比ReduceFunction更加通用,基于中间结果(累加器)进行增量
.aggregate(new AggregateFunction<SensorReading, Integer, Integer>() {
@Override
public Integer createAccumulator() {
// 创建累加器,初始值为0
return 0;
}
@Override
public Integer add(SensorReading value, Integer accumulator) {
// 累加器操作
return accumulator + 1;
}
@Override
public Integer getResult(Integer accumulator) {
// 输出结果
return accumulator;
}
@Override
public Integer merge(Integer acc1, Integer acc2) {
// 累加器合并操作,只有会话窗口的时候才会调用
return acc1 + acc2;
}
});
incrementalStream.print("增量聚合函数");
// 全窗口函数:窗口需要维护全部原始数据,窗口触发时对窗口内所有数据进行聚合计算
DataStream<Tuple3<String, Long, Integer>> fullWindowStream = dataStream.keyBy("id")
// 窗口分配器
.timeWindow(Time.seconds(15))
// 窗口函数:ProcessWindowFunction,WindowFunction
// ProcessWindowFunction:能获取窗口中包含的所有元素的迭代器以及有关元素所属的窗口信息的上下文对象
// .process(new ProcessWindowFunction<SensorReading, Object, Tuple, TimeWindow>() {
// 与下面的WindowFunction类似
// });
// WindowFunction:在某些可以试用ProcessWindowFunction的地方,也可以使用
.apply(new WindowFunction<SensorReading, Tuple3<String, Long, Integer>, Tuple, TimeWindow>() {
@Override
public void apply(Tuple tuple, TimeWindow window, Iterable<SensorReading> input, Collector<Tuple3<String, Long, Integer>> out) throws Exception {
// tuple为当前的key,window为上下文对象可获取当前的窗口信息,input为迭代器可获取窗口内所有输入数据,out为收集器可输出结果
String id = tuple.getField(0); // ID
Long windowEnd = window.getEnd(); // 窗口结束的时间戳
Integer count = IteratorUtils.toList(input.iterator()).size(); // 窗口内所有数据的个数
out.collect(new Tuple3<>(id, windowEnd, count));
}
});
fullWindowStream.print("全窗口函数");
// 执行任务
env.execute();
}
}
在终端启动netcat作为输入流:
nc -lk 9999
执行程序:
根据窗口中相同key的元素的数据量来触发执行,而不是输入的所有元素的总数。
计数窗口
package api.window;
import api.pojo.SensorReading;
import org.apache.flink.api.common.functions.AggregateFunction;
import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
/**
* 计数窗口
*/
public class Window2_CountWindow {
public static void main(String[] args) throws Exception {
// 创建执行环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 设置并行度为1
env.setParallelism(1);
// 从Socket读取数据
DataStream<String> inputStream = env.socketTextStream("localhost",9999);
// 转换成SensorReading类型
DataStream<SensorReading> dataStream = inputStream.map(new MapFunction<String, SensorReading>() {
@Override
public SensorReading map(String value) throws Exception {
String[] fields = value.split(",");
return new SensorReading(fields[0], new Long(fields[1]), new Double(fields[2]));
}
});
// 温度平均值:每收到2个相同key的数据就计算一次,每一次计算的窗口范围是10个元素
DataStream<Double> avgTempStream = dataStream.keyBy("id")
// 窗口分配器
.countWindow(10, 2)
// 滚动计数窗口:相同Key的元素数量为5个
// .countWindow(5)
// 滑动计数窗口:窗口长度为10个元素,滑动间隔为2个元素
// .countWindow(10, 2)
// 窗口函数
.aggregate(new MyAvgTemp());
avgTempStream.print("温度平均值");
// 执行任务
env.execute();
}
// 实现自定义的AggregateFunction
public static class MyAvgTemp implements AggregateFunction<SensorReading, Tuple2<Double, Integer>, Double> {
@Override
public Tuple2<Double, Integer> createAccumulator() {
// 创建累加器,初始值:总温度为0.0,数据量为0
return new Tuple2<>(0.0, 0);
}
@Override
public Tuple2<Double, Integer> add(SensorReading value, Tuple2<Double, Integer> accumulator) {
// 累加器操作:温度累加,数据量累加
return new Tuple2<>(accumulator.f0 + value.getTemperature(), accumulator.f1 + 1);
}
@Override
public Double getResult(Tuple2<Double, Integer> accumulator) {
// 输出结果:求温度平均值
return accumulator.f0 / accumulator.f1;
}
@Override
public Tuple2<Double, Integer> merge(Tuple2<Double, Integer> acc1, Tuple2<Double, Integer> acc2) {
// 累加器合并操作,只有会话窗口的时候才会调用
return new Tuple2<>(acc1.f0 + acc2.f0, acc1.f1 + acc2.f1);
}
}
}
在终端启动netcat作为输入流:
nc -lk 9999
执行程序:
其他可选API
// 将迟到的数据放入侧输出流
OutputTag<SensorReading> outputTag = new OutputTag<SensorReading>("late") {
};
SingleOutputStreamOperator<SensorReading> sumStream = dataStream.keyBy("id")
// window:用于分组的流,拥有同一个key值的数据将进入同一个窗口,可以设置并行度
.window(TumblingProcessingTimeWindows.of(Time.seconds(15)))
// windowAll:用于非分组的流,每进入一条数据就增加一个窗口,每个窗口处理一条数据,并行度始终为1
// .windowAll(TumblingEventTimeWindows.of(Time.seconds(15)))
// 触发器:定义窗口什么时候关闭,触发计算并输出结果
// .trigger() // 需要实现自定义的Trigger
// 移除器:定义移除某些数据的逻辑
// .evictor() // 需要实现自定义的Evictor
// 输出结果后先不关闭窗口,延迟等待设定的时间,如果有迟到的数据就不断更新输出结果
.allowedLateness(Time.minutes(1))
// 将迟到的数据放入侧输出流
.sideOutputLateData(outputTag)
// 窗口函数:sum,min,max,minBy,maxBy...
.sum("temperature");
// 获取侧输出流
sumStream.getSideOutput(outputTag).print();