Flink 延时数据处理

markdown 复制代码
Flink 延时数据处理,我们第一时间想到的是 **Watermark**,但是 Watermark 真的能够完全解决数据延时问题吗?  
**肯定不能**。

通常对于延时数据的处理方式主要有 3 种:

1. **直接丢弃**  
   少量的数据丢失或许并不影响最终结果,毕竟离线任务还会再处理一遍。
2. **把迟到的部分单独再开一个 window 处理**  
   典型的"晚到数据再算一次"场景。
3. **把符合要求的数据重新导入到原窗口中**  
   触发更新(需要配合状态后端 + 查询性表/外部存储)。

下面重点介绍第 1、2 种常用方式。

参考博客:  
[Flink笔记-延迟数据处理(Out Of Order & Late、AllowedLateness & OutputTag)](https://blog.csdn.net/yangxiaobo118/article/details/100173001)

## Out Of Order & Late

这两个概念都是为了处理乱序而产生的,区别如下:

- **Out Of Order**:通过 Watermark 机制解决,属于**第一层防护**,是全局性的,通常所说的"乱序问题解决方案"指的就是这一类。
- **Late Element**:通过窗口上的 `allowedLateness` 机制解决,属于**第二层防护**,只针对特定的 window operator。

## AllowedLateness & OutputTag

DataStream API 提供了 `allowedLateness` 方法来指定是否对迟到数据进行额外处理。

- 指定 `allowedLateness` 后,Flink 会把窗口的 **EndTime + allowedLateness** 作为窗口最终被销毁的时间。
- 当某条数据的 EventTime ≤ windowEnd + allowedLateness,但 Watermark 已经 > windowEnd 时,会**立即触发窗口计算**(而不是等待)。
- 如果 EventTime > windowEnd + allowedLateness,这条数据才会被真正丢弃或走 side output。

> 默认情况下,GlobalWindow 的最大 Lateness 是 `Long.MAX_VALUE`,所以数据会一直累积,永远不会被清理。

### 核心代码示例

```java
public class AllowLateness {
    // 定义 Side Output Tag
    private static final OutputTag<Tuple2<String, Integer>> myTag =
            new OutputTag<Tuple2<String, Integer>>("myTag") {};

    public static void main(String[] args) throws Exception {
        List<Tuple2<String, Integer>> source = Lists.newArrayList();
        source.add(new Tuple2<>("qingh1", 1));
        source.add(new Tuple2<>("qingh2", 2));

        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.enableCheckpointing(20000, CheckpointingMode.EXACTLY_ONCE);
        env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);

        SingleOutputStreamOperator<String> result = env.fromCollection(source)
            .assignTimestampsAndWatermarks(new AssignerWithPunctuatedWatermarks<Tuple2<String, Integer>>() {
                @Override
                public long extractTimestamp(Tuple2<String, Integer> element, long previousElementTimestamp) {
                    return System.currentTimeMillis() - 1000;
                }

                @Nullable
                @Override
                public Watermark checkAndGetNextWatermark(Tuple2<String, Integer> lastElement, long extractedTimestamp) {
                    return new Watermark(System.currentTimeMillis() - 500);
                }
            })
            .keyBy(t -> "key")
            .timeWindow(Time.milliseconds(10))
            .allowedLateness(Time.milliseconds(10))
            .sideOutputLateData(myTag)           // 真正的 late 数据会走这里
            .process(new ProcessWindowFunction<Tuple2<String, Integer>, String, String, TimeWindow>() {
                @Override
                public void process(String key, Context context, Iterable<Tuple2<String, Integer>> elements, Collector<String> out) {
                    for (Tuple2<String, Integer> e : elements) {
                        out.collect(e.f0);
                    }
                }
            });

        // 取出 side output(真正迟到的数据)
        DataStream<Tuple2<String, Integer>> lateData = result.getSideOutput(myTag);
        lateData.print();

        env.execute("AllowLateness Demo");
    }
}

常见疑问解答

Q:为什么有时候第一条数据没打印出来?

A:可能是因为 isSkippedElement 被标记为 true。

InternalTimeServiceManager#advanceWatermark 中会判断当前元素是否已经晚于 窗口最大时间戳 + allowedLateness 。如果满足该条件,isSkippedElement = true,该元素会被直接丢弃或输出到 side output(取决于是否配置了 sideOutputLateData

加上 result.print() 后可以看到正常计算结果,side output 只包含真正"太晚"的数据。

关于侧输出(Side Output / OutputTag)

  • OutputTag 是带有名称和类型信息的侧输出标识。
  • 支持侧输出的 Function 有:
    • ProcessFunction
    • CoProcessFunction
    • ProcessWindowFunction
    • ProcessAllWindowFunction
  • 使用方式:在 Context.output(outputTag, value) 中手动输出。
  • 获取方式:SingleOutputStreamOperator.getSideOutput(tag)

手动把数据输出到 Side Output 的例子

java 复制代码
.timeWindow(Time.milliseconds(10))
.allowedLateness(Time.milliseconds(10))
.sideOutputLateData(myTag)
.process(new ProcessWindowFunction<Tuple2<String, Integer>, String, String, TimeWindow>() {
    @Override
    public void process(String s, Context context, Iterable<Tuple2<String, Integer>> elements, Collector<String> out) {
        for (Tuple2<String, Integer> element : elements) {
            // 正常逻辑可以忽略
            // 手动把当前窗口所有数据输出到 side output
            context.output(myTag, element);
        }
    }
});

DataStream<Tuple2<String, Integer>> side = result.getSideOutput(myTag);
side.print();

重点:context.output(myTag, element);

小结

机制 作用范围 是否触发计算 真正迟到数据处理方式
Watermark 全局 触发窗口关闭 默认丢弃
allowedLateness 单个窗口 延迟触发 仍参与计算
sideOutputLateData 单个窗口 不参与计算 输出到侧输出流,可单独处理

合理组合使用 Watermark + allowedLateness + sideOutputLateData,几乎可以覆盖所有延迟数据的处理需求。

复制代码
相关推荐
车传新1 小时前
Flink
大数据·flink
TeleostNaCl1 小时前
Android TV | 一种不跳出应用指定页面的类 Monkey 的 Android TV 压测脚本
android·经验分享·压力测试
4***W4291 小时前
SpringCloud实战十三:Gateway之 Spring Cloud Gateway 动态路由
java·spring cloud·gateway
k***08291 小时前
mysql中general_log日志详解
android·数据库·mysql
程序员-周李斌1 小时前
ArrayList 源码深度分析(基于 JDK 8)
java·开发语言·数据结构·算法·list
safestar20121 小时前
Spring Boot的魔法与陷阱:从自动配置原理到生产环境避坑实战
java·spring boot·后端
v***55341 小时前
Spring Boot环境配置
java·spring boot·后端
J***51681 小时前
Spring Cloud GateWay搭建
java
IT·小灰灰1 小时前
深度解析重排序AI模型:基于硅基流动API调用多语言重排序AI实战指南
java·大数据·javascript·人工智能·python·数据挖掘·php