说明
本博客每周五更新一次,上周五太忙,推迟到今天更新,以后尽量周五更新。 本博文主要分享flink的窗口计算相关函数类型,窗口函数分类、特点和使用方法。
窗口函数
Flink中提供了四种类型的Window Function,分别为ReduceFunction、AggregateFuture(新版本废弃)、FoldFunction、ProcessWindowFunction,按照计算原理不同又分为两大类。
增量聚合函数,对应ReduceFunction、AggregateFunction、FoldFunction
该类型函数计算性能较高,占用存储空间少,因为窗口中只维护中间结果状态值,不需要缓存原始数据。 全量窗口函数:对应ProcessWindowFunction。
该类型函数使用代价较高,性能较弱,因为算子需要对所有属于该窗口的接入数据进行缓存,等到窗口触发时,对所有的原始数据进行汇总计算。 如果接入数据量较大或窗口时间较长,大概率会导致计算性能下降。
ReduceFunction
该函数对输入的两个相同类型的数据元素按照指定的计算方法进行聚合逻辑,最终输出类型相同的结果元素。 实例代码如下:
val inputStream: DataStream[ ( Int , Long ) ] = . . . ;
val reduceWindowStream = inputStream
. keyBy( _. _0)
. window( SlidingEventTimeWindows. of( Time. hours( 1 ) , Time. minutes( 10 ) ) )
. 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 ) ) )
. 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逻辑 实例代码如下
class MyAverageAggregate extends AggregateFunction[ ( String , Long ) , ( Long , Long ) , Double ] {
override def createAccumulator( ) = ( 0L , 0L )
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)
}
val inputStream: DataStream[ ( String , Long ) ] = . . .
val aggregateWindowStream = inputStream. keyBy( _. _1)
. window( SlidingEventTimeWindows. of( Time. hours( 1 ) , Time. minutes( 10 ) ) )
. 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 ) ) )
. 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( ) ;
public abstract long currentWatermark( ) ;
public abstract KeyedStateStore windowState( ) ;
public abstract KeyedStateStore globalState( ) ;
public abstract < X > void output( OutputTag< X > outputTag, x value) ;
}
}
在实现ProcessWindowFunction接口时,如果不操作状态数据,仅实现proce()方法即可,该方法中定义了评估窗口和具体数据输出的逻辑。如下代码通过自定义实现ProcessWindowFunction完成基于窗口上的Key统计包括求和、最小值、最大值,以及平均值的聚合指标,并获取窗口结束时间等元数据信息。
val inputStream: DataStream[ ( String , Long ) ] = . . . ;
val staticStream = inputStream. keyBy( _. _1) . timeWindow( Time. seconds( 10 ) ) . process( new 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( ( 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(
( r1: ( String , Long , Int ) , r2: ( String , Long , Int ) ) => {
if ( r1. _2 > r2. _2) r2 else r1
} ,
(
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) )
}
)
总结
flink窗口函数用于实现和处理窗口中的业务逻辑,是实现窗口功能的必要技能,学习和理解对功能开发有很大的帮助。 从去年到现在,坚持写博文半年多,有了几十篇博文,自我感觉技术上有了点提升,以后我会继续坚持,期待量变引起质变的一天。