• 增量聚合和全窗口函数的结合使用


    增量聚合和全窗口函数的对比

    已经了解了Window API中两类窗口函数的用法,下面先来做个简单的总结。增量聚合函数处理计算会更高效。举一个最简单的例子,对一组数据求和。大量的数据连续不断到来,全窗口函数只是把它们收集缓存起来,并没有处理;到了窗口要关闭、输出结果的时候,再遍历所有数据依次叠加,得到最终结果。而如果采用增量聚合的方式,那么只需要保存一个当前和的状态,每个数据到来时就会做一次加法,更新状态;到了要输出结果的时候,只要将当前状态直接拿出来就可以了。增量聚合相当于把计算量“均摊”到了窗口收集数据的过程中,自然就会比全窗口聚合更加高效、输出更加实时。而全窗口函数的优势在于提供了更多的信息,可以认为是更加“通用”的窗口操作。它只负责收集数据、提供上下文相关信息,把所有的原材料都准备好,至于拿来做什么我们完全可以任意发挥。这就使得窗口计算更加灵活,功能更加强大。所以在实际应用中,我们往往希望兼具这两者的优点,把它们结合在一起使用。Flink的Window API就给我们实现了这样的用法。我们之前在调用WindowedStream的.reduce()和.aggregate()方法时,只是简单地直接传入了一个ReduceFunction或AggregateFunction进行增量聚合。除此之外,其实还可以传入第二个参数:一个全窗口函数,可以是WindowFunction或者ProcessWindowFunction。

     // ReduceFunction与WindowFunction结合
     public <R> SingleOutputStreamOperator<R> reduce(ReduceFunction<T> reduceFunction, WindowFunction<T, R, K, W> function) 
     // ReduceFunction与ProcessWindowFunction结合
     public <R> SingleOutputStreamOperator<R> reduce(ReduceFunction<T>  reduceFunction,  ProcessWindowFunction<T,  R,  K,  W> function)
     // AggregateFunction与WindowFunction结合
     public <ACC, V, R> SingleOutputStreamOperator<R> aggregate(AggregateFunction<T,  ACC,  V>  aggFunction,  WindowFunction<V,  R,  K,  W> windowFunction)
     // AggregateFunction与ProcessWindowFunction结合
     public <ACC, V, R> SingleOutputStreamOperator<R> aggregate(AggregateFunction<T, ACC, V> aggFunction,ProcessWindowFunction<V, R, K, W> windowFunction)

    这样调用的处理机制是:基于第一个参数(增量聚合函数)来处理窗口数据,每来一个数据就做一次聚合;等到窗口需要触发计算时,则调用第二个参数(全窗口函数)的处理逻辑输出结果。需要注意的是,这里的全窗口函数就不再缓存所有数据了,而是直接将增量聚合函数的结果拿来当作了Iterable类型的输入。一般情况下,这时的可迭代集合中就只有一个元素了。下面举一个具体的实例来说明。在网站的各种统计指标中,一个很重要的统计指标就是热门的链接;想要得到热门的url,前提是得到每个链接的“热门度”。一般情况下,可以用url的浏览量(点击量)表示热门度。我们这里统计10秒钟的url浏览量,每5秒钟更新一次;另外为了更加清晰地展示,还应该把窗口的起始结束时间一起输出。我们可以定义滑动窗口,并结合增量聚合函数和全窗口函数来得到统计结果。

    准备包装类

    public class UrlViewCount {
        public String url;
        public Long cnt;
        public Long start;
        public Long end;
    
        public UrlViewCount() {
        }
    
        public UrlViewCount(String url, Long cnt, Long start, Long end) {
            this.url = url;
            this.cnt = cnt;
            this.start = start;
            this.end = end;
        }
    
        @Override
        public String toString() {
            return "UrlViewCount{" +
                    "url='" + url + '\'' +
                    ", cnt=" + cnt +
                    ", TimestampStart=" + new Timestamp(start) +
                    ", TimestampEnd=" + new Timestamp(end) +
                    '}';
        }
    }

    实现逻辑

    /**
     * 使用增量聚合函数计算 PV
     * 使用全窗口函数包装数据
     */
    public class UVCountExample0627 {
        public static void main(String[] args) throws Exception {
            //获取执行环境&设置并行度
            StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
            env.setParallelism(1);
            //获取数据&提取时间戳
            SingleOutputStreamOperator<Event> eventSingleOutputStreamOperator = 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;
                                }
                            })
            );
            //按 url key
            eventSingleOutputStreamOperator.keyBy(data -> data.url)
                    .window(SlidingEventTimeWindows.of(Time.seconds(10), Time.seconds(5)))
                    //AggregateFunction 进行增量聚聚合
                    .aggregate(new AggregateFunction<Event, Long, Long>() {
                                   //增量聚合逻辑
                                   @Override
                                   public Long createAccumulator() {
                                       //初始值
                                       return 0L;
                                   }
    
                                   @Override
                                   public Long add(Event value, Long accumulator) {
                                       //累加规则
                                       return accumulator + 1L;
                                   }
    
                                   @Override
                                   public Long getResult(Long accumulator) {
                                       //获取结果
                                       return accumulator;
                                   }
    
                                   @Override
                                   public Long merge(Long a, Long b) {
                                       //合并
                                       return a + b;
                                   }
                               }, //使用全窗口函数包装数据
                            new ProcessWindowFunction<Long, UrlViewCount, String, TimeWindow>() {
                                /**
                                 * new ProcessWindowFunction<Long, UrlViewCount, String, TimeWindow>
                                 *     Long 输入数据类型
                                 *     UrlViewCount 输出黄数据类型
                                 *     String key 类型:data -> data.url ;url String
                                 *     TimeWindow 需要一个这样的变量
                                 */
    
                                @Override
                                public void process(String url, Context context, Iterable<Long> elements, Collector<UrlViewCount> out) throws Exception {
                                    /**
                                     * String url key
                                     * Context context 上下文
                                     *  Iterable<Long> elements 数据
                                     *  Collector<UrlViewCount> out 搜集器
                                     */
                                    long end = context.window().getEnd();
                                    long start = context.window().getStart();
                                    Long next = elements.iterator().next();
                                    out.collect(new UrlViewCount(url, next, start, end));
                                }
                            }).print();
    
            //执行
            env.execute();
        }
    }

    代码中用一个AggregateFunction来实现增量聚合,每来一个数据就计数加一;得到的结果交给ProcessWindowFunction,结合窗口信息包装成我们想要的UrlViewCount,最终输出统计结果。注:ProcessWindowFunction是处理函数中的一种,这里只用它来将增量聚合函数的输出结果包裹一层窗口信息。窗口处理的主体还是增量聚合,而引入全窗口函数又可以获取到更多的信息包装输出,这样的结合兼具了两种窗口函数的优势,在保证处理性能和实时性的同时支持了更加丰富的应用场景。

  • 相关阅读:
    最新自然语言处理(NLP)四步流程:Embed->Encode->Attend->Predict
    Word Embedding
    知乎:GAN 的发展对于研究通用人工智能有什么意义?
    2016年总结:教师路的开启,爱情味的初尝 (下)
    WinForm 窗口缩放动画效果
    关于DrawIndexedPrimitive函数的调用
    spring mvc--默认都使用了哪些bean
    杭电 HDU 1031 Design T-Shirt
    Android选择/拍照 剪裁 base64/16进制/byte上传图片+PHP接收图片
    hdoj5645DZY Loves Balls
  • 原文地址:https://www.cnblogs.com/wdh01/p/16435000.html
Copyright © 2020-2023  润新知