微信公众号:大数据开发运维架构
关注可了解更多大数据相关的资讯。问题或建议,请公众号留言;
如果您觉得“大数据开发运维架构”对你有帮助,欢迎转发朋友圈
从微信公众号拷贝过来,格式有些错乱,建议直接去公众号阅读
一、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{
ValueState lastStatus;
ValueState 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 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 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 sourceDS = senv.socketTextStream("localhost", 9000)
.filter(new FilterFunction() {
@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 warningDS = sourceDS.map(new MapFunction() {
@Override
public MessageInfo map(String line) throws Exception {
String[] lines = line.split(",");
return new MessageInfo(lines[0], lines[1],lines[2]);
}
}).keyBy(new KeySelector() {
@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,RUNNING
192.168.1.101,2019-04-07 21:02,DEAD
192.168.1.101,2019-04-07 21:03,DEAD
测试结果,如果两次连续状态为DEAD,且30秒内没有恢复正常,则进行报警输出,如下图所示:
这里只是讲了一种比较常用的KeyedProcessFunction,其他的后续会发出来,感谢关注!!!
如果觉得我的文章能帮到您,请关注微信公众号“大数据开发运维架构”,并转发朋友圈,谢谢支持!!!