1.window 概念
将无限流切割为有限流的一种方式,它会将流数据分发到有限大小的桶(bucket)中进行分析
2.window 类型
2.1 时间窗口(Time Window)
2.1.1 滚动时间窗口
- 将数据依据固定的窗口长度对数据进行切分
- 时间对齐,窗口长度固定,没有重叠
2.1.2 滑动时间窗口
- 滑动窗口由固定的窗口长度和滑动间隔组成
- 窗口长度固定,可以有重叠
2.1.3 会话窗口
- 由一系列事件组合一个指定时间长度的 timeout 间隙组成,也就是一段时间没有接收到新数据就会生成新的窗口
- 特点:时间无对齐
2.2 计数窗口(Count Window)
2.2.1 滚动计数窗口
2.2.2 滑动计数窗口
3.window API
用 .window() 来定义一个窗口,然后基于这个 window 去做一些聚合或者其它处理操作。 window () 方法必须在 keyBy 之后才能用。 Flink 提供了更加简单的 .timeWindow 和 .countWindow 方法,用于定义时间窗口和计数窗口
3.1 窗口分配器(window assigner)
- Window() 方法接收的输入参数是一个 WindowAssigner
- WindowAssigner 负责将每条输入的数据分发到正确的 window 中
- Flink 提供了通用的 WindowAssigner
滚动窗口(tumbling window)
滑动窗口(sliding window)
会话窗口(session window)
全局窗口(global window)
3.2 创建不同类型的窗口
3.3 窗口函数(window function)
window function 定义了要对窗口中收集的数据做的计算操作
可以分为两类
-
增量聚合函数(incremental aggregation functions)
每条数据到来就进行计算,保持一个简单的状态
ReduceFunction
AggregateFunction -
全窗口函数(full window functions)
先把窗口所有数据收集起来,等到计算的时候会遍历所有数据
ProcessWindowFunction:
一些业务场景,我们需要收集窗口内所有的数据进行计算,例如计算窗口数据的中位数,或者计算窗口数据中出现频率最高的值。这样的需求,使用 ReduceFunction 和AggregateFunction就无法实现了。这个时候就需要 ProcessWindowFunction 了
3.3.1 案例:求窗口中最小温度值
import com.jaffe.day02.SensorSource
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.windowing.time.Time
/**
* @Author jaffe
* @Date 2020/06/10 14:10
*/
object MinTempPerWindow {
def main(args: Array[String]): Unit = {
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setParallelism(1)
val stream = env.addSource(new SensorSource)
stream
.map(r => (r.id,r.temperature))
.keyBy(_._1)
.timeWindow(Time.seconds(10))
.reduce((r1,r2) => (r1._1,r2._2.min(r1._2)))
.print()
env.execute()
}
}
3.3.2 案例:使用增量聚合函数实现窗口温度平均值计算
import com.jaffe.day02.SensorSource
import org.apache.flink.api.common.functions.AggregateFunction
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.windowing.time.Time
/**
* @Author jaffe
* @Date 2020/06/10 14:11
*/
object AvgTempPerWindow {
def main(args: Array[String]): Unit = {
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setParallelism(1)
val stream = env
.addSource(new SensorSource)
stream
.map(r => (r.id, r.temperature))
.keyBy(_._1)
.timeWindow(Time.seconds(5))
.aggregate(new AvgTempFunction)
.print()
env.execute()
}
// 平均温度值 = 总的温度值 / 温度的条数
class AvgTempFunction extends AggregateFunction[(String, Double),//输入的传感器ID和温度值
(String, Double, Long),//累加器的ID,目前温度总和,温度条数
(String, Double)//输出传感器ID和平均温度值
] {
// 创建累加器
override def createAccumulator(): (String, Double, Long) = ("", 0.0, 0L)
// 累加数据
override def add(in: (String, Double), acc: (String, Double, Long)): (String, Double, Long) = {
(in._1, acc._2 + in._2, acc._3 + 1)
}
//运算结果
override def getResult(acc: (String, Double, Long)): (String, Double) = {
(acc._1, acc._2 / acc._3)
}
//累加器间的合并
override def merge(acc: (String, Double, Long), acc1: (String, Double, Long)): (String, Double, Long) = {
(acc._1, acc._2 + acc1._2, acc._3 + acc1._3)
}
}
}
3.3.3 案例:全窗口聚合函数实现窗口平均值温度计算
下面例子:
- process() 方法接受的参数为:window 的 key,Iterable 迭代器包含窗口的所有元素,Collector 用于输出结果流。Context 参数和别的 process 方法一样。
- ProcessWindowFunction 的 Context 对象还可以访问 window 的元数据 (窗口开始和结束时间),当前处理时间和水位线,per-windowstate 和 per-key global state,side outputs。
- per-window state: 用于保存一些信息,这些信息可以被 process() 访问,只要 process 所处理的元素属于这个窗口。
- per-key global state: 同一个 key,也就是在一条 KeyedStream 上,不同的 window 可以访问 per-key global state 保存的值
import com.jaffe.day02.SensorSource
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.scala.function.ProcessWindowFunction
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.streaming.api.windowing.windows.TimeWindow
import org.apache.flink.util.Collector
/**
* @Author jaffe
* @Date 2020/06/10 14:50
*/
object AvgTempPerWindowByProcessWindowFunction {
def main(args: Array[String]): Unit = {
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setParallelism(1)
val stream = env
.addSource(new SensorSource)
stream
.map(r => (r.id, r.temperature))
.keyBy(_._1)
.timeWindow(Time.seconds(5))
.process(new AvgTempFunc)
.print()
env.execute()
}
class AvgTempFunc extends ProcessWindowFunction[(String, Double), (String, Double), String, TimeWindow] {
override def process(key: String, context: Context, elements: Iterable[(String, Double)], out: Collector[(String, Double)]): Unit = {
val size = elements.size
var sum = 0.0
for (r <- elements) {
sum += r._2
}
out.collect((key, sum / size))
}
}
3.3.4 案例:Aggregate(增量聚合和全窗口聚合结合使用计算窗口最大最小温度)
import com.jaffe.day02.{SensorReading, SensorSource}
import org.apache.flink.api.common.functions.AggregateFunction
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.scala.function.ProcessWindowFunction
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.streaming.api.windowing.windows.TimeWindow
import org.apache.flink.util.Collector
/**
* @Author jaffe
* @Date 2020/06/10 15:36
*/
object MinMaxTempByAggregateAndProcess {
case class MinMaxTemp(id: String,
min: Double,
max: Double,
endTs: Long)
def main(args: Array[String]): Unit = {
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setParallelism(1)
val stream = env.addSource(new SensorSource)
stream
.keyBy(_.id)
.timeWindow(Time.seconds(5))
// 第一个参数:增量聚合,第二个参数:全窗口聚合
.aggregate(new Agg,new WindowResult)
.print()
env.execute()
}
//增量聚合
class Agg extends AggregateFunction[SensorReading,(String,Double,Double),(String,Double,Double)]{
override def createAccumulator(): (String, Double, Double) = {
("",Double.MaxValue,Double.MinValue)
}
override def add(in: SensorReading, acc: (String, Double, Double)): (String, Double, Double) = {
(in.id,in.temperature.min(acc._2),in.temperature.max(acc._3))
}
override def getResult(acc: (String, Double, Double)): (String, Double, Double) = acc
override def merge(acc1: (String, Double, Double), acc2: (String, Double, Double)): (String, Double, Double) = {
(acc1._1,acc1._2.min(acc2._2),acc1._3.max(acc2._3))
}
}
class WindowResult extends ProcessWindowFunction[(String,Double,Double), MinMaxTemp,String,TimeWindow]{
override def process(key: String, context: Context, elements: Iterable[(String, Double, Double)], out: Collector[MinMaxTemp]): Unit = {
// 迭代器中只有一个值,就是增量聚合函数发送过来的聚合结果
val minMax = elements.head
out.collect(MinMaxTemp(key,minMax._2,minMax._3,context.window.getEnd))
}
}
}
3.3.5 案例:Reduce(增量聚合和全窗口聚合结合使用计算窗口最大最小温度)
import com.jaffe.day02.SensorSource
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.scala.function.ProcessWindowFunction
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.streaming.api.windowing.windows.TimeWindow
import org.apache.flink.util.Collector
/**
* @Author jaffe
* @Date 2020/06/10 16:26
*/
object MinMaxTempByReduceAndProcess {
case class MinMaxTemp(id: String,
min: Double,
max: Double,
endTs: Long)
def main(args: Array[String]): Unit = {
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setParallelism(1)
val stream = env.addSource(new SensorSource)
stream
.map(r => (r.id, r.temperature, r.temperature))
.keyBy(_._1)
.timeWindow(Time.seconds(5))
.reduce(
(r1: (String, Double, Double), r2: (String, Double, Double)) => {
(r1._1, r1._2.min(r2._2), r1._3.max(r2._3))
},
new WindowResult
)
.print()
env.execute()
}
class WindowResult extends ProcessWindowFunction[(String, Double, Double),
MinMaxTemp, String, TimeWindow] {
override def process(key: String, context: Context, elements: Iterable[(String, Double, Double)], out: Collector[MinMaxTemp]): Unit = {
val temp = elements.head
out.collect(MinMaxTemp(temp._1, temp._2, temp._3, context.window.getEnd))
}
}
}
4.其它可选 API
.trigger() —— 触发器
定义 window 什么时候关闭,触发计算并输出结果
.evitor() —— 移除器
定义移除某些数据的逻辑
.allowedLateness() —— 允许处理迟到的数据
.sideOutputLateData() —— 将迟到的数据放入侧输出流
.getSideOutput() —— 获取侧输出流