Flink对于迟到数据的处理


简单理解什么是迟到数据

比如一个0-10s的窗口,设置了延迟时间是2s,后来来了一条12s的数据,窗口要触发然后关闭,紧接着又来了一条6s的数据,这个数据应该属于0-10s,但是窗口的延迟时间已经过去并且窗口已经关闭了,此时这条数据就叫做迟到数据。

迟到数据的处理

1.窗口允许迟到

Flink的窗口,也允许迟到数据。当触发了窗口计算后,会先计算当前的结果,但是此时并不会关闭窗口。
以后每来一条迟到数据,就触发一次这条数据所在窗口计算。直到wartermark 超过了窗口结束时间+推迟时间,此时窗口会真正关闭。

案例:

package window;
import flink_Partition.WaterSensorMapFunction;
import flink_transfrom.WaterSensor;
import org.apache.commons.lang.time.DateFormatUtils;
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.windowing.ProcessWindowFunction;
import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows;
import org.apache.flink.streaming.api.windowing.assigners.TumblingProcessingTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
import org.apache.flink.util.Collector;

import java.time.Duration;
public class WatermarkAllowLateness {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

        SingleOutputStreamOperator<WaterSensor> sensorsDS = env
                .socketTextStream("hadoop102", 7777)
                .map(new WaterSensorMapFunction());

        //TODO 定义Watermark策略
        WatermarkStrategy<WaterSensor> watermarkStrategy = WatermarkStrategy
                .<WaterSensor>forBoundedOutOfOrderness(Duration.ofSeconds(3)) //指定watermark生成:乱序的,等待3s
                //指定 时间戳分配器,从数据中提取
                .withTimestampAssigner(new SerializableTimestampAssigner<WaterSensor>() {
                    @Override
                    public long extractTimestamp(WaterSensor element, long recordTimestamp) {
                        return element.getTs() * 1000L;
                    }
                });


        SingleOutputStreamOperator<WaterSensor> sensorsDSWithWatermark = sensorsDS.assignTimestampsAndWatermarks(watermarkStrategy);

        SingleOutputStreamOperator<String> watermark = sensorsDSWithWatermark.keyBy(r -> r.getId())
                // 使用 事件时间语义 的窗口
                .window(TumblingEventTimeWindows.of(Time.seconds(10)))
                .allowedLateness(Time.seconds(2))//推迟两秒关窗
                .process(
                        new ProcessWindowFunction<WaterSensor, String, String, TimeWindow>() {
                            @Override
                            public void process(String s, Context context, Iterable<WaterSensor> elements, Collector<String> out) throws Exception {
                                long startTs = context.window().getStart();
                                long endTs = context.window().getEnd();
                                String windowStart = DateFormatUtils.format(startTs, "yyyy-MM-dd HH:mm:ss.SSS");
                                String windowEnd = DateFormatUtils.format(endTs, "yyyy-MM-dd HH:mm:ss.SSS");

                                long count = elements.spliterator().estimateSize();

                                out.collect("key=" + s + "的窗口[" + windowStart + "," + windowEnd + ")包含" + count + "条数据===>" + elements.toString());
                            }
                        }
                );

        watermark.print();

        env.execute();
    }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
设置了迟到时间后,窗口是这样的:
在这里插入图片描述

15s这条数据来了后窗口就关闭了,所以后续的5s的数据也不会进入0-10s这个窗口(桶),所以也不会输出了。

2.使用侧流接收迟到的数据

关窗后的迟到数据可以放到侧输出流中。

package window;
import flink_Partition.WaterSensorMapFunction;
import flink_transfrom.WaterSensor;
import org.apache.commons.lang.time.DateFormatUtils;
import org.apache.flink.api.common.eventtime.SerializableTimestampAssigner;
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.api.common.typeinfo.Types;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.windowing.ProcessWindowFunction;
import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows;
import org.apache.flink.streaming.api.windowing.assigners.TumblingProcessingTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
import org.apache.flink.util.Collector;
import org.apache.flink.util.OutputTag;

import java.time.Duration;
public class WatermarkAllowLateness {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

        SingleOutputStreamOperator<WaterSensor> sensorsDS = env
                .socketTextStream("hadoop102", 7777)
                .map(new WaterSensorMapFunction());

        //TODO 定义Watermark策略
        WatermarkStrategy<WaterSensor> watermarkStrategy = WatermarkStrategy
                .<WaterSensor>forBoundedOutOfOrderness(Duration.ofSeconds(3)) //指定watermark生成:乱序的,等待3s
                //指定 时间戳分配器,从数据中提取
                .withTimestampAssigner(new SerializableTimestampAssigner<WaterSensor>() {
                    @Override
                    public long extractTimestamp(WaterSensor element, long recordTimestamp) {
                        return element.getTs() * 1000L;
                    }
                });


        SingleOutputStreamOperator<WaterSensor> sensorsDSWithWatermark = sensorsDS.assignTimestampsAndWatermarks(watermarkStrategy);

        OutputTag<WaterSensor> lateTag = new OutputTag<>("late-data", Types.POJO(WaterSensor.class));
        SingleOutputStreamOperator<String> watermark = sensorsDSWithWatermark.keyBy(r -> r.getId())
                // 使用 事件时间语义 的窗口
                .window(TumblingEventTimeWindows.of(Time.seconds(10)))
                .allowedLateness(Time.seconds(2))//推迟两秒关窗
                .sideOutputLateData(lateTag)//关窗后的迟到数据,放入侧输出流
                .process(
                        new ProcessWindowFunction<WaterSensor, String, String, TimeWindow>() {
                            @Override
                            public void process(String s, Context context, Iterable<WaterSensor> elements, Collector<String> out) throws Exception {
                                long startTs = context.window().getStart();
                                long endTs = context.window().getEnd();
                                String windowStart = DateFormatUtils.format(startTs, "yyyy-MM-dd HH:mm:ss.SSS");
                                String windowEnd = DateFormatUtils.format(endTs, "yyyy-MM-dd HH:mm:ss.SSS");

                                long count = elements.spliterator().estimateSize();

                                out.collect("key=" + s + "的窗口[" + windowStart + "," + windowEnd + ")包含" + count + "条数据===>" + elements.toString());
                            }
                        }
                );

        watermark.getSideOutput(lateTag).printToErr("关窗后的迟到数据"); //从主流获取侧输出流打印
        watermark.print();//打印主流

        env.execute();
    }
}

在这里插入图片描述

在这里插入图片描述

<think>在Flink 1.16中,处理迟到数据主要依靠**事件时间语义**和**水印(Watermark)**机制,并结合窗口的**允许延迟(allowed lateness)**和**侧输出(side output)**功能。以下是具体实现方法: --- ### 1. **定义水印策略** 水印用于表示事件时间的进展,并容忍一定程度的乱序。在Flink SQL中,可以在创建表时定义水印策略: ```sql CREATE TABLE orders ( order_id STRING, amount DECIMAL(10, 2), order_time TIMESTAMP(3), WATERMARK FOR order_time AS order_time - INTERVAL '5' SECOND -- 允许5秒的乱序 ) WITH (...); ``` - `WATERMARK FOR order_time AS order_time - INTERVAL '5' SECOND` 表示系统允许事件时间比水印时间晚5秒。 --- ### 2. **使用窗口函数并设置允许延迟** 在窗口聚合(如`TUMBLE`、`HOP`)中,通过`GROUP BY`子句指定窗口,并设置`ALLOWED_LATENESS`来定义窗口关闭前的最大延迟时间: ```sql INSERT INTO result_table SELECT TUMBLE_END(order_time, INTERVAL '1' HOUR) AS window_end, SUM(amount) AS total_revenue FROM orders GROUP BY TUMBLE(order_time, INTERVAL '1' HOUR) -- 滚动窗口1小时 ALLOWED_LATENESS (INTERVAL '10' MINUTE); -- 允许迟到10分钟 ``` - **允许延迟机制**:窗口不会在水印到达窗口结束时间后立即关闭,而是会继续等待`10`分钟。在此期间到达的迟到数据,会触发窗口的**增量更新**(即重新计算并输出新结果)。 --- ### 3. **使用侧输出捕获迟到数据** 对于超过允许延迟的极端迟到数据,可通过`EMIT`语法(Flink 1.16新增)输出到独立的侧输出流: ```sql INSERT INTO result_table SELECT TUMBLE_END(order_time, INTERVAL '1' HOUR) AS window_end, SUM(amount) AS total_revenue FROM orders GROUP BY TUMBLE(order_time, INTERVAL '1' HOUR) WITH ( 'allow-lateness' = '10min', -- 允许迟到10分钟 'emit-mode' = 'allow-lateness-and-retract' -- 允许延迟并支持撤回 ); ``` - **侧输出流**:超过允许延迟的数据会被路由到侧输出表(需提前定义),避免污染主结果流。 --- ### 完整示例 #### Step 1: 创建源表(含水印) ```sql CREATE TABLE orders ( order_id STRING, amount DECIMAL(10, 2), order_time TIMESTAMP(3), WATERMARK FOR order_time AS order_time - INTERVAL '5' SECOND ) WITH ( 'connector' = 'kafka', 'topic' = 'orders', 'properties.bootstrap.servers' = 'localhost:9092', 'format' = 'json' ); ``` #### Step 2: 创建结果表 ```sql CREATE TABLE revenue_report ( window_end TIMESTAMP(3), total_revenue DECIMAL(10, 2) ) WITH ( 'connector' = 'jdbc', 'url' = 'jdbc:mysql://localhost:3306/flink', 'table-name' = 'revenue' ); ``` #### Step 3: 执行聚合(允许延迟10分钟) ```sql INSERT INTO revenue_report SELECT TUMBLE_END(order_time, INTERVAL '1' HOUR) AS window_end, SUM(amount) AS total_revenue FROM orders GROUP BY TUMBLE(order_time, INTERVAL '1' HOUR) ALLOWED_LATENESS (INTERVAL '10' MINUTE); ``` --- ### 关键点说明 1. **水印**:定义了事件时间的进度和乱序容忍度(如5秒)。 2. **允许延迟(`ALLOWED_LATENESS`)**:窗口会额外等待一段时间(如10分钟),期间迟到数据触发结果更新。 3. **侧输出**:超过允许延迟的数据单独处理,避免影响主流程[^1][^3]。 --- ### 相关问题 1. Flink SQL中水印延迟设置对窗口计算有何影响? 2. 如何配置Flink SQL作业以处理超过允许延迟的极端迟到数据? 3. 在Flink 1.16中,`EMIT`语法的具体使用场景有哪些? 4. 如何验证Flink SQL窗口聚合结果的准确性?</think>在 Flink 1.16 SQL 中处理迟到数据主要通过 **水印(Watermark)** 机制和 **允许延迟(Allowed Lateness)** 功能实现,以下是完整实现方案: ### 1. 定义水印策略 在创建表时使用 `WATERMARK` 子句定义水印策略,允许数据迟到达: ```sql CREATE TABLE orders ( order_id STRING, amount DECIMAL(10, 2), order_time TIMESTAMP(3), WATERMARK FOR order_time AS order_time - INTERVAL '2' MINUTE -- 允许2分钟延迟 ) WITH ( 'connector' = 'kafka', 'topic' = 'orders', 'properties.bootstrap.servers' = 'localhost:9092', 'format' = 'json' ); ``` - `WATERMARK FOR order_time AS order_time - INTERVAL '2' MINUTE` 表示系统允许事件时间比水印时间晚2分钟到达[^2] ### 2. 使用允许延迟功能 在窗口聚合时添加 `ALLOWED_LATENESS` 子句处理迟到数据: ```sql INSERT INTO revenue_report SELECT HOP_END(order_time, INTERVAL '5' MINUTE, INTERVAL '10' MINUTE) AS window_end, SUM(amount) AS total_revenue FROM orders GROUP BY HOP(order_time, INTERVAL '5' MINUTE, INTERVAL '10' MINUTE) ALLOWED_LATENESS (INTERVAL '5' MINUTE); -- 额外允许5分钟延迟 ``` - **窗口行为**: - 主窗口:每10分钟计算一次(窗口大小) - 滑动间隔:每5分钟触发一次计算 - 允许延迟:窗口关闭后额外等待5分钟处理迟到数据 ### 3. 完整示例(每5分钟计算,每10分钟输出) ```sql -- 创建结果表 CREATE TABLE revenue_report ( window_end TIMESTAMP(3), total_revenue DECIMAL(10, 2) ) WITH ( 'connector' = 'jdbc', 'url' = 'jdbc:mysql://localhost:3306/flink', 'table-name' = 'revenue' ); -- 处理迟到数据的聚合查询 INSERT INTO revenue_report SELECT HOP_END(order_time, INTERVAL '5' MINUTE, INTERVAL '10' MINUTE) AS window_end, SUM(amount) AS total_revenue FROM orders GROUP BY HOP(order_time, INTERVAL '5' MINUTE, INTERVAL '10' MINUTE) ALLOWED_LATENESS (INTERVAL '5' MINUTE); ``` ### 迟到数据处理机制 1. **首次计算结果**:在窗口结束时间(如10:10)立即输出 2. **延迟数据到达**:窗口关闭后5分钟内到达的数据(允许延迟期内) 3. **更新结果**:Flink 会输出一条**撤回消息**(retract)和**更新后的结果** 4. **最终关闭**:水印超过 `窗口结束时间 + 允许延迟` 后窗口永久关闭 ### 验证结果准确性 可通过查询系统表验证迟到数据处理: ```sql SELECT * FROM TABLE( DESCRIBE_STATEMENT( 'INSERT INTO revenue_report...' -- 替换为实际INSERT语句 ) ); ``` ### 注意事项 1. **水印生成**:确保使用事件时间(`TIMESTAMP(3)` 类型) 2. **状态保留**:允许延迟会增加状态存储时间 3. **结果更新**:下游系统需支持撤回/更新(如JDBC、Upsert Kafka) 4. **极端延迟**:超过允许延迟的数据会被丢弃(可通过侧输出捕获)[^1][^3] > **提示**:在 Flink 1.16 中,Python UDF 支持得到增强,可通过 JNI 在 JVM 中执行自定义函数[^4],适用于复杂迟到数据处理逻辑。 --- ### 相关问题 1. Flink SQL 中如何配置水印延迟时间以优化迟到数据处理? 2. 在 Flink 1.16 中,如何实现窗口聚合结果的撤回和更新? 3. 如何处理超过允许延迟时间的极端迟到数据? 4. Flink SQL 的 `HOP` 窗口与 `CUMULATE` 窗口在处理迟到数据时有何区别? 5. 如何监控 Flink SQL 作业中迟到数据处理情况?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

IT阿牛

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

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

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

打赏作者

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

抵扣说明:

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

余额充值