flink(七):窗口函数

说明

  • 本博客每周五更新一次,上周五太忙,推迟到今天更新,以后尽量周五更新。
  • 本博文主要分享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)))
    //指定聚合函数逻辑,将根据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))
        }
    )

总结

  • flink窗口函数用于实现和处理窗口中的业务逻辑,是实现窗口功能的必要技能,学习和理解对功能开发有很大的帮助。
  • 从去年到现在,坚持写博文半年多,有了几十篇博文,自我感觉技术上有了点提升,以后我会继续坚持,期待量变引起质变的一天。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值