window类型
时间窗口(time window)
滚动时间窗口
滑动时间窗口
会话窗口
计数窗口(count window)
滚动计数窗口
滑动计数窗口
滚动窗口(tumbling window)
将数据依据固定的窗口长度对数据进行切分
时间对齐,窗口长度固定,没有重叠
滑动窗口(sliding window)
滑动窗口是固定窗口的更广义的一种形式,滑动窗口由固定的窗口长度和滑动间隔组成
窗口长度固定,可以有重叠
会话窗口(session window)
由一系列事件组合一个指定时间长度的timeout间隙组成,也就是一段时间没有接收到新数据就会生成新的窗口
特点:时间无对齐
window API
窗口分配器--window()方法
我们可以通过用.window()来定义一个窗口,然后基于这个window去做一些聚合或者其他处理操作。注意:window()方法必须在keyby之后才能用。
flink提供了更加简单的.timeWindow和.countWindow方法,用于定义时间窗口和技术窗口
窗口分配器(window assigner)
window()方法接收的输入参数是一个windowAssigner
windowAssigner负责将每条输入的数据分发到正确的window中
flink提供了通用的WindowAssigner
滚动窗口(tumbling window)
滑动窗口(sliding window)
会话窗口(session window)
全局窗口(global window)
创建不同类型的窗口
滚动窗口 .timeWindow(Time.seconds(10))
滑动窗口.timeWindow(Time.seconds(15),Time.seconds(5))
会话窗口 .window(EventTimeSessionWindows.withGap(Time.minutes(10))
滚动计数窗口 .countWindow(5)
滑动计数窗口.countWindow(10,2)
窗口函数(window function)
Flink中提供了四种类型的Window Function,分别为ReduceFunction、AggregateFuture(新版本废弃)、FoldFunction、ProcessWindowFunction,按照计算原理不同又分为两大类。
1,增量聚合函数,对应ReduceFunction、AggregateFunction、FoldFunction
- 该类型函数计算性能较高,占用存储空间少,因为窗口中只维护中间结果状态值,不需要缓存原始数据。
2,全量窗口函数:对应ProcessWindowFunction。
- 该类型函数使用代价较高,性能较弱,因为算子需要对所有属于该窗口的接入数据进行缓存,等到窗口触发时,对所有的原始数据进行汇总计算。
- 如果接入数据量较大或窗口时间较长,大概率会导致计算性能下降
ReduceFunction
- 该函数对输入的两个相同类型的数据元素按照指定的计算方法进行聚合逻辑,最终输出类型相同的结果元素。
- 实例代码如下:
val inputStream:DataStream[(Int,Long)]=...;
val reduceWindowStream = inputStream
.keyBy(_._0)
//指定窗口类型
.window(SlidingEventTimeWindows.of(Time.hours(1),Time.minutes(10)))
//指定聚合函数逻辑,将根据ID将第二个字段求和
.reduce{(v1,v2) => (v1._1,v1._2 + v2._2)}
- 除了直接使用表达式方式实现ReduceFunction逻辑定义,也可以创建Class实现ReduceFunction接口来定义聚合逻辑,实例如下:
val reduceWindowStream = inputStream.keyBy(_._1)
//指定窗口类型
.window(SlidingEventTimeWindows.of(Time.hours(1),Time.minutes(10)))
//定义ReduceFunction实现类定义聚合函数逻辑,将根据ID将第二个字段求和
.reduce(new ReduceFunction[(Int,Long)]){
override def reduce(t1:(Int,Long),t2:(Int,Long)):(Int,Long) = {(t1._1,t1._2+t2._2)}
}
AggregateFunction
与ReduceFunction相似,AggregateFunction同样基于中间状态计算结果的增量计算函数,但它更通用和灵活,实现复杂度更高。
AggregateFunction接口中定义了三个需要复写的方法
add()定义数据的添加逻辑
getResult()定义根据accumulator计算结果逻辑
merge()定义合并accumulator逻辑
实例代码如下
//定义求取均值的AggregateFunction
class MyAverageAggregate extends AggregateFunction[(String,Long),(Long,Long),Double]{
//定义createAccumulator为两个参数的元祖
override def createAccumulator() = (0L,0L)
//定义输入数据累加到accumulator的逻辑
override def add(input:(String,Long),acc:(Long,Long)) = (acc._1+input._2,acc._2+1L)
//更加累加器得出结果
override def getResult(acc:(Long,Long))= acc._1 / acc._2
//定义累加器合并的逻辑
override def merge(acc:(Long,Long),acc2:(Long,Long))=(acc1._1+acc2._1,acc1._2+acc2._2)
}
//在DataStream API使用定义好的AggregateFunction
val inputStream:DataStream[(String,Long)] = ...
val aggregateWindowStream = inputStream.keyBy(_._1)
//指定窗口类型
.window(SlidingEventTimeWindows.of(Time.hours(1),Time.minutes(10)))
//指定聚合函数逻辑,根据ID将第二个字段求平均值
.aggregate(new MyAverageAggregate)
FoldFunction(新版本废弃)
该函数定义了如何将窗口中的输入元素与外部元素合并的逻辑,实例如下,将"flink"字符串添加到inputStream数据集中所有元素第二个字段上。代码如下:
val inputStream:DataStream[(String,Long)] = ...
val foldWindowStream = inputStream.keyBy(_._1)
//指定窗口类型
.window(SlidingEventTimeWindows.of(Time.hours(1),Time.minutes(10)))
//指定聚合函数逻辑,将flink字符串和第二个字段相连接并输出
.fold("flink"){(acc,v) => acc+v._2}
ProcessWindowFunction
统计复杂指标时,可能需要依赖整个窗口中所有的数据元素,或操作窗口中状态数据和窗口元数据,此时需要ProcessWindowFunction函数,完成基于窗口全部数据元素的结果计算。
例如统计窗口数据元素中某一字段的中位数和众数,实例代码如下,其中Context抽象类完整定义了Window的元数据以及可操作Window的状态数据,包括GlobalState以及WindowState。
Flink中ProcessWindowsFunction抽象类定义
public abstract class ProcessWindowFunction<IN,OUT,KEY,W extends Window> extends AbstractRichFunction{
//评估窗口并定义输出的元素
void process(KEY key ,Context ctx,Interable<IN> vals,Collector<OUT> out) throws Exception;
//定义清除每个窗口计算结束后中间状态的逻辑
public void clear(Context ctx)throws Exception{}
//定义包含窗口元数据的上下文
public abstract class Context implements Serializable{
//返回窗口的元数据
public abstract W window();
//返回窗口当前的处理时间
public abstract long currentProcessingTime();
//返回窗口当前的event-time的Watermark
public abstract long currentWatermark();
//返回每个窗口的中间状态
public abstract KeyedStateStore windowState();
//返回每个key对应的中间状态
public abstract KeyedStateStore globalState();
//根据OutputTag输出数据
public abstract <X> void output(OutputTag<X> outputTag,x value);
}
}
在实现ProcessWindowFunction接口时,如果不操作状态数据,仅实现proce()方法即可,该方法中定义了评估窗口和具体数据输出的逻辑。如下代码通过自定义实现ProcessWindowFunction完成基于窗口上的Key统计包括求和、最小值、最大值,以及平均值的聚合指标,并获取窗口结束时间等元数据信息。
val inputStream:DataStream[(String,Long)]=...;
//向DataStream数据集指定StaticProcessFunction
val staticStream = inputStream.keyBy(_._1).timeWindow(Time.seconds(10)).process(new StaticProcessFunction)
//定义StaticProcessFunction根据窗口中的数据统计指标
class StaticProcessFunction extend是 ProcessWindowFunction[(String,Long,Int),(String,Long,Long,Long,Long,Long),String,TimeWindow]{
override def process(
key:String,
ctx:Context,
vals:Interable[(String,Long,Int)],
out:Collector[(String,Long,Long,Long,Long,Long)]):Unit = {
//定义求和、最大值、最小值、均值、窗口时间戳
val sum=vals.map(_._2).sum
val min=vals.map(_._2).min
val max=vals.map(_._2).max
val savg=sum / vals.size
val windowEnd = ctx.window.getEnd
//通过out.collect返回计算结果
out.collect((key,min,max,sum,avg,windowEnd))
}
}
Incremental Aggregation和ProcessWindowFunction整合
ReduceFunction和AggregateFunction等增量聚合函数一定程度上能提升窗口计算性能,但灵活性不如ProcessWindowFunction。但ProcessWindowFunction更消耗资源。
此时可以将Incremental Aggregation Function和ProcessWindowFunction进行整合,充分利用两种函数各自的优势。
将增量的ReduceFunction和ProcessWindowFunction整合,求取窗口中指标最大值以及对应窗口的终止时间,实例如下:
val inputStream:DataStream[(String,Long)]=...;
val result = inputStream.keyBy(_._1)
.timeWindow(Time.seconds(10))
.reduce(
//定义ReduceFunction完成求值最小值的逻辑
(r1:(String,Long,Int),r2:(String,Long,Int)) =>{
if(r1._2 >r2._2) r2 else r1
},
//定义ProcessWindowFunction,完成窗口元数据的采集
(
key:String,
window:TimeWindow,
minReadings: Interable[(String,Long,Int)],
out: Collector[(Long,(String,Long,Int))]
) => {
val min=minReadings.iterator.next()
//采集窗口结束时间和最小值赌赢的数据元素
out.collect((window.getEnd,min))
}
)