Flink1.10进阶:ProcessFunction介绍及KeyedProcessFunction

一、ProcessFunction介绍


    从之前的文章我们知道,转换算子是无法访问事件的时间戳信息和水位线信息的。而这在一些应用场景下,极为重要。例如我们常用的MapFunction转换算子就无法访问时间戳或者当前事件的事件时间。


    基于此,DataStream API提供了一系列的Low-Level转换算子。可以访问时间戳、watermark以及注册定时事件。还可以输出特定的一些事件,例如超时事件等。Process Function用来构建事件驱动的应用以及实现自定义的业务逻辑(使用之前的window函数和转换算子无法实现)。例如,Flink SQL就是使用Process Function实现的。
Flink提供了8个Process Function:

  • ProcessFunction dataStream

  • KeyedProcessFunction 用于KeyedStream,keyBy之后的流处理

  • CoProcessFunction 用于connect连接的流

  • ProcessJoinFunction 用于join流操作

  • BroadcastProcessFunction 用于广播

  • KeyedBroadcastProcessFunction keyBy之后的广播

  • ProcessWindowFunction 窗口增量聚合

  • ProcessAllWindowFunction 全窗口聚合


    ProcessFunction是一个低级的流处理操作,允许访问所有流应用程序的基本构件:

  • events (stream elements)

  • state (fault-tolerant, consistent, only on keyed stream)

  • timers (event time and processing time, only on keyed stream)


    ProcessFunction 可以被认为是一种提供了对 KeyedState 和定时器访问的 FlatMapFunction。每在输入流中接收到一个事件,就会调用来此函数来处理。对于容错的状态,ProcessFunction 可以通过 RuntimeContext 访问 KeyedState,类似于其他有状态函数访问 KeyedState。


    Timers 定时器可以对处理时间和事件时间的变化做一些处理。每次调用 processElement() 都可以获得一个 Context 对象,通过该对象可以访问元素的事件时间戳以及 TimerService。TimerService 可以为尚未发生的事件时间/处理时间实注册回调。当定时器到达某个时刻时,会调用 onTimer() 方法。在调用期间,所有状态再次限定为定时器创建的键,允许定时器操作 KeyedState。


二、KeyedProcessFunction简单使用


1.模拟案例场景:


    对于Hadoop运维人员,需要
监控生产环境大数据各个组件的状态信息,由于Regionserver经常挂掉,这里模拟监控Regionserver的状态信息,每次Regionserver上线和下线都会发送一个状态信息,当然Ambari也有服务自动的功能,这里如果Regionserver挂掉后,Ambari服务自启动功能在30秒以内没有将Regionserver拉起(这里只是为了测试使用30秒),这里我们就进行持续的告警,一般我们的都是发送钉钉告警 ,直到收到上线消息,告警取消。


    1).这里消息模拟从socket接收告警消息,消息格式包含三个字段分别是:主机名hostname,告警时间time,状态status(RUNNING服务正常,DEAD服务停止);


    2).接收消息之后对数据流按照主机名进行分组,对于状态为DEAD的消息,设置定时器5分钟以内如果状态不恢复为RUNNING,则定时进行告警,如果30秒内恢复RUNNING状态,认为上一条消息是误报,则删除定时器,取消报警。



2.消息类MessageInfo,代码如下:

package com.hadoop.ljs.flink110.window;
/** * @author: Created By lujisen * @company ChinaUnicom Software JiNan * @date: 2020-04-07 11:21 * @version: v1.0 * @description: com.hadoop.ljs.flink110.window */public class MessageInfo {    String  hostname;    String  msgTime;    String status;/*RUNNING 正常 DEAD 宕机*/

   MessageInfo(String hostname,String msgTime,String status){        this.hostname=hostname;        this.msgTime=msgTime;        this.status=status;    }
   public String getHostname() {        return hostname;    }
   public void setHostname(String hostname) {        this.hostname = hostname;    }
   public String getMsgTime() {        return msgTime;    }
   public void setMsgTime(String msgTime) {        this.msgTime = msgTime;    }
   public String getStatus() {        return status;    }    public void setStatus(String status) {        this.status = status;    }}


3.自定义MyKeyedProcessFunction类继承自KeyedProcessFunction,代码如下:

package com.hadoop.ljs.flink110.window;import org.apache.flink.api.common.state.ValueState;import org.apache.flink.api.common.state.ValueStateDescriptor;import org.apache.flink.configuration.Configuration;import org.apache.flink.streaming.api.functions.KeyedProcessFunction;import org.apache.flink.util.Collector;/** * @author: Created By lujisen * @company ChinaUnicom Software JiNan * @date: 2020-04-07 11:20 * @version: v1.0 * @description: com.hadoop.ljs.flink110.window */public class MyKeyedProcessFunction extends KeyedProcessFunction<String, MessageInfo, String>{
   ValueState<String> lastStatus;    ValueState<Long> warningTimer;
   @Override    public void open(Configuration parameters) throws Exception {        super.open(parameters);       lastStatus=getRuntimeContext().getState(new ValueStateDescriptor<>("lastStatus", String.class));       warningTimer=getRuntimeContext().getState(new ValueStateDescriptor<>("warning-timer", Long.class));
   }
   @Override    public void processElement(MessageInfo value, Context ctx, Collector<String> out) throws Exception {        /*获取*/        String currentStatus=value.getStatus();        Long currentTimer=warningTimer.value();

       System.out.println("currentStatus:"+currentStatus);        System.out.println("lastStatus:"+lastStatus.value());
       /*连续两次状态都是2 宕机状态,则新建定时器 30秒后进行告警*/
       if("DEAD".equals(currentStatus) && "DEAD".equals(lastStatus.value())){
           long  timeTs=Long.valueOf(ctx.timerService().currentProcessingTime())+30000L;            ctx.timerService().registerProcessingTimeTimer(timeTs);            warningTimer.update(timeTs);        }        /*如果不是连续告警,我们认为是误报警,删除定时器*/        else if(("RUNNING".equals(currentStatus) && "DEAD".equals(lastStatus.value()))){            if(null!=currentTimer){              ctx.timerService().deleteProcessingTimeTimer(currentTimer);            }            warningTimer.clear();        }        /*更新上一次的状态信息*/        lastStatus.update(value.getStatus());    }
   @Override    public void onTimer(long timestamp, OnTimerContext ctx, Collector<String> out) throws Exception {        /*输出报警信息,Regionserver两次状态监测为2 宕机*/        out.collect("主机IP:"+ctx.getCurrentKey()+" 两次Regionserver状态监测宕机,请监测!!!");    }}


4.主函数代码,仔细阅读代码注释:

package com.hadoop.ljs.flink110.window;import org.apache.flink.api.common.functions.FilterFunction;import org.apache.flink.api.common.functions.MapFunction;import org.apache.flink.api.java.functions.KeySelector;import org.apache.flink.streaming.api.TimeCharacteristic;import org.apache.flink.streaming.api.datastream.DataStream;import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;/** * @author: Created By lujisen * @company ChinaUnicom Software JiNan * @date: 2020-04-07 11:13 * @version: v1.0 * @description: com.hadoop.ljs.flink110.window */public class KeyedProcessFunctionMonitor {    public static void main(String[] args) throws Exception {
       StreamExecutionEnvironment senv = StreamExecutionEnvironment.getExecutionEnvironment();        /*设置使用EventTime作为Flink的时间处理标准,不指定默认是ProcessTime*/        senv.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
       //这里为了便于理解,设置并行度为1,默认并行度是当前机器的cpu数量        senv.setParallelism(1);        /*指定数据源 从socket的9000端口接收数据,先进行了不合法数据的过滤*/        DataStream<String> sourceDS = senv.socketTextStream("localhost", 9000)                .filter(new FilterFunction<String>() {                    @Override                    public boolean filter(String line) throws Exception {                        if (null == line || "".equals(line)) {                            return false;                        }                        String[] lines = line.split(",");                        if (lines.length != 3) {                            return false;                        }                        return true;                    }                });
       /*做了一个简单的map转换,将数据转换成MessageInfo格式,第一个字段代表是主机IP,第二个字段的代表的是消息时间,第三个字段是Regionserver状态*/        DataStream<String> warningDS = sourceDS.map(new MapFunction<String, MessageInfo>() {            @Override            public MessageInfo map(String line) throws Exception {                String[] lines = line.split(",");                return new MessageInfo(lines[0], lines[1],lines[2]);            }        }).keyBy(new KeySelector<MessageInfo, String>() {            @Override            public String getKey(MessageInfo value) throws Exception {                return value.hostname;            }        }).process(new MyKeyedProcessFunction());        /*打印报警信息*/        warningDS.print();
       senv.execute();    }}



5.程序测试


    从socket端发送数据如下,这里测试一台机器的消息:

192.168.1.101,2019-04-07 21:00,RUNNING192.168.1.101,2019-04-07 21:02,DEAD192.168.1.101,2019-04-07 21:03,DEAD



    测试结果,如果两次连续状态为DEAD,且30秒内没有恢复正常,则进行报警输出,如下图所示:

图片

    

    这里只是讲了一种比较常用的KeyedProcessFunction,其他的后续会发出来,感谢关注!!!


猜你喜欢

转载自blog.51cto.com/15080019/2653861