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

提出缺点

ProcessWindowFunction 缺点

窗口先 缓存所有元素,等到触发时,对窗口内的所有元素执行计算,这种方式效率低。

AggregateFunction 缺点

来一条数据,就处理一条数据,效率更高,但无法 获取 window信息(窗口开始时间、结束时间),无法计算平均值、中位数

解决

Flink 提供一个方法,可以把增量函数 AggregateFunction 和全量函数 ProcessWindowFunction 结合 使用

对于一个窗口来说,Flink 先增量计算,窗口关闭前,将增量计算结果(一般只有一个)发送给 ProcessWindowFunction 作为输入再进行处理。

案例

城市的气温数据,每隔30秒,获得该城市的这30秒内的温度最大值、最小值、平均值

准备数据

在工程目录下创建 data/qiwen.txt,内容如下:

江苏,苏州,20.5,2021-01-29 16:00:00
江苏,盐城,24.5,2021-01-29 16:00:20
江苏,盐城,28.5,2021-01-29 16:00:30
江苏,盐城,23.5,2021-01-29 16:00:40
江苏,苏州,21.5,2021-01-29 16:00:10
江苏,苏州,22.5,2021-01-29 16:00:20
江苏,苏州,24.5,2021-01-29 16:00:30
江苏,苏州,23.5,2021-01-29 16:00:40
江苏,苏州,22.5,2021-01-29 16:00:50

java

package std2;

import org.apache.flink.api.common.RuntimeExecutionMode;
import org.apache.flink.api.common.functions.AggregateFunction;
import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.common.serialization.SimpleStringSchema;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
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.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.streaming.connectors.kafka.FlinkKafkaConsumer;
import org.apache.flink.util.Collector;
import org.apache.kafka.clients.consumer.ConsumerConfig;

import java.util.Properties;

public class Agg_Process_Fun_kafka {

    static class TempRecord {
        private String province;
        private String city;
        private Double temp;
        private String time;

        public TempRecord(String province, String city, Double temp, String time) {
            this.province = province;
            this.city = city;
            this.temp = temp;
            this.time = time;
        }

        public String getProvince() {
            return province;
        }

        public void setProvince(String province) {
            this.province = province;
        }

        public String getCity() {
            return city;
        }

        public void setCity(String city) {
            this.city = city;
        }

        public Double getTemp() {
            return temp;
        }

        public void setTemp(Double temp) {
            this.temp = temp;
        }

        public String getTime() {
            return time;
        }

        public void setTime(String time) {
            this.time = time;
        }
    }

    static class Result {
        private String key;
        // flink运算时,要将较大的值赋给该属性,所以要初始化最小值
        private Double max = Double.MIN_VALUE;

        // flink运算时,要将较小的值赋给该属性,所以要初始化最大值
        private Double min = Double.MAX_VALUE;

        private Double sum = 0.0;

        private Double avg = 0.0;

        private Integer count = 0;

        public Result() {
        }

        @Override
        public String toString() {
            return "TempRecordAggsResult{" +
                    "key='" + key + '\'' +
                    ", max=" + max +
                    ", min=" + min +
                    ", sum=" + sum +
                    ", avg=" + avg +
                    ", count=" + count +
                    '}';
        }

        public String getKey() {
            return key;
        }

        public void setKey(String key) {
            this.key = key;
        }

        public Double getMax() {
            return max;
        }

        public void setMax(Double max) {
            this.max = max;
        }

        public Double getMin() {
            return min;
        }

        public void setMin(Double min) {
            this.min = min;
        }

        public Double getSum() {
            return sum;
        }

        public void setSum(Double sum) {
            this.sum = sum;
        }

        public Double getAvg() {
            return avg;
        }

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

        public Integer getCount() {
            return count;
        }

        public void setCount(Integer count) {
            this.count = count;
        }
    }

    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setRuntimeMode(RuntimeExecutionMode.STREAMING);
        env.setParallelism(2);

        Properties pro = new Properties();
        pro.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop1:9092,hadoop2:9092,hadoop3:9092");
        pro.setProperty(ConsumerConfig.GROUP_ID_CONFIG,"consumer-group");
        pro.setProperty(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,"latest");

        // 内置连接器,创建FlinkKafka消费者,需要指定主题、反序列化、kafka配置
        FlinkKafkaConsumer<String> fkc = new FlinkKafkaConsumer<>("qiwen", new SimpleStringSchema(), pro);

        DataStreamSource<String> source = env.addSource(fkc);

        SingleOutputStreamOperator<Result> result = source
                .flatMap(new FlatMapFunction<String, TempRecord>() {
                    @Override
                    public void flatMap(String value, Collector<TempRecord> out) throws Exception {
                        String[] arr = value.split(",");
                        Double temp = 0.0;
                        if (arr[2] != null) {
                            temp = Double.parseDouble(arr[2]);
                        }
                        TempRecord tempRecord = new TempRecord(arr[0], arr[1], temp, arr[3]);
                        out.collect(tempRecord);
                    }
                })
                .keyBy(TempRecord::getCity)
                .window(TumblingProcessingTimeWindows.of(Time.seconds(5)))
                .aggregate(
                        //增量计算
                        new AggFunImpl(),
                        //全量计算
                        new ProWinFunImpl());
        result.print();

        env.execute();
    }

    static class AggFunImpl implements AggregateFunction<TempRecord, Result, Result> {
        @Override
        public Result createAccumulator() {
            return new Result();
        }

        @Override
        public Result add(TempRecord value, Result accumulator) {
            Double maxTemp = value.getTemp() > accumulator.getMax() ? value.getTemp() : accumulator.getMax();
            Double minTemp = value.getTemp() < accumulator.getMin() ? value.getTemp() : accumulator.getMin();
            accumulator.setKey(value.getProvince() + "," + value.getCity());
            accumulator.setMax(maxTemp);
            accumulator.setMin(minTemp);
            accumulator.setSum(value.getTemp() + accumulator.getSum());
            accumulator.setCount(accumulator.getCount() + 1);
            return accumulator;
        }

        @Override
        public Result getResult(Result accumulator) {
            return accumulator;
        }

        @Override
        public Result merge(Result a, Result b) {
            return null;
        }
    }

    static class ProWinFunImpl extends ProcessWindowFunction<Result, Result, String, TimeWindow> {
        @Override
        public void process(String s, ProcessWindowFunction<Result, Result, String, TimeWindow>.Context context, Iterable<Result> elements, Collector<Result> out) {
            if (elements.iterator().hasNext()) {
                Result result = elements.iterator().next();
//                                    System.out.println("result:" + result.toString());
                result.setAvg(result.getSum() / result.getCount());
                out.collect(result);
            }
        }
    }
}

启动zk

登录 hadoop1,执行下面命令:

/program/bin/zk.sh start

启动kafka

登录 hadoop1,执行下面命令:

/program/bin/kafka.sh start

在 idea 中运行 flink 程序

启动生产者、指定主题

登录 hadoop1,执行下面命令:

kafka-console-producer.sh --broker-list hadoop1:9092,hadoop2:9092,hadoop3:9092 --topic qiwen

然后输入:

江苏,苏州,20.5,2021-01-29 16:00:00
江苏,盐城,24.5,2021-01-29 16:00:20
江苏,盐城,28.5,2021-01-29 16:00:30
江苏,盐城,23.5,2021-01-29 16:00:40
江苏,苏州,21.5,2021-01-29 16:00:10
江苏,苏州,22.5,2021-01-29 16:00:20
江苏,苏州,24.5,2021-01-29 16:00:30
江苏,苏州,23.5,2021-01-29 16:00:40
江苏,苏州,22.5,2021-01-29 16:00:50

执行结果

在 idea 控制台中打印结果如下:

1> TempRecordAggsResult{key='江苏,苏州', max=24.5, min=20.5, sum=135.0, avg=22.5, count=6}
2> TempRecordAggsResult{key='江苏,盐城', max=28.5, min=23.5, sum=76.5, avg=25.5, count=3}

原文出处:/show_1IX4YEbUzTxF.html