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

说明

是 ReduceFunction 的通用版本

应用场景

电商平台,一般都要统计热门商品,或者哪个url的访问最大,典型双11,如果首页某个商品访问量没达标,要立即撤下,不能让需求较小的商品占据首页黄金位置。

需要设定窗口,统计每个url的访问量,然后再去排序

调用方式

stream
 .keyBy( ... )
 .window( ... )
 .aggregate( AggregateFunction 对象);

接口定义

@PublicEvolving
public interface AggregateFunction<IN, ACC, OUT> extends Function, Serializable {
    //创建初始累加器
    ACC createAccumulator();

    //将输入元素,进行增量聚合
    ACC add(IN var1, ACC var2);

    // 返回结果,一个窗口,只会调用一次
    OUT getResult(ACC var1);

    //在 session window 会用到。两个累加器合并为一个累加器
    ACC merge(ACC var1, ACC var2);
}

泛型具有三种类型:

  • 输入类型(IN)
  • 累加器类型(ACC)
  • 输出类型(OUT)

这样设计主要是为了解决:输入类型、累加器类型 和 输出类型 不一致的问题

同时 累加器类型 可以自定义,我们可以在 累加器类型 里构建我们想要的数据结构。
比如我们要计算一个窗口内某个字段的平均值,那么ACC中要保存总和以及个数

案例

统计各书出现的次数

数据

id,文章标题
111,java从入门到入坟
222,mysql从入门到入坟
333,hadoop从入门到放弃
333,hadoop从入门到放弃
444,flink从入门到放弃

思路

  1. 根据字符串,进行拆分,并封装到对象中
  2. keyBy() 根据书名进行分组
  3. 实现 aggregate(),统计各本书出现的次数

连接kafka

代码

package day4;

import org.apache.flink.api.common.RuntimeExecutionMode;
import org.apache.flink.api.common.functions.AggregateFunction;
import org.apache.flink.api.common.functions.MapFunction;
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.windowing.assigners.TumblingProcessingTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer;
import org.apache.kafka.clients.consumer.ConsumerConfig;

import java.util.Properties;

public class Std6agg {

    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setRuntimeMode(RuntimeExecutionMode.AUTOMATIC);//自动模式,根据数据源自行判断
        env.setParallelism(1);

        // 定义kafka相关配置项
        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<>("guanggao", new SimpleStringSchema(), pro);

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

        SingleOutputStreamOperator<Result> agg=text.map(new MapFunImpl())
                .keyBy(value -> value.getTitle())//流处理中的分组
                .window(TumblingProcessingTimeWindows.of(Time.seconds(15)))
                .aggregate(new AggFunImpl());
        agg.print();
        //启动并等待程序结束,可传入任务名
        env.execute();
    }

    static class MapFunImpl implements MapFunction<String, Article> {
        @Override
        public Article map(String s) throws Exception {
            String[] arr=s.split(",");
            Article article=new Article();
            article.setId(arr[0]);
            article.setTitle(arr[1]);
            return article;
        }
    }

    static class AggFunImpl implements AggregateFunction<Article, Result, Result> {

        @Override
        public Result createAccumulator() {
            return new Result("",0);
        }

        @Override
        public Result add(Article value, Result accumulator) {
            accumulator.setKey(value.getTitle());
            accumulator.setNum( accumulator.getNum()  +  1  );
            return accumulator;
        }

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

        @Override
        public Result merge(Result a, Result b) {

            return null;
        }
    }

    //文章封装类
    static class Article{
        String id;
        String title;
        public String getId() {
            return id;
        }
        public void setId(String id) {
            this.id = id;
        }
        public String getTitle() {
            return title;
        }
        public void setTitle(String title) {
            this.title = title;
        }

        @Override
        public String toString() {
            return "Article{" +
                    "id='" + id + '\'' +
                    ", title='" + title + '\'' +
                    '}';
        }
    }
    //结果封装类
    static class Result{
        String key;
        Integer num;
        public Result(String key, Integer num) {
            this.key = key;
            this.num = num;
        }
        public String getKey() {
            return key;
        }
        public void setKey(String key) {
            this.key = key;
        }
        public Integer getNum() {
            return num;
        }
        public void setNum(Integer num) {
            this.num = num;
        }
        @Override
        public String toString() {
            return "Result{" +
                    "key='" + key + '\'' +
                    ", num=" + num +
                    '}';
        }
    }
}

启动 kafka 环境

启动 hadoop1、hadoop2、hadoop3 虚拟机

启动zookeeper

登录 hadoop1,执行下面命令

/program/bin/zk.sh start

启动kafka

登录 hadoop1,执行下面命令

/program/bin/kafka.sh start

创建主题 guanggao

flink 从该主题 读取 数据:

/program/kafka_2.12-2.4.1/bin/kafka-topics.sh --create --zookeeper hadoop1:2181 --replication-factor 3 --partitions 3 --topic guanggao

注意: zookeeper 的主题名称不要有 . 或 _,详见 链接

启动 guanggao 主题生产者

启动另一个窗口执行下面命令:

/program/kafka_2.12-2.4.1/bin/kafka-console-producer.sh --broker-list hadoop1:9092 --topic guanggao

在上面的环境中,每间隔 5s 输入:

111,java从入门到入坟
222,mysql从入门到入坟
333,hadoop从入门到放弃
333,hadoop从入门到放弃
444,flink从入门到放弃

观察 idea 控制台,显示各本书出现的次数

连接 socket

代码

import org.apache.flink.api.common.RuntimeExecutionMode;
import org.apache.flink.api.common.functions.AggregateFunction;
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.windowing.assigners.TumblingProcessingTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;

public class Std12_agg {
    public static void main(String[] args) throws Exception {

        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setRuntimeMode(RuntimeExecutionMode.AUTOMATIC);//自动模式,根据数据源自行判断
        DataStream<String> dataStream = env.socketTextStream("hadoop1", 9999);
        SingleOutputStreamOperator<Article> soso=dataStream.map(new MapFunction<String, Article>() {
            @Override
            public Article map(String s) throws Exception {
                String[] arr=s.split(",");
                Article article=new Article();
                article.setId(arr[0]);
                article.setTitle(arr[1]);
                return article;
            }
        });
        KeyedStream<Article,String> ks=soso.keyBy(new KeySelector<Article,String>(){
            /**
             *
             * @param t:传入的元祖,格式:(广告id,点击数量)
             * @return
             * @throws Exception
             */
            @Override
            public String getKey(Article t) throws Exception {
                return t.getTitle();
            }
        });//流处理中的分组
        //统计各个key出现的次数
        SingleOutputStreamOperator<Result> agg = ks.window(TumblingProcessingTimeWindows.of(Time.seconds(15)))
                .aggregate(new AggFunImpl());
        agg.print();
        //启动并等待程序结束,可传入任务名
        env.execute();
    }

    static class AggFunImpl implements AggregateFunction<Article, Result, Result> {

        @Override
        public Result createAccumulator() {
            return new Result("",0);
        }

        @Override
        public Result add(Article value, Result accumulator) {
            accumulator.setKey(value.getTitle());
            accumulator.setNum( accumulator.getNum()  +  1  );
            return accumulator;
        }

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

        @Override
        public Result merge(Result a, Result b) {
            a.setNum(  a.getNum() + b.getNum() );
            return a;
        }
    }
    //文章封装类
    static class Article{
        String id;
        String title;
        public String getId() {
            return id;
        }
        public void setId(String id) {
            this.id = id;
        }
        public String getTitle() {
            return title;
        }
        public void setTitle(String title) {
            this.title = title;
        }
    }
    //结果封装类
    static class Result{
        String key;
        Integer num;
        public Result(String key, Integer num) {
            this.key = key;
            this.num = num;
        }
        public String getKey() {
            return key;
        }
        public void setKey(String key) {
            this.key = key;
        }
        public Integer getNum() {
            return num;
        }
        public void setNum(Integer num) {
            this.num = num;
        }
        @Override
        public String toString() {
            return "Result{" +
                    "key='" + key + '\'' +
                    ", num=" + num +
                    '}';
        }
    }
}

测试

在linux 执行下面命令:

nc -lk 9999

启动java程序

在 nc 窗口输入下面内容:

111,java从入门到入坟
222,mysql从入门到入坟
333,hadoop从入门到放弃
333,hadoop从入门到放弃
444,flink从入门到放弃

观察 idea 控制台,显示各本书出现的次数


原文出处:/show_1IX22Z8RyX5K.html