Streaming 流式计算是一种被设计用于处理无限数据集的数据处理引擎,而无限数据集是指一种不断增长的本质上无限的数据集,而 window 是一种切割无限数据为有限块进行处理的手段。
Window 是无限数据流处理的核心,Window 将一个无限的 stream 拆分成有限大小的”buckets”桶,我们可以在这些桶上做计算操作。
Window可以分为两类:
ConutWindow:按照指定的数据条数生成一个Window,与时间无关。
TimeWindow:按照时间生产Window。
根据TimeWindow的窗口实现原理可以分为三类:滚动窗口(Tumbling Window)、滑动窗口(Sliding Window)和会话窗口(Session Window)。
将数据依据固定的窗口长度对数据进行切片。
特点:时间对齐,窗口长度固定,没有重叠。
使用场景:适合做BI统计(每个时间段的聚合计算)
滚动窗口分配器将每个元素分配到一个指定窗口大小的窗口中,滚动窗口有一个固定的大小,并且不会出现重叠。例如创建一个五分钟大小的滚动窗口。
滑动窗口是固定窗口的更广义的一种形式,滑动窗口由固定的窗口长度的滑动间隔组成。
特点:时间对齐,窗口长度固定,可以有重叠
适用场景:对最近一个时间段内的数据统计(最近时间段的触发情况)
滑动窗口分配器将元素分配到固定长度的窗口中,与滚动窗口类似,窗口的大小由窗口大小参数来配置,另一个窗口滑动参数控制滑动窗口开始的频率。因此,滑动窗口如果滑动参数小于窗口大小的话,窗口是可以重叠的,在这种情况下元素会被分配到多个窗口中。例如,你有 10 分钟的窗口和 5 分钟的滑动,那么每个窗口中 5 分钟的窗口里包含着上个 10 分钟产生的数据,
由一系列事件组合一个指定时间长度的 timeout 间隙组成,类似于 web 应用的session,也就是一段时间没有接收到新数据就会生成新的窗口。
特点:时间无对齐
适用场景:特定单对单会话
session 窗口分配器通过 session 活动来对元素进行分组,session 窗口跟滚动窗口和滑动窗口相比,不会有重叠和固定的开始时间和结束时间的情况,相反,当它在一个固定的时间周期内不再收到元素,即非活动间隔产生,那个这个窗口就会关闭。一个 session 窗口通过一个 session 间隔来配置,这个 session 间隔定义了非活跃周期的长度,当这个非活跃周期产生,那么当前的 session 将关闭并且后续的元素将被分配到新的 session 窗口中去。
package com.ts.window;
import com.ts.flink.SensorReading;
import org.apache.commons.collections.IteratorUtils;
import org.apache.flink.api.common.functions.AggregateFunction;
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.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.windowing.WindowFunction;
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 TimeWindowApi {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
// socket 文字流
DataStream<String> inputStream = env.socketTextStream("localhost", 7777);
// 转换成SensorReading类型
DataStream<SensorReading> dataStream = inputStream.map(line -> {
String[] fields = line.split(",");
return new SensorReading(fields[0], new Long(fields[1]), new Double(fields[2]));
});
// 1. 增量聚合函数 用于统计统一id的条数
DataStream<Integer> resultStream = dataStream.keyBy("id")
// 开窗15s
.timeWindow(Time.seconds(15))
// 自定义聚合函数
.aggregate(new AggregateFunction<SensorReading, Integer, Integer>() {
// 累加器 类似于sum 需要给一个初始值
@Override
public Integer createAccumulator() {
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 a, Integer b) {
return a + b;
}
});
resultStream.print();
// 2. 全窗口函数
SingleOutputStreamOperator<Tuple3<String, Long, Integer>> resultStream2 = dataStream.keyBy("id")
.timeWindow(Time.seconds(15))
// apply用于对窗口内的数据进行全量聚合
.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 {
// 得到分组的id
String id = tuple.getField(0);
// 获取窗口的结束时间
Long windowEnd = window.getEnd();
// 得到每组的数据条数
Integer count = IteratorUtils.toList(input.iterator()).size();
System.out.println("a:"+count);
// 输出结果
out.collect(new Tuple3<>(id, windowEnd, count));
}
});
resultStream2.print();
env.execute();
}
}
package com.ts.window;
import com.ts.flink.SensorReading;
import org.apache.flink.api.common.functions.AggregateFunction;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
public class CountWindowApi {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
// socket文本流
DataStream<String> inputStream = env.socketTextStream("localhost", 7777);
// 转换成SensorReading类型
DataStream<SensorReading> dataStream = inputStream.map(line -> {
String[] fields = line.split(",");
return new SensorReading(fields[0], new Long(fields[1]), new Double(fields[2]));
});
// 开计数窗口测试
SingleOutputStreamOperator<Double> avgTempResultStream = dataStream.keyBy("id")
// 开一个十条数据 每两条滑动一次的窗口
.countWindow(10, 2)
// 自定义聚合函数
.aggregate(new MyAvgTemp());
avgTempResultStream.print();
env.execute();
}
public static class MyAvgTemp implements AggregateFunction<SensorReading, Tuple2<Double, Integer>, Double> {
// 初始值为一个2个元素的tuple,f0为double类型,f1为int类型
@Override
public Tuple2<Double, Integer> createAccumulator() {
return new Tuple2<>(0.0, 0);
}
// 聚合条件是f0累加温度的值,f1累加次数
@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> a, Tuple2<Double, Integer> b) {
return new Tuple2<>(a.f0 + b.f0, a.f1 + b.f1);
}
}
}
package com.ts.window;
import com.ts.flink.SensorReading;
import org.apache.flink.streaming.api.TimeCharacteristic;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.AscendingTimestampExtractor;
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.util.OutputTag;
public class EventTimeWindowApi {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 指定Stream的Time方式 修改为事件时间
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
// 设置Stream数据的水位线
env.getConfig().setAutoWatermarkInterval(100);
// socket文本流
DataStream<String> inputStream = env.socketTextStream("localhost", 7777);
// 转换成SensorReading类型,分配时间戳和watermark
DataStream<SensorReading> dataStream = inputStream.map(line -> {
String[] fields = line.split(",");
return new SensorReading(fields[0], new Long(fields[1]), new Double(fields[2]));
})
// 升序数据设置事件时间和watermark
.assignTimestampsAndWatermarks(new AscendingTimestampExtractor<SensorReading>() {
@Override
public long extractAscendingTimestamp(SensorReading element) {
return element.getTimestamp() * 1000L;
}
})
// 乱序数据设置时间戳和watermark
.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor<SensorReading>(Time.seconds(2)) {
@Override
public long extractTimestamp(SensorReading element) {
return element.getTimestamp() * 1000L;
}
});
OutputTag<SensorReading> outputTag = new OutputTag<SensorReading>("late") {
};
// 基于事件时间的开窗聚合,统计15秒内温度的最小值
SingleOutputStreamOperator<SensorReading> minTempStream = dataStream.keyBy("id")
.timeWindow(Time.seconds(15))
.allowedLateness(Time.minutes(1))
.sideOutputLateData(outputTag)
.minBy("temperature");
minTempStream.print("minTemp");
minTempStream.getSideOutput(outputTag).print("late");
env.execute();
}
}
// 如果要自定义triggrer,就要重写一个自己的trigger
.trigger(new Trigger<SensorReading, GlobalWindow>() {
// 方法会在窗口中每进入一条数据的时候调用一次
@Override
public TriggerResult onElement(SensorReading sensorReading, long l, GlobalWindow globalWindow, TriggerContext triggerContext) throws Exception {
return null;
}
// 方法会在一个ProcessingTime定时器触发的时候调用
@Override
public TriggerResult onProcessingTime(long l, GlobalWindow globalWindow, TriggerContext triggerContext) throws Exception {
return null;
}
// 方法会在一个EventTime定时器触发的时候调用
@Override
public TriggerResult onEventTime(long l, GlobalWindow globalWindow, TriggerContext triggerContext) throws Exception {
return null;
}
// 方法会在窗口清除的时候调用
@Override
public void clear(GlobalWindow globalWindow, TriggerContext triggerContext) throws Exception {
}
})
.evictor(new Evictor<SensorReading, TimeWindow>() {
// 在计算操作执行前执行evict操作
@Override
public void evictBefore(Iterable<TimestampedValue<SensorReading>> iterable, int i, TimeWindow timeWindow, EvictorContext evictorContext) {
}
// 在计算操作执行后执行evict操作
@Override
public void evictAfter(Iterable<TimestampedValue<SensorReading>> iterable, int i, TimeWindow timeWindow, EvictorContext evictorContext) {
}
})
allowedLateness() —— 允许处理迟到的数据
sideOutputLateData() —— 将迟到的数据放入侧输出流
getSideOutput() —— 获取侧输出流
// 创建一条侧输出流
OutputTag<SensorReading> outputTag = new OutputTag<SensorReading>("late") {
};
// 开窗15s,允许数据迟到1min,迟到的数据会放到侧输出流
SingleOutputStreamOperator<SensorReading> sumStream = dataStream.keyBy("id")
.timeWindow(Time.seconds(15))
.allowedLateness(Time.minutes(1))
// 放入侧输出流
.sideOutputLateData(outputTag)
.sum("temperature");
// 从侧输出流获取迟到的数据
sumStream.getSideOutput(outputTag).print("late");