flink1.12.x State状态实现 maxBy() 功能

说明

通过 state 来实现 maxBy 功能

案例说明

温度传感器发来 温度数据,由于发送数据太频繁,所以采用以下统计:

根据 传感器(id) 分组,统计各个 传感器(id)的最大温度

发送数据格式:

{"id":"1","temp":35.3,"time":"2020-01-01 10:10:00"}

java

package state;

import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.common.functions.RichMapFunction;
import org.apache.flink.api.common.serialization.SimpleStringSchema;
import org.apache.flink.api.common.state.ValueState;
import org.apache.flink.api.common.state.ValueStateDescriptor;
import org.apache.flink.api.java.functions.KeySelector;
import org.apache.flink.api.java.tuple.Tuple3;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.shaded.jackson2.com.fasterxml.jackson.databind.JsonNode;
import org.apache.flink.shaded.jackson2.com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
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.connectors.kafka.FlinkKafkaConsumer;
import org.apache.flink.util.Collector;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Properties;

public class S1ValueState {
    final static Logger log= LoggerFactory.getLogger(S1ValueState.class);
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        // 定义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<>("kafka2flink", new SimpleStringSchema(), pro);
        DataStreamSource<String> dss = env.addSource(fkc);

        KeyedStream<Tuple3<String, Double, String>, String> keyBy = dss
                .flatMap(new MyFlatMap())
                .keyBy(new KeySelector<Tuple3<String, Double, String>, String>() {
                    @Override
                    public String getKey(Tuple3<String, Double, String> value) throws Exception {
                        return value.f0;
                    }
                });

//        SingleOutputStreamOperator<Tuple3<String, Double, String>> maxSOSO = keyBy.maxBy(1);

        // 通过状态实现 maxBy() 功能
        SingleOutputStreamOperator<Tuple3<String, Double, String>> maxSOSO = keyBy.map(new RichMapFunction<Tuple3<String, Double, String>, Tuple3<String, Double, String>>() {
            private ValueState<Tuple3<String, Double, String>> vs = null;

            @Override
            public void open(Configuration parameters) throws Exception {
                // 创建 ValueStateDescriptor 对象
                ValueStateDescriptor vsd = new ValueStateDescriptor("max_value",Double.class);
                // 根据 ValueStateDescriptor 对象生成 ValueState,赋初始值
                vs = getRuntimeContext().getState(vsd);
            }

            @Override
            public Tuple3<String, Double, String> map(Tuple3<String, Double, String> data) throws Exception {
                // 从 state 中取出数据
                Tuple3<String, Double, String> dataVS = vs.value();
                Tuple3<String, Double, String> ret = null;
                // 如果 state中的数据为空,表示第一次运行,就返回传入的数据,并将传入数据放入 state中
                if(dataVS == null){
                    ret = data;
                    vs.update(data);
                }else{
                    // 如果传入数据,比state中数据 的 温度大,就更新,并返回该输入的数据
                    if(data.f1 > dataVS.f1) {
                        ret = data;
                        vs.update(data);
                    }else{// 否则,说明 state中数据 的 温度大,返回 state 中的数据
                        ret = dataVS;
                    }
                }
                return ret;
            }
        });

        maxSOSO.print();
        env.execute();
    }

    public static class MyFlatMap implements FlatMapFunction<String, Tuple3<String,Double,String>> {
        @Override
        public void flatMap(String data, Collector<Tuple3<String, Double, String>> out) throws Exception {

            String id = null;
            Double temp = null;
            String timeStr = null;

            try {
                ObjectMapper om = new ObjectMapper();
                JsonNode jsonNode = om.readTree(data);
                JsonNode idJN = jsonNode.get("id");
                JsonNode tempJN = jsonNode.get("temp");
                JsonNode timeJN = jsonNode.get("time");

                if (idJN != null) {
                    id = idJN.asText();
                }
                if (tempJN != null) {
                    temp = tempJN.asDouble();
                }
                if (timeJN != null) {
                    timeStr = timeJN.asText();
                }
                out.collect(Tuple3.of(id,temp,timeStr));
            }catch (Exception e){
                log.error("data error:"+data);
            }
        }
    }
}

启动zk

登录 hadoop1,执行下面命令:

/program/bin/zk.sh start

启动kafka

登录 hadoop1,执行下面命令:

/program/bin/kafka.sh start

在 idea 中运行 flink 程序

创建生产者、指定主题

登录 hadoop1,执行下面命令:

/program/kafka_2.12-2.4.1/bin/kafka-console-producer.sh --broker-list hadoop1:9092,hadoop2:9092,hadoop3:9092 --topic kafka2flink

第一次输入

在kafka控制输入下面内容:

{"id":"1","temp":35.3,"time":"2020-01-01 10:10:00"}

执行结果

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

1> {"id":"1","temp":35.3,"time":"2020-01-01 10:10:00"}

解释:因为只有一条数据,所以就输出该条数据

第二次输入

在kafka控制输入下面内容:

{"id":"1","temp":33.1,"time":"2020-01-01 10:12:00"}

执行结果

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

1> {"id":"1","temp":35.3,"time":"2020-01-01 10:10:00"}

解释: 由于第二次输入的数据,id 与第一次输入相同,分为一组,但 temp 较小,所以输出结果是 第一次的数据

第三次输入

在kafka控制输入下面内容:

{"id":"1","temp":42.5,"time":"2020-01-01 10:14:00"}

执行结果

1> {"id":"1","temp":42.5,"time":"2020-01-01 10:14:00"}

解释:由于第三次输入数据,id 与之前输入相同,分为一组,但 temp 最大,所以输出结果是 本次的数据


原文出处:/show_1IX48FbuKoM6.html