flink定时器(Timer)

flink定时器(Timer)

定时器(Timer)和定时服务(TimerService)

只有在 KeyedStream 中才支持使用 TimerService 设置定时器的 操作。所以一般情况下,我们都是先做了 keyBy 分区之后,再去定义处理操作;代码中更加常见的处理函数是 KeyedProcessFunction,最基本的 ProcessFunction 反而出镜率没那么高。

接下来我们就先从定时服务(TimerService)入手,详细讲解 KeyedProcessFunction 的用法。

KeyedProcessFunction 的一个特色,就是可以灵活地使用定时器。

定时器(timers)是处理函数中进行时间相关操作的主要机制。在.onTimer()方法中可以实 现定时处理的逻辑,而它能触发的前提,就是之前曾经注册过定时器、并且现在已经到了触发 时间。注册定时器的功能,是通过上下文中提供的“定时服务”(TimerService)来实现的。

TimerService 是 Flink 关于时间和定时器的基础服务接口,包含以下六个方法:

// 获取当前的处理时间
long currentProcessingTime();
// 获取当前的水位线(事件时间)
long currentWatermark();
// 注册处理时间定时器,当处理时间超过 time 时触发
void registerProcessingTimeTimer(long time);
// 注册事件时间定时器,当水位线超过 time 时触发
void registerEventTimeTimer(long time);
// 删除触发时间为 time 的处理时间定时器
void deleteProcessingTimeTimer(long time);
// 删除触发时间为 time 的事件时间定时器
void deleteEventTimeTimer(long time)

六个方法可以分成两大类:基于处理时间和基于事件时间。而对应的操作主要有三个:获 取当前时间,注册定时器,以及删除定时器。需要注意,尽管处理函数中都可以直接访问 TimerService,不过只有基于 KeyedStream 的处理函数,才能去调用注册和删除定时器的方法; 未作按键分区的 DataStream 不支持定时器操作,只能获取当前时间。

基于 KeyedStream 注册定时器时,会传入一个定时器触发的时间戳,这个时间戳的定时器对于每个 key 都是有效的。这样,我们的代码并不需要做额外的处理,底层就可以直接对不同 key 进行独立的处理操作了。

这里注意定时器的时间戳必须是毫秒数,所以我们得到整秒之后还要乘以 1000。定时器 默认的区分精度是毫秒。 另外 Flink 对.onTimer()和.processElement()方法是同步调用的(synchronous),所以也不会 出现状态的并发修改。 Flink 的定时器同样具有容错性,它和状态一起都会被保存到一致性检查点(checkpoint) 中。当发生故障时,Flink 会重启并读取检查点中的状态,恢复定时器。如果是处理时间的定时器,有可能会出现已经“过期”的情况,这时它们会在重启时被立刻触发。

KeyedProcessFunction 的使用

KeyedProcessFunction 可以说是处理函数中的“嫡系部队”,可以认为是 ProcessFunction 的 一个扩展。我们只要基于 keyBy 之后的 KeyedStream,直接调用.process()方法,这时需要传入 的参数就是 KeyedProcessFunction 的实现类。

stream.keyBy( t -> t.f0 )
.process(new MyKeyedProcessFunction())

类似地,KeyedProcessFunction 也是继承自 AbstractRichFunction 的一个抽象类,源码中定 义如下:

public abstract class KeyedProcessFunction<K, I, O> extends AbstractRichFunction 
{
...
public abstract void processElement(I value, Context ctx, Collector<O> out) 
throws Exception;
public void onTimer(long timestamp, OnTimerContext ctx, Collector<O> out) 
throws Exception {}
public abstract class Context {...}
...
}

可以看到与 ProcessFunction 的定义几乎完全一样,区别只是在于类型参数多了一个 K, 这是当前按键分区的 key 的类型。同样地,我们必须实现一个.processElement()抽象方法,用来处理流中的每一个数据;另外还有一个非抽象方法.onTimer(),用来定义定时器触发时的回调操作。由于定时器只能在 KeyedStream 上使用,所以到了 KeyedProcessFunction 这里,我们 才真正对时间有了精细的控制,定时方法.onTimer()才真正派上了用场。

下面是一个使用处理时间定时器的具体示例:

package com.ambitfly.timer;

import com.ambitfly.pojo.Event;
import com.ambitfly.source.ClickSource;
import org.apache.flink.api.common.functions.RuntimeContext;
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.functions.KeyedProcessFunction;
import org.apache.flink.util.Collector;

import java.sql.Timestamp;

public class ProcessingTimeTimerTest {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

        SingleOutputStreamOperator<Event> stream = env.addSource(new ClickSource());

        stream.keyBy(data->data.user).process(new KeyedProcessFunction<String, Event, String>() {
            @Override
            public void processElement(Event event, KeyedProcessFunction<String, Event, String>.Context ctx, Collector<String> out) throws Exception {
                Long currTs = ctx.timerService().currentProcessingTime();
                out.collect(ctx.getCurrentKey()+" 数据到达,到达时间:"+new Timestamp(currTs));

                // 注册一个十秒后的定时器
                ctx.timerService().registerProcessingTimeTimer(currTs + 10*1000L);



            }

            @Override
            public void onTimer(long timestamp, KeyedProcessFunction<String, Event, String>.OnTimerContext ctx, Collector<String> out) throws Exception {
                out.collect(ctx.getCurrentKey()+" 定时器触发时间"+new Timestamp(timestamp));

            }
        }).print();

        env.execute();


    }
}

在上面的代码中,由于定时器只能在 KeyedStream 上使用,所以先要进行 keyBy;这里 的.keyBy(data -> true)是将所有数据的 key 都指定为了 true,其实就是所有数据拥有相同的 key, 会分配到同一个分区。

之后我们自定义了一个 KeyedProcessFunction,其中.processElement()方法是每来一个数据 都会调用一次,主要是定义了一个 10 秒之后的定时器;而.onTimer()方法则会在定时器触发时 调用。所以我们会看到,程序运行后先在控制台输出“数据到达”的信息,等待 10 秒之后, 又会输出“定时器触发”的信息,打印出的时间间隔正是 10 秒。

当然,上面的例子是处理时间的定时器,所以我们是真的需要等待 10 秒才会看到结果。 事件时间语义下,又会有什么不同呢?我们可以对上面的代码略作修改,做一个测试:

package com.ambitfly.timer;

import com.ambitfly.pojo.Event;
import com.ambitfly.source.ClickSource;
import org.apache.flink.api.common.eventtime.SerializableTimestampAssigner;
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.KeyedProcessFunction;
import org.apache.flink.util.Collector;

import java.sql.Timestamp;

public class EventTimeTimerTest {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

        SingleOutputStreamOperator<Event> stream = env.addSource(new ClickSource())
                        .assignTimestampsAndWatermarks(WatermarkStrategy.<Event>forMonotonousTimestamps()
                                .withTimestampAssigner(new SerializableTimestampAssigner<Event>() {
                                    @Override
                                    public long extractTimestamp(Event event, long recordTimestamp) {
                                        return event.timestamp;
                                    }
                                }));

        stream.keyBy(data->data.user).process(new KeyedProcessFunction<String, Event, String>() {
            @Override
            public void processElement(Event event, KeyedProcessFunction<String, Event, String>.Context ctx, Collector<String> out) throws Exception {
                Long currTs = ctx.timestamp();
                out.collect(ctx.getCurrentKey()+" 数据到达,时间戳:"+new Timestamp(currTs) + " watermark: " + ctx.timerService().currentWatermark());

                // 注册一个十秒后的定时器
                ctx.timerService().registerEventTimeTimer(currTs + 10*1000L);



            }

            @Override
            public void onTimer(long timestamp, KeyedProcessFunction<String, Event, String>.OnTimerContext ctx, Collector<String> out) throws Exception {
                out.collect(ctx.getCurrentKey()+" 定时器触发时间"+new Timestamp(timestamp));

            }
        }).print();

        env.execute();


    }
}
  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
Flink提供了两种类型的定时器:事件时间定时器和处理时间定时器。 事件时间定时器基于事件时间,可以在数据流中插入一个事件时间戳,一旦到达指定时间,就会触发定时器。事件时间定时器适用于需要等待一段时间以获取完整结果的场景,例如处理窗口。 处理时间定时器基于处理时间,可以在数据到达后的固定时间间隔后触发定时器。处理时间定时器适用于需要在一定时间间隔内执行某个操作的场景,例如清理过期数据。 下面是一个使用处理时间定时器的示例代码: ``` DataStreamSource<String> source = env.fromElements("a", "b", "c"); source .map(new MapFunction<String, Tuple2<String, Long>>() { @Override public Tuple2<String, Long> map(String value) throws Exception { return Tuple2.of(value, System.currentTimeMillis() + 10000L); } }) .keyBy(0) .process(new KeyedProcessFunction<String, Tuple2<String, Long>, String>() { @Override public void processElement(Tuple2<String, Long> value, Context ctx, Collector<String> out) throws Exception { // 注册处理时间定时器,10秒后触发 ctx.timerService().registerProcessingTimeTimer(value.f1); // 保存状态 ValueState<String> state = getRuntimeContext().getState(new ValueStateDescriptor<>("value-state", String.class)); state.update(value.f0); } @Override public void onTimer(long timestamp, OnTimerContext ctx, Collector<String> out) throws Exception { // 处理时间定时器触发,输出状态中的值 ValueState<String> state = getRuntimeContext().getState(new ValueStateDescriptor<>("value-state", String.class)); out.collect(state.value()); } }) .print(); env.execute(); ``` 在上面的示例中,我们先从一个字符串数据源中获取数据,并使用MapFunction将每个字符串与当前时间戳组成一个Tuple2。然后,我们使用KeyedProcessFunction对每个Tuple2进行处理,其中我们注册了处理时间定时器,并将状态保存在ValueState中。当定时器触发时,我们可以从状态中获取值并输出。 需要注意的是,处理时间定时器的触发时间是相对于 Flink JobManager 的机器时间的,而不是相对于数据流中的事件时间。因此,在使用处理时间定时器时应特别注意处理时间与事件时间之间的差异。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值