Flink中实现自定义ProcessFunction实现定时器、侧输出

本文介绍如何在Flink中通过自定义KeyedProcessFunction实现基于价格阈值的数据分流,并设置定时器来处理时间相关的业务逻辑。

在Flink中,当我们需要获取到算子的Processing Time或者Water Mark以及定时器时,可以实现ProcessFunction函数。
目前该函数主要有:K恶业的ProcessFuntion,ProcessFunction,CoPropcessFunction等,核心功能主要如下:

  • 可以使用状态计算,能够在算子中访问Keyed State
  • 可以设置定时器
  • 侧输出,可以将一部分数据发送到另外一个数据流中,而且输出的两个数据流数据类型可以不一样。

如下自定义实现一个KeyedProcessFunction:


public class MyKeyedProcessFunctionJava extends KeyedProcessFunction<String, StockPrice,String> {
   
   

    private ValueState<Long> currentTime;
    private ValueState<Double> lastPrice ;
    private static long  intervalMs = 500 ;

    OutputTag<StockPrice> highLevel ;
    OutputTag<StockPrice> middleLevel;
    OutputTag<StockPrice> lowLevel;

    public MyKeyedProcessFunctionJava(OutputTag<StockPrice> highLevel,
                                      OutputTag<StockPrice> middleLevel,
                                      OutputTag<StockPrice> lowLevel){
   
   
        this.lowLevel = lowLevel;
        this.middleLevel= middleLevel;
        this.highLevel = highLevel;

    }
    @Override
    public void open(Configuration parameters) 
在 Apache Flink 中,“延迟消费”通常指的是:**接收到消息后,并不立即处理,而是等待一段时间(如 10 秒、1 分钟)后再进行处理**。这种需求常见于: - 延迟数据分析(例如订单创建后 5 分钟未支付则标记为异常) - 缓冲窗口以聚合多个事件 - 实现“至少延迟 X 时间再处理”的业务规则 Flink 提供了多种机制来实现“延迟消费”,核心依赖 **`KeyedProcessFunction` + 定时器(Timer)**。 --- ## ✅ 核心原理 Flink 本身是流式实时处理引擎,默认是“来了就处理”。要实现“延迟消费”,必须: 1. 将数据暂存到状态中(State) 2. 注册一个未来的定时器Processing Time 或 Event Time) 3. 在定时器触发时再读取并处理数据 > ⚠️ 注意:这不是 Kafka 的“延迟拉取”,而是 Flink 内部对数据的“延迟处理”。 --- ## ✅ 方法一:使用 `KeyedProcessFunction` + `ValueState` + 定时器(推荐) ### 🎯 场景 每条消息需要延迟固定时间(如 10 秒)后再输出。 ### ✅ 示例代码:延迟 10 秒处理消息 ```java import org.apache.flink.api.common.state.ValueState; import org.apache.flink.api.common.state.ValueStateDescriptor; import org.apache.flink.api.common.typeinfo.TypeHint; import org.apache.flink.api.common.typeinfo.TypeInformation; import org.apache.flink.configuration.Configuration; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.KeyedProcessFunction; import org.apache.flink.util.Collector; public class DelayedConsumptionExample { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); // 输入数据流:(key, message, timestamp) env.fromElements( Tuple3.of("user1", "login", System.currentTimeMillis()), Tuple3.of("user2", "order", System.currentTimeMillis()), Tuple3.of("user1", "click", System.currentTimeMillis()) ).returns(new TypeHint<Tuple3<String, String, Long>>() {}) .keyBy(t -> t.f0) // 按 key 分组(用于 Keyed State) .process(new DelayMessageByKey(10_000)) // 延迟 10 秒 .print(); env.execute("Flink Delayed Consumption"); } // 自定义类型别名 public static class Tuple3<T0, T1, T2> { public T0 f0; public T1 f1; public T2 f2; public Tuple3() {} public Tuple3(T0 f0, T1 f1, T2 f2) { this.f0 = f0; this.f1 = f1; this.f2 = f2; } } // 延迟处理器 public static class DelayMessageByKey extends KeyedProcessFunction<String, Tuple3<String, String, Long>, String> { private final long delayMs; private ValueState<Tuple3<String, String, Long>> bufferedMsg; public DelayMessageByKey(long delayMs) { this.delayMs = delayMs; } @Override public void open(Configuration parameters) { ValueStateDescriptor<Tuple3<String, String, Long>> descriptor = new ValueStateDescriptor<>( "bufferedMsg", TypeInformation.of(new TypeHint<Tuple3<String, String, Long>>() {}) ); bufferedMsg = getRuntimeContext().getState(descriptor); } @Override public void processElement(Tuple3<String, String, Long> value, Context ctx, Collector<String> out) throws Exception { // 缓存消息到状态 bufferedMsg.update(value); // 注册一个处理时间定时器:当前时间 + 延迟时间 long timerTs = ctx.timerService().currentProcessingTime() + delayMs; ctx.timerService().registerProcessingTimeTimer(timerTs); } @Override public void onTimer(long timestamp, OnTimerContext ctx, Collector<String> out) { // 定时器触发 → 取出缓存的消息并处理 Tuple3<String, String, Long> msg = bufferedMsg.value(); if (msg != null) { out.collect("Delayed: [" + msg.f0 + "] " + msg.f1 + " (originally at " + new java.util.Date(msg.f2) + ")"); // 清理状态 bufferedMsg.clear(); } } } } ``` ### ✅ 输出示例(假设延迟 10 秒): ``` Delayed: [user1] login (originally at Wed Apr 05 10:00:00 CST 2025) Delayed: [user2] order (originally at Wed Apr 05 10:00:00 CST 2025) ... ``` > 每条消息都会被延迟约 10 秒后才输出。 --- ## ✅ 方法二:支持多消息延迟 —— 使用 `ListState` 如果一个 key 下有多条消息需要延迟,就不能用 `ValueState`,而要用 `ListState`: ```java private ListState<Tuple3<String, String, Long>> bufferedMessages; @Override public void open(Configuration parameters) { ListStateDescriptor<Tuple3<String, String, Long>> desc = new ListStateDescriptor<>( "bufferedMessages", TypeInformation.of(new TypeHint<Tuple3<String, String, Long>>() {}) ); bufferedMessages = getRuntimeContext().getListState(desc); } @Override public void processElement(Tuple3<String, String, Long> value, Context ctx, Collector<String> out) { bufferedMessages.add(value); // 添加到列表 if (!getRuntimeContext().getState(ValueState.class).isPresent()) { // 简化判断是否已注册 long timerTs = ctx.timerService().currentProcessingTime() + delayMs; ctx.timerService().registerProcessingTimeTimer(timerTs); } } @Override public void onTimer(long timestamp, OnTimerContext ctx, Collector<String> out) { for (Tuple3<String, String, Long> msg : bufferedMessages.get()) { out.collect("Delayed: " + msg.f1); } bufferedMessages.clear(); } ``` --- ## ✅ 方法三:基于 Event Time 的延迟处理(更精确) 如果你希望基于事件时间(而非系统时间)延迟处理,可以使用 **Event Time Timer**: ```java ctx.timerService().registerEventTimeTimer(value.f2 + delayMs); // 基于 event time ``` 但注意:只有当 Watermark 超过该时间时才会触发,适用于有序或轻微乱序场景。 --- ## ✅ 方法四:个性化延迟(每条消息不同延迟时间) 参考前文:使用 `MapState<msgId, Data>` 并为每条消息注册独立定时器,在 `onTimer` 中扫描到期消息。 --- ## ✅ 关键点总结 | 特性 | 说明 | |------|------| | ✅ 支持延迟 | 必须结合 `ProcessFunction` + `State` + `Timer` | | ⏱ 定时器类型 | `ProcessingTimeTimer`(立即触发)、`EventTimeTimer`(等 watermark) | | 💾 状态存储 | `ValueState`(单条)、`ListState`(多条)、`MapState`(带 ID) | | 🔁 清理状态 | 处理完务必 `.clear()` 防止内存泄漏 | | 🔄 容错保障 | 所有状态和定时器都会随 Checkpoint 持久化,故障恢复后继续 | --- ## ✅ 常见应用场景 | 场景 | 实现方式 | |------|----------| | 订单超时未支付检测 | 接收订单 → 延迟 30 分钟 → 查询是否已支付 | | 用户行为去重窗口 | 缓存用户动作 5 分钟 → 判断是否有重复操作 | | 数据补全等待期 | 等待关联数据到达(最多等 10 秒)→ 合并输出 | | 延迟统计指标 | 延迟 1 分钟再计入实时大盘(防抖动) | --- ## ✅ 性能与注意事项 - ❗ 不要无限缓存:设置最大延迟时间或 TTL(可通过 `StateTtl` 设置) - ⚠️ 高频消息慎用:避免大量定时器导致性能下降 - ✅ 合理选择 key:按 key 分布负载,避免热点 - 📦 使用 `ListState` 时注意序列化开销 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值