Flink 认为 Batch 是 Streaming 的一个特例,所以 Flink 底层引擎是一个流式引擎,在上面实现了流处理和批处理。而窗口(window)就是从 Streaming 到 Batch 的一个桥梁。Flink 提供了非常完善的窗口机制。
什么是window
在流式数据中,数据是连续的。有时我们需要根据业务做一些聚合类的操作,例如过去五分钟内用户浏览量的计算。这五分钟就是一个窗口。
窗口可以由时间或者数量来做区分
1.根据时间进行截取,比如每10分钟统计一次
2.根据消息数量进行统计,比如每100个数据统计一次
时间窗口
时间窗口又分为滚动窗口,滑动窗口,和会话窗口
滚动窗口:
时间对齐,窗口长度固定,没有重叠
如图:以固定的长度进行分割,比如一个网站统计每分钟的浏览量,BI的pv,uv统计
滑动窗口
时间对齐,窗口长度固定,有重叠,展现的是数据的变化趋势
如图:窗口大小为4,步长为2,每隔两秒统计仅4s的数据
适用于 股票的实时波动情况等
会话窗口
当流中达到多长时间没有新的数据到来,上一个会话窗口就是截至到新数据到来前接收到的最后一条数据,当新数据到来后,上一个窗口将会关闭,开启一个新的窗口。
常见于,web程序session的统计
代码实现
常见分类:keyed window和Non-keyed window
二者区分是否跟于keyBy之后
CountWindow实现
object CountWindow {
def main(args: Array[String]): Unit = {
val environment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
val dataStream: DataStream[String] = environment.socketTextStream("192.168.23.171", 8989, '\n')
//None_keyed Stream
// dataStream.flatMap(_.split(" "))
// .map((_,1))
// .countWindowAll(10)
// .sum(1)
// .print()
//keyed Stream
dataStream.flatMap(_.split(" "))
.map((_,1))
.keyBy(0)
.countWindow(10)
.sum(1)
.print()
environment.execute("CountWindowStream")
}
}
滚动窗口实现
object TubmlingWindow {
def main(args: Array[String]): Unit = {
val environment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
val dataStream: DataStream[String] = environment.socketTextStream("node01", 8989, '\n')
// Non-keyed 分组
// dataStream.flatMap(_.split(" "))
// .map((_, 1))
// .timeWindowAll(Time.seconds(5))
// .windowAll(TumblingProcessingTimeWindows.of(Time.seconds(5)))
// .sum(1)
// .print()
// keyed Stream
dataStream.flatMap(_.split(" "))
.map((_,1))
.keyBy(0)
// .timeWindow(Time.seconds(5))
.window(TumblingProcessingTimeWindows.of(Time.seconds(5)))
.sum(1)
.print()
environment.execute("StreamTubmlingWindow")
}
}
滑动窗口实现
object SlindingWindow {
def main(args: Array[String]): Unit = {
val environment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
val dataStream: DataStream[String] = environment.socketTextStream("node01", 8989, '\n')
// Non-keyed 分组
// dataStream.flatMap(_.split(" "))
// .map((_, 1))
.timeWindowAll(Time.seconds(10),Time.seconds(5))
// .windowAll(SlidingProcessingTimeWindows.of(Time.seconds(10),Time.seconds(5)))
// .sum(1)
// .print()
dataStream.flatMap(_.split(" "))
.map((_,1))
.keyBy(0)
// .timeWindow(Time.seconds(10),Time.seconds(5))
.window(SlidingProcessingTimeWindows.of(Time.seconds(10),Time.seconds(5)))
.sum(1)
.print()
environment.execute("StreamSlidingWindow")
}
}
会话窗口实现
object SessionWindow {
def main(args: Array[String]): Unit = {
val environment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
val dataStream: DataStream[String] = environment.socketTextStream("node01", 8989, '\n')
// Non-keyed 分组
// dataStream.flatMap(_.split(" "))
// .map((_, 1))
// .windowAll(ProcessingTimeSessionWindows.withGap(Time.seconds(5)))
// .sum(1)
// .print()ronment.socketTextStream("node01", 8989, '\n')
dataStream.flatMap(_.split(" "))
.map((_,1))
.keyBy(0)
.window(EventTimeSessionWindows.withGap(Time.seconds(5)))
.sum(1)
.print()
environment.execute("StreamSessionWindow")
}
}
Flink的水印机制
Flink流处理时间方式
- EventTime[事件时间]
事件发生的时间,例如:点击网站上的某个链接的时间 - IngestionTime[进入时间]
某个Flink节点的source operator接收到数据的时间,例如:某个source消费到kafka中的数据 - ProcessingTime[处理时间]
某个Flink节点执行某个operation的时间,例如:timeWindow接收到数据的时间
设置Flink流处理的时间类型
// 设置为按照事件时间来进行计算
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
// 设置为按照处理时间来进行计算
env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime)
比如一个10分钟一个滚动窗口,统计每个时间段访问人数
10:00:00到10:00:00 为一个窗口
假设10:09:59产生了一条数据,但是因为网络延迟,进入flink已经10:10:03那么该数据该如何归类,所以此时应该使用Eventtime
使用水印可以解决这类网络延迟问题,可以理解水印就是一个时间戳,每接收处理一个消息都会添加水印
水印影响原有事件时间,当数据流添加水印后,会按照水印时间来触发窗口统计计算,当接收到的水印时间大于等于窗口的endTime,触发计算。