• Flink 全量窗口聚合函数


    与增量聚合函数不同,全窗口函数需要先收集窗口中的数据,并在内部缓存起来,等到窗口要输出结果的时候再取出数据进行计算。很明显,这就是典型的批处理思路了——先攒数据,等一批都到齐了再正式启动处理流程。这样做毫无疑问是低效的:因为窗口全部的计算任务都积压在了要输出结果的那一瞬间,而在之前收集数据的漫长过程中却无所事事。这就好比平时不用功,到考试之前通宵抱佛脚,肯定不如把工夫花在日常积累上。那为什么还需要有全窗口函数呢?这是因为有些场景下,我们要做的计算必须基于全部的数据才有效,这时做增量聚合就没什么意义了;另外,输出的结果有可能要包含上下文中的一些信息(比如窗口的起始时间),这是增量聚合函数做不到的。所以,我们还需要有更丰富的窗口计算方式,这就可以用全窗口函数来实现。在Flink中,全窗口函数也有两种:WindowFunction和ProcessWindowFunction

    1、窗口函数(WindowFunction)

    WindowFunction字面上就是“窗口函数”,它其实是老版本的通用窗口函数接口。我们可以基于WindowedStream调用.apply()方法,传入一个WindowFunction的实现类。

    stream.keyBy(<key selector>)
            .window(<window assigner>)
            .apply(new MyWindowFunction());

    这个类中可以获取到包含窗口所有数据的可迭代集合(Iterable),还可以拿到窗口(Window)本身的信息。WindowFunction接口在源码中实现如下:

    public interface WindowFunction<IN, OUT, KEY, W extends Window> extends Function, Serializable {
        void apply(KEY key, W window, Iterable<IN> input, Collector<OUT> out) throws Exception;
    }

    当窗口到达结束时间需要触发计算时,就会调用这里的apply方法。可以从input集合中取出窗口收集的数据,结合key和window信息,通过收集器(Collector)输出结果。这里Collector的用法,与FlatMapFunction中相同。不过也看到了,WindowFunction能提供的上下文信息较少,也没有更高级的功能。事实上,它的作用可以被ProcessWindowFunction全覆盖,所以之后可能会逐渐弃用。一般在实际应用,直接使用ProcessWindowFunction就可以了。

    2、处理窗口函数(ProcessWindowFunction)

    ProcessWindowFunction是Window API中最底层的通用窗口函数接口。之所以说它“最底层”,是因为除了可以拿到窗口中的所有数据之外,ProcessWindowFunction还可以获取到一个“上下文对象”(Context)。这个上下文对象非常强大,不仅能够获取窗口信息,还可以访问当前的时间和状态信息。这里的时间就包括了处理时间(processing time)和事件时间水位线(event time watermark)。这就使得ProcessWindowFunction更加灵活、功能更加丰富。事实上,ProcessWindowFunction是Flink底层API——处理函数(process function)中的一员,当然,这些好处是以牺牲性能和资源为代价的。作为一个全窗口函数,ProcessWindowFunction同样需要将所有数据缓存下来、等到窗口触发计算时才使用。它其实就是一个增强版的WindowFunction。具体使用跟WindowFunction非常类似,可以基于WindowedStream调用.process()方法,传入一个ProcessWindowFunction的实现类。下面是一个电商网站统计每小时UV的例子:

    /**
     * 统计每10s 的 UV
     */
    public class WindowProcessFunction0627 {
        public static void main(String[] args) throws Exception {
            //获取执行环境
            StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
            env.setParallelism(1);
            SingleOutputStreamOperator<Event> eventStream = env.addSource(new ClickSource())
                    .assignTimestampsAndWatermarks(
                            WatermarkStrategy.<Event>forBoundedOutOfOrderness(Duration.ofSeconds(3))
                                    .withTimestampAssigner(
                                            new SerializableTimestampAssigner<Event>() {
                                                @Override
                                                public long extractTimestamp(Event element, long recordTimestamp) {
                                                    return element.timestamp;
                                                }
                                            }
                                    )
                    );
    
            //方案1 使用全量窗口函数实现:10s 输出一次Uv
            eventStream.keyBy(data -> true)
                    .window(TumblingEventTimeWindows.of(Time.seconds(10)))
                    .process(new MyProcessWindowFun0627()).print();
    
            //execut
            env.execute();
        }
    
        public static class MyProcessWindowFun0627 extends ProcessWindowFunction<Event, String, Boolean, TimeWindow> {
    
    
            @Override
            public void process(Boolean aBoolean, Context context, Iterable<Event> elements, Collector<String> out) throws Exception {
                //声明用户数据的结合
                HashSet<String> uSet = new HashSet<>();
                //循环保存
                for (Event element : elements) {
                    uSet.add(element.user);
                }
                //获取 Uv
                Long aLong = Long.valueOf(uSet.size());
    //获取窗口开始结束时间
                long end = context.window().getEnd();
                long start = context.window().getStart();
                out.collect("窗口  " + new Timestamp(start)+ " ~ " + new Timestamp(end) + "  --> " +aLong);
            }
        }
    }

    输出结果

    窗口  2022-06-27 22:46:20.0 ~ 2022-06-27 22:46:30.0  --> 3
    窗口  2022-06-27 22:46:30.0 ~ 2022-06-27 22:46:40.0  --> 5
    窗口  2022-06-27 22:46:40.0 ~ 2022-06-27 22:46:50.0  --> 4
    窗口  2022-06-27 22:46:50.0 ~ 2022-06-27 22:47:00.0  --> 4

    这里使用的是事件时间语义。定义10秒钟的滚动事件窗口后,直接使用ProcessWindowFunction来定义处理的逻辑。我们可以创建一个HashSet,将窗口所有数据的userId写入实现去重,最终得到HashSet的元素个数就是UV值。当然,这里我们并没有用到上下文中其他信息,所以其实没有必要使用ProcessWindowFunction。全窗口函数因为运行效率较低,很少直接单独使用,往往会和增量聚合函数结合在一起,共同实现窗口的处理计算。

  • 相关阅读:
    搭建公共DNS服务器[转]
    zabbix3.4 yum快速安装
    centos7安装图形化界面并远程连接
    记一次排查黑客入侵
    centos6安装图形界面并远程连接
    树莓派做NAS
    document.documentElement.scrollTop||document.body.scrollTop;
    矩阵连乘
    母函数详解
    Catalan 数
  • 原文地址:https://www.cnblogs.com/wdh01/p/16394256.html
Copyright © 2020-2023  润新知