水善利万物而不争,处众人之所恶,故几于道
Flink作为一个流式处理引擎,被设计用来处理无限数据集,理论上来说,无限数据集是一种不断产生,源源不断的数据集,说白了就是你不知道这个数据流它啥时候结束,这就是无限数据集。
流式计算的思想是每来一个数据我就直接处理,而不用等,因此他非常适合在实时性要求比较高的场景下使用。
在流处理的场景下,如果我们想要统计过去某个时间段或过去多少条数据的指标时,就需要用到窗口,在Flink中,窗口(window)可以将流划分为有限块进行处理,Flink将这些有限的块抽象为“存储桶(bucket)”,我们可以在这些所谓的桶上做计算,也就实现了无限数据的有限计算。
窗口(Window)是处理无界流的关键所在。窗口可以将数据流装入大小有限的“桶”中,再对每个“桶”加以处理。
窗口的声明周期是:一个窗口在第一个属于它的元素到达时就会被创建,然后在时间(event 或 processing time) 超过窗口的“结束时间戳 + 用户定义的 allowed lateness (可容忍的迟到时间)”时 被完全删除。Flink 仅保证删除基于时间的窗口,其他类型的窗口不做保证, 比如全局窗口(Global Windows)。 例如,对于一个基于 event time 且范围互不重合(滚动)的窗口策略, 如果窗口设置的时长为五分钟、可容忍的迟到时间(allowed lateness)为 1 分钟, 那么第一个元素落入 12:00 至 12:05 这个区间时,Flink 就会为这个区间创建一个新的窗口。 当 watermark 越过 12:06 时,这个窗口将被摧毁。关于窗口的详细介绍查看->官方对于窗口的介绍
window(TumblingProcessingTimeWindows.of(Time.seconds(10)))
,参数是时间滚动窗口大小。10秒滚动一个窗口
滚动窗口将元素分发到指定大小的窗口。滚动窗口的大小是固定的,且个窗口之间没有空隙,不会重叠。比如说,如果你指定了滚动窗口的大小为5分钟,那么每5分钟就会有一个窗口被计算,且一个新的窗口被创建。如下图所示:
示例代码:
public class Flink01_Window_Time {
public static void main(String[] args) {
Configuration conf = new Configuration();
conf.setInteger("rest.port",1000);
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(conf);
env.setParallelism(2);
env
.socketTextStream("hadoop101",9999)
.map(line->{
String[] datas = line.split(",");
return new WaterSensor(
datas[0],
Long.valueOf(datas[1]),
Integer.valueOf(datas[2])
);
})
.keyBy(WaterSensor::getId)
// 定义一个长度为10s的滚动窗口 每隔10s滚动一次
.window(TumblingProcessingTimeWindows.of(Time.seconds(10)))
.process(
// 泛型的含义是:输入元素的类型,输出元素的类型,key的类型,窗口类型
new ProcessWindowFunction<WaterSensor, Object, String, TimeWindow>() {
// 在窗口关闭的时候触发一次
@Override
public void process(String s, // key ,keyBy之后的key
Context context, //上下文对象:里面封装了一些信息,比如窗口开始时间,结束时间,定时服务器...
Iterable<WaterSensor> elements, // 存储了这个窗口内所有的元素
Collector<Object> out) throws Exception {
// 把Iterable中所有的元素取出并存入到 list 集合中
List<WaterSensor> list = AnqclnUtil.toList(elements);
// 获取窗口的相关信息,窗口开始时间和结束时间
String startTime = AnqclnUtil.toDateTime(context.window().getStart());
String endTime = AnqclnUtil.toDateTime(context.window().getEnd());
out.collect("窗口:"+startTime+" "+endTime+" ,key:"+s + " ,list:"+list);
}
}
)
.print();
try {
env.execute();
} catch (Exception e) {
e.printStackTrace();
}
}
}
自定义的工具类:
public class AnqclnUtil {
// 要先声明泛型
public static <T>List<T> toList(Iterable<T> elements) {
List<T> list = new ArrayList<>();
for (T t : elements) {
list.add(t);
}
return list;
}
// 将long类型的时间转换为时间字符串
public static String toDateTime(long ts) {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(ts);
}
}
运行结果:
window(SlidingProcessingTimeWindows.of(Time.seconds(10),Time.seconds(5)))
,参数是时间滑动窗口大小和滑动距离。5秒滑动一个窗口,每个窗口最多放10个元素
与滚动窗口类似,滑动窗口的 assigner 分发元素到指定大小的窗口,窗口大小通过 window size 参数设置。 滑动窗口需要一个额外的滑动距离(window slide)参数来控制生成新窗口的频率。 因此,如果 slide 小于窗口大小,滑动窗口可以允许窗口重叠。这种情况下,一个元素可能会被分发到多个窗口。
比如说,你设置了大小为 10 分钟,滑动距离 5 分钟的窗口,你会在每 5 分钟得到一个新的窗口, 里面包含之前 10 分钟到达的数据(如下图所示)。
示例代码:
public class Flink01_Window_Time {
public static void main(String[] args) {
Configuration conf = new Configuration();
conf.setInteger("rest.port",1000);
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(conf);
env.setParallelism(2);
env
.socketTextStream("hadoop101",9999)
.map(line->{
String[] datas = line.split(",");
return new WaterSensor(
datas[0],
Long.valueOf(datas[1]),
Integer.valueOf(datas[2])
);
})
.keyBy(WaterSensor::getId)
// 定义一个长度为10s的滚动窗口 每隔10s滚动一次
// .window(TumblingProcessingTimeWindows.of(Time.seconds(10)))
// 定义一个滑动窗口:窗口长度是10s,滑动间隔5s 这种情况一个元素可能出现在多个窗口,因为有滑动
.window(SlidingProcessingTimeWindows.of(Time.seconds(10),Time.seconds(5)))
.process(
// 泛型的含义是:输入元素的类型,输出元素的类型,key的类型,窗口类型
new ProcessWindowFunction<WaterSensor, Object, String, TimeWindow>() {
// 在窗口关闭的时候触发一次
@Override
public void process(String s, // key ,keyBy之后的key
Context context, //上下文对象:里面封装了一些信息,比如窗口开始时间,结束时间,定时服务器...
Iterable<WaterSensor> elements, // 存储了这个窗口内所有的元素
Collector<Object> out) throws Exception {
// 把Iterable中所有的元素取出并存入到 list 集合中
List<WaterSensor> list = AnqclnUtil.toList(elements);
// 获取窗口的相关信息,窗口开始时间和结束时间
String startTime = AnqclnUtil.toDateTime(context.window().getStart());
String endTime = AnqclnUtil.toDateTime(context.window().getEnd());
out.collect("窗口:"+startTime+" "+endTime+" ,key:"+s + " ,list:"+list);
}
}
)
.print();
try {
env.execute();
} catch (Exception e) {
e.printStackTrace();
}
}
}
window(ProcessingTimeSessionWindows.withGap(Time.seconds(4)))
,参数是会话间隔,也就是多久没有活跃就关闭当前会话。4秒不活跃就关闭窗口。
会话窗口的 assigner 会把数据按活跃的会话分组。 与滚动窗口和滑动窗口不同,会话窗口不会相互重叠,且没有固定的开始或结束时间。 会话窗口在一段时间没有收到数据之后会关闭,即在一段不活跃的间隔之后。 会话窗口的 assigner 可以设置固定的会话间隔(session gap)或 用 session gap extractor 函数来动态地定义多长时间算作不活跃。 当超出了不活跃的时间段,当前的会话就会关闭,并且将接下来的数据分发到新的会话窗口。
示例代码:
public class Flink01_Window_Time {
public static void main(String[] args) {
Configuration conf = new Configuration();
conf.setInteger("rest.port",1000);
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(conf);
env.setParallelism(2);
env
.socketTextStream("hadoop101",9999)
.map(line->{
String[] datas = line.split(",");
return new WaterSensor(
datas[0],
Long.valueOf(datas[1]),
Integer.valueOf(datas[2])
);
})
.keyBy(WaterSensor::getId)
// 定义一个长度为10s的滚动窗口 每隔10s滚动一次
// .window(TumblingProcessingTimeWindows.of(Time.seconds(10)))
// 定义一个滑动窗口:窗口长度是10s,滑动间隔5s 这种情况一个元素可能出现在多个窗口,因为有滑动
// .window(SlidingProcessingTimeWindows.of(Time.seconds(10),Time.seconds(5)))
// 定义一个session窗口,时间间隔为4s 对于session窗口来说,不同的key出发时间不同,每个key都维护自己的session
.window(ProcessingTimeSessionWindows.withGap(Time.seconds(4)))
.process(
// 泛型的含义是:输入元素的类型,输出元素的类型,key的类型,窗口类型
new ProcessWindowFunction<WaterSensor, Object, String, TimeWindow>() {
// 在窗口关闭的时候触发一次
@Override
public void process(String s, // key ,keyBy之后的key
Context context, //上下文对象:里面封装了一些信息,比如窗口开始时间,结束时间,定时服务器...
Iterable<WaterSensor> elements, // 存储了这个窗口内所有的元素
Collector<Object> out) throws Exception {
// 把Iterable中所有的元素取出并存入到 list 集合中
List<WaterSensor> list = AnqclnUtil.toList(elements);
// 获取窗口的相关信息,窗口开始时间和结束时间
String startTime = AnqclnUtil.toDateTime(context.window().getStart());
String endTime = AnqclnUtil.toDateTime(context.window().getEnd());
out.collect("窗口:"+startTime+" "+endTime+" ,key:"+s + " ,list:"+list);
}
}
)
.print();
try {
env.execute();
} catch (Exception e) {
e.printStackTrace();
}
}
}
countWindow(3)
,参数是个数滚动窗口大小。3个元素滚动一个窗口
每来多少个元素就滚动一次
示例代码:
public class Flink02_Window_Count {
public static void main(String[] args) {
Configuration conf = new Configuration();
conf.setInteger("rest.port",1000);
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(conf);
env.setParallelism(2);
env
.socketTextStream("hadoop101",9999)
.map(line -> {
String[] data = line.split(",");
return new WaterSensor(
data[0],
Long.valueOf(data[1]),
Integer.valueOf(data[2])
);
})
.keyBy(WaterSensor::getId)
// 定义长度为3的基于个数的滚动窗口 因为是keyBy之后的,所以key一样的不到三条不会触发
.countWindow(3)
.process(new ProcessWindowFunction<WaterSensor, String, String, GlobalWindow>() {
@Override
public void process(String s,
Context context,
Iterable<WaterSensor> elements,
Collector<String> out) throws Exception {
List<WaterSensor> list = AnqclnUtil.toList(elements);
out.collect(" key: "+s+" "+list);
}
})
.print();
try {
env.execute();
} catch (Exception e) {
e.printStackTrace();
}
}
}
countWindow(3,2)
,参数是个数滑动窗口大小和滑动步长。每两个元素产生一个新的窗口,每个窗口最多放3个元素。
就比滚动的多了个参数,滑动步长。步长是生成新窗口的条件,而窗口大小是指这个窗口最多能放多少个元素
示例代码:
public class Flink02_Window_Count {
public static void main(String[] args) {
Configuration conf = new Configuration();
conf.setInteger("rest.port",1000);
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(conf);
env.setParallelism(2);
env
.socketTextStream("hadoop101",9999)
.map(line -> {
String[] data = line.split(",");
return new WaterSensor(
data[0],
Long.valueOf(data[1]),
Integer.valueOf(data[2])
);
})
.keyBy(WaterSensor::getId)
// 定义长度为3的基于个数的滚动窗口 因为是keyBy之后的,所以key一样的不到三条不会触发
// .countWindow(3)
// 定义一个长度为3(窗口内元素的最大个数) 每来两个2个元素滑动一次,
.countWindow(3,2)
.process(new ProcessWindowFunction<WaterSensor, String, String, GlobalWindow>() {
@Override
public void process(String s,
Context context,
Iterable<WaterSensor> elements,
Collector<String> out) throws Exception {
List<WaterSensor> list = AnqclnUtil.toList(elements);
out.collect(" key: "+s+" "+list);
}
})
.print();
try {
env.execute();
} catch (Exception e) {
e.printStackTrace();
}
}
}