flink1.12.x 窗口函数(window function)-全量 ProcessWindowFunction

说明

ProcessWindowFunction 等到窗口 截止,再对 全部数据 进行统计

可以实现 中位数、平均值 这类,需要获取 全部数据才能统计的功能

比 windowFunction 提供了更多的上下文信息

调用方式

stream
 .keyBy( ... )
 .window( ... )
 .process( ProcessWindowFunction 对象);

当窗口到达结束时间时,触发计算,就会调用 process() 方法

接口定义

public abstract class ProcessWindowFunction<IN, OUT, KEY, W extends Window> extends AbstractRichFunction

    public abstract void process(KEY key, Context context, Iterable<IN> elements, Collector<OUT> out) throws Exception;
}

方法 process 的形参解释:

  • KEY key:key

  • Context context:上下文,可获取windows开始时间、结束时间

  • Iterable<IN> elements:当前窗口中接收的全部数据,是可迭代对象

  • Collector<OUT> out:返回结果

keyBy() 后,即:数据分组后,每组执行一次 process() 方法

缺点

接收到所有数据后,再做处理,在 process 中迭代数据,这种方式效率较差。来一条数据,就处理一条数据,效率更高

案例

按照 年月 统计订单平均价

数据

2021-10-01,java从入门到入坟,80
2021-10-21,mysql从入门到入坟,50
2021-9-05,hadoop从入门到放弃,90
2021-9-15,zookeeper从入门到放弃,40
2021-9-25,flink从入门到放弃,30

思路

  1. 根据字符串,进行拆分,并封装到对象中
  2. keyBy() 根据 年-月进行分组
  3. 实现 process(),按照 年月 统计订单平均价

java

package top.malaoshi;

import org.apache.flink.api.common.RuntimeExecutionMode;
import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.api.java.functions.KeySelector;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.datastream.KeyedStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.windowing.ProcessWindowFunction;
import org.apache.flink.streaming.api.functions.windowing.WindowFunction;
import org.apache.flink.streaming.api.windowing.assigners.TumblingProcessingTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
import org.apache.flink.util.Collector;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;

public class WindowProcess全量窗口统计订单平均价 {
    /**
     * 数据格式:
     * 日期时间,商品名,价格
     * 2021-10-01,java从入门到入坟,80
     * 2021-10-21,mysql从入门到入坟,50
     * 2021-9-05,hadoop从入门到放弃,90
     * 2021-9-15,zookeeper从入门到放弃,40
     * 2021-9-25,flink从入门到放弃,30
     *
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {

        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setRuntimeMode(RuntimeExecutionMode.AUTOMATIC);//自动模式,根据数据源自行判断
        DataStream<String> dataStream = env.socketTextStream("hadoop1", 9999);

        SingleOutputStreamOperator<Goods> soso = dataStream.map(new MapFunction<String, Goods>() {

            @Override
            public Goods map(String s) throws Exception {
                String[] arr = s.split(",");
                Goods goods = new Goods();
                goods.setDate(arr[0]);
                goods.setTitle(arr[1]);
                goods.setPrice(Double.parseDouble(arr[2]));
                return goods;
            }
        });
        KeyedStream<Goods, String> ks = soso.keyBy(new KeySelector<Goods, String>() {
            /**
             *
             * @param goods:传入的元祖,格式:(广告id,点击数量)
             * @return
             * @throws Exception
             */
            @Override
            public String getKey(Goods goods) throws Exception {
                String date = goods.getDate();
                int index = date.lastIndexOf("-");
                String yearMonth = date.substring(0, index);
                return yearMonth;
            }
        });//流处理中的分组

        //统计各个key出现的次数
        SingleOutputStreamOperator<Result> agg = ks.window(TumblingProcessingTimeWindows.of(Time.seconds(15)))
                .process(new ProcessWindowFunctionImpl());

        agg.print();
        //启动并等待程序结束,可传入任务名
        env.execute();
    }

    static class ProcessWindowFunctionImpl extends ProcessWindowFunction<Goods, Result, String, TimeWindow> {
        /**
         *
         * @param s key
         * @param context 上下文,可获取窗口开始、结束时间
         * @param iterable 当前窗口中接收的全部数据
         * @param collector 返回结果
         * @throws Exception
         */
        @Override
        public void process(String s, Context context, Iterable<Goods> iterable, Collector<Result> collector) throws Exception {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String windowStart = sdf.format(new Date(context.window().getStart()));
            String windowEnd = sdf.format(new Date(context.window().getEnd()));
            System.out.println(windowStart + "--------" + windowEnd+"---"+s);
            Iterator<Goods> iterator = iterable.iterator();
            int count = 0;
            double totalPrice = 0.0;
            while (iterator.hasNext()) {
                Goods goods = iterator.next();
                System.out.println("--" + goods.getTitle()+"--"+goods.getDate());
                totalPrice += goods.getPrice();
                count++;
            }
            if (count != 0) {
                double avg = totalPrice / count;
                Result result = new Result(s, avg);
                collector.collect(result);
            }
        }
    }

    static class Goods {
        String date;
        String title;
        Double price;


        public String getTitle() {
            return title;
        }

        public void setTitle(String title) {
            this.title = title;
        }

        public String getDate() {
            return date;
        }

        public void setDate(String date) {
            this.date = date;
        }

        public Double getPrice() {
            return price;
        }

        public void setPrice(Double price) {
            this.price = price;
        }
    }

    static class Result {
        String date;
        Double avg;

        public Result(String date, Double avg) {
            this.date = date;
            this.avg = avg;
        }

        public String getDate() {
            return date;
        }

        public void setDate(String date) {
            this.date = date;
        }

        public Double getAvg() {
            return avg;
        }

        public void setAvg(Double avg) {
            this.avg = avg;
        }

        @Override
        public String toString() {
            return "Result{" +
                    "date='" + date + '\'' +
                    ", avg=" + avg +
                    '}';
        }
    }
}

测试

在linux 执行下面命令:

nc -lk 9999

启动java程序

在 nc 窗口输入下面内容:

2021-10-01,java从入门到入坟,80
2021-10-21,mysql从入门到入坟,50
2021-9-05,hadoop从入门到放弃,90
2021-9-15,zookeeper从入门到放弃,40
2021-9-25,flink从入门到放弃,30

观察 idea 控制台,按照 年月 统计订单平均价

如下:

2021-12-14 10:48:00--------2021-12-14 10:48:15---2021-9
--zookeeper从入门到放弃--2021-9-15
2021-12-14 10:48:00--------2021-12-14 10:48:15---2021-10
--flink从入门到放弃--2021-9-25
--hadoop从入门到放弃--2021-9-05
--java从入门到入坟--2021-10-01
--mysql从入门到入坟--2021-10-21
2> Result{date='2021-10', avg=65.0}
4> Result{date='2021-9', avg=53.333333333333336}

每组执行一次 process() 方法


原文出处:/show_1IX2OvDZvUX6.html