Flink教程(11) KeyedProcessFunction的使用

一、前言

KeyedProcessFunction是用来处理KeyedStream的。每有一个数据进入算子,则会触发一次processElement()的处理。它还提供了计时器的功能,在特定场景下,非常适合。

二、结构

KeyedProcessFunction继承AbstractRichFunction,它和ProcessFunction类似,都有processElement()、onTimer(),且都是富函数,自然有open()和close()方法。
在这里插入图片描述

1. processElement

先看下processElement的参数,依次是输入值、上下文、输出值。

public abstract void processElement(I value, Context ctx, Collector<O> out)
  • 每一个数据进入算子,都会执行processElement处理它
  • 返回0、1、多个输出值

2. 上下文的使用

(1)获取当前流的key

ctx.getCurrentKey()

(2)侧输出流

OutputTag<SensorRecord> lowTempTag = new OutputTag<>("lowTemp");

if (value.getRecord()< 10){
    ctx.output(lowTempTag, value);
}

(3)时间服务
事件时间
注意Watermark是整个数据流的,和在KeyBy之前还是之后没有关系,Watermark和source的并行度有关,如果在自己测试调试功能时,可以先暂时设置并行度为1,方便测试。

//获取当前数据流的水位线
long currentWatermark = ctx.timerService().currentWatermark();

//设置定时器的时间为当前水位线+10秒
long ts = currentWatermark + 10000L;

//注册事件时间定时器
ctx.timerService().registerEventTimeTimer(ts);

//删除事件时间定时器
ctx.timerService().deleteEventTimeTimer(ts);

处理时间

//获取当前数据处理时间
long currentProcessTime = ctx.timerService().currentProcessingTime();

//设置定时器的时间为当前水位线+10秒
long ts = currentProcessTime + 10000L;

//注册处理时间定时器
ctx.timerService().registerProcessingTimeTimer(ts);

//删除处理时间定时器
ctx.timerService().deleteProcessingTimeTimer(ts);

3. onTimer

在定时器满足时间条件时,会触发onTimer,可以用out输出返回值。

public void onTimer(long timestamp, OnTimerContext ctx, Collector<O> out)

三、温度报警案例

1. 温度记录实体

public class TempRecord {

    private String province;

    private String city;

    private String deviceId;

    private Double temp;

    private LocalDateTime eventTime;
	省略其它。。。
}

2. 主程序

public class Test06_KeyedProcessFunction {

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

        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        env.setParallelism(1);

        //从Flink1.12开始,默认为EventTime了,所以下面一句可以省略
        env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);

        DataStreamSource<String> source = env.socketTextStream(BaseConstant.URL, BaseConstant.PORT);

        SingleOutputStreamOperator<TempRecord> dataStream = source
                .flatMap(new TempRecordUtils.BeanFlatMap())
                .assignTimestampsAndWatermarks(
                        WatermarkStrategy.<TempRecord>forBoundedOutOfOrderness(Duration.ofSeconds(1))
                                .withTimestampAssigner(new SerializableTimestampAssigner<TempRecord>() {

                                    @Override
                                    public long extractTimestamp(TempRecord element, long recordTimestamp) {
                                        return element.getTimeEpochMilli();
                                    }
                                })
                );

        dataStream
                .keyBy(TempRecord::getCity)
                .process(new MyKeyedProcessFunction(30)).print();

        env.execute();
    }
}

3. 自定义KeyedProcessFunction实现温度报警

public static class MyKeyedProcessFunction extends KeyedProcessFunction<String, TempRecord, String> {

    private int timeInterval;

    public MyKeyedProcessFunction() {
    }

    public MyKeyedProcessFunction(int timeInterval) {
        this.timeInterval = timeInterval;
    }

    //上次温度state
    private transient ValueState<Double> lastTempState;
    //上次温度描述
    private transient ValueStateDescriptor<Double> lastTempDescriptor;
    //结束时间
    private transient ValueState<Long> timeToStopState;
    //结束时间描述
    private transient ValueStateDescriptor<Long> timeToStopDescriptor;

    @Override
    public void open(Configuration parameters) throws Exception {
        super.open(parameters);

        lastTempDescriptor = new ValueStateDescriptor<Double>("last-temp", Double.class);

        timeToStopDescriptor = new ValueStateDescriptor<Long>("time-to-stop", Long.class);
    }

    // 一个窗口结束的时候调用一次(一个分组执行一次),不适合大量数据,全量数据保存在内存中,会造成内存溢出
    @Override
    public void processElement(TempRecord value, Context ctx, Collector<String> out) throws Exception {

        lastTempState = getRuntimeContext().getState(lastTempDescriptor);
        if (lastTempState.value() == null) {
            lastTempState.update(Double.MIN_VALUE);
        }

        timeToStopState = getRuntimeContext().getState(timeToStopDescriptor);
        if (timeToStopState.value() == null) {
            timeToStopState.update(0L);
        }
        
        Double lastTemp = lastTempState.value();
        Long timeToStop = timeToStopState.value();

        if (lastTemp < value.getTemp()) {

            if (timeToStop.equals(0L)) {

                long currentWatermark = ctx.timerService().currentWatermark();

                System.out.println("currentWatermark = " + currentWatermark);

                Long ts = currentWatermark + timeInterval * 1000L;

                if (ts > 0) {
                    ctx.timerService().registerEventTimeTimer(ts);

                    timeToStopState.update(ts);
                }
            }

        } else {
            if (timeToStopState.value() != null && !timeToStopState.value().equals(0L)) {
                ctx.timerService().deleteEventTimeTimer(timeToStopState.value());
            }

            timeToStopState.clear();
        }

        lastTempState.update(value.getTemp());
    }

    @Override
    public void onTimer(long timestamp, OnTimerContext ctx, Collector<String> out) throws Exception {
        super.onTimer(timestamp, ctx, out);

        //定时器触发,输出报警信息
        String time = LocalDateTime.ofInstant(
                Instant.ofEpochMilli(ctx.timerService().currentWatermark()), ZoneId.systemDefault()
        ).format(FormatterConstant.commonDtf);

        out.collect(ctx.getCurrentKey() + "在" + time + "前一段时间温度值连续上升");
        timeToStopState.clear();
    }

    @Override
    public void close() throws Exception {
        super.close();
        lastTempState.clear();
        timeToStopState.clear();
    }
}
  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Flink中,可以使用TIMESTAMP类型来表示事件的时间戳。对于使用毫秒级时间戳的数据,需要进行转换才能使用TIMESTAMP类型。可以使用TO_TIMESTAMP内置函数来进行转换,但是TO_TIMESTAMP函数不支持数值型时间戳的转换。因此,我们可以使用FROM_UNIXTIME函数将数值型时间戳转换为字符串形式的时间,并指定格式。然后再使用TO_TIMESTAMP函数将字符串形式的时间转换为TIMESTAMP类型。举个例子,可以使用以下SQL语句来将13位的时间戳转换为TIMESTAMP类型: ``` CREATE TABLE start_log_source ( mid_id VARCHAR, user_id INT, ... app_time BIGINT, -- 13位的时间戳(1587975971431) ts AS TO_TIMESTAMP(FROM_UNIXTIME(app_time / 1000, 'yyyy-MM-dd HH:mm:ss')), -- 定义事件时间 WATERMARK FOR ts AS ts - INTERVAL '5' SECOND -- 在ts上定义5秒延迟的 watermark ) WITH ( 'connector.type' = 'kafka', -- 使用 kafka connector 'connector.version' = 'universal', -- kafka 版本,universal 支持 0.11 以上的版本 'connector.topic' = 'start_log', -- kafka topic 'connector.properties.group.id' = 'start_log_group', 'connector.startup-mode' = 'earliest-offset', -- 从起始 offset 开始读取 'connector.properties.zookeeper.connect' = '192.168.1.109:2181', -- zookeeper 地址 'connector.properties.bootstrap.servers' = '192.168.1.109:9092', -- kafka broker 地址 'format.type' = 'json' -- 数据源格式为 json ); ``` 在上述示例中,我们定义了一个名为`start_log_source`的表,其中包含了一个名为`app_time`的BIGINT类型的字段,表示13位的时间戳。然后,我们使用TO_TIMESTAMP和FROM_UNIXTIME函数将`app_time`转换为TIMESTAMP类型的字段`ts`。另外,我们还定义了一个5秒延迟的watermark来处理事件的乱序。整个过程会通过Flink的Kafka connector读取数据并使用JSON格式进行解析。 此外,在Flink的源码中,可以找到关于设置时间戳的具体实现。在类`org.apache.flink.streaming.api.operators.TimestampedCollector#setTimestamp`中,通过判断`StreamRecord`对象是否包含时间戳来设置相应的时间戳。如果`StreamRecord`对象中有时间戳,将会将时间戳设置为重用对象的时间戳。否则,会将时间戳擦除。 总结来说,Flink中的TIMESTAMP类型可以用于表示事件的时间戳,对于使用毫秒级时间戳的数据,可以通过转换函数将其转换为TIMESTAMP类型。此外,在Flink的源码中,可以找到设置时间戳的具体实现。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [【FlinkFlink Invalid timestamp -1 Timestamp should always be none-negative or null](https://blog.csdn.net/qq_21383435/article/details/115859370)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [Flink SQL中Timestamp使用的坑](https://blog.csdn.net/zhangdongan1991/article/details/105796613)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

为啥总是用户昵称已存在

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值