迟到数据的处理

本文深入探讨Apache Flink如何处理迟到数据,介绍了设置水位线延迟时间以处理乱序数据,通过运行窗口处理迟到数据以及使用窗口侧输出流确保数据不丢失。通过示例代码展示了处理迟到数据的过程,详细解释了各个阶段的机制,确保最终结果的准确性。
摘要由CSDN通过智能技术生成

迟到数据:指某个水位线之后到来的数据,它的时间戳其实是在水位线之前的

设置水位线延迟时间

水位线是事件时间的进展,它是我们整个应用的全局逻辑时钟。水位线生成之后,会随着 数据在任务间流动,从而给每个任务指明当前的事件时间

之前我们讲到触发器时曾提到过“定时器”,时间窗口的操作底层就是靠定时器来控制触 发的。既然是底层机制,定时器自然就不可能是窗口的专利了;事实上它是 Flink 底层 API— —处理函数(process function)的重要部分

水位线其实是所有事件时间定时器触发的判断标准。那么水位线的延迟,当然也就是全局时钟的滞后

给水位线设置一个“能够处理大多数乱序数据的小延迟”,所有定时器就都会按照延迟后的水位线来触发。

运行窗口处理迟到数据

在水位线到达窗口结束时间时,先快速地输出一个近似正确的计算结果; 然后保持窗口继续等到延迟数据,每来一条数据,窗口就会再次计算,并将更新后的结果输出。 这样就可以逐步修正计算结果,最终得到准确的统计值了。

将迟到的数据放入窗口侧输出流

即使我们有了前面的双重保证,可窗口不能一直等下去,最后总要真正关闭。窗口一旦关 闭,后续的数据就都要被丢弃了。那如果真的还有漏网之鱼又该怎么办呢?

那就要用到最后一招了:用窗口的侧输出流来收集关窗以后的迟到数据。这种方式是最后 “兜底”的方法,只能保证数据不丢失;因为窗口已经真正关闭,所以是无法基于之前窗口的 结果直接做更新的。我们只能将之前的窗口计算结果保存下来,然后获取侧输出流中的迟到数 据,判断数据所属的窗口,手动对结果进行合并更新。尽管有些烦琐,实时性也不够强,但能 够保证最终结果一定是正确的

Flink 处理迟到数据,对于结果的正确性有三重保障:

  • 水位线的延迟
  • 窗口运行迟到数据
  • 将迟到数据放入窗口侧输出流
import com.yingzi.chapter05.Source.Event;
import org.apache.flink.api.common.eventtime.SerializableTimestampAssigner;
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.api.common.functions.AggregateFunction;
import org.apache.flink.api.common.functions.MapFunction;
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.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 ProcessLateDateExample {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

        // 读取 socket 文本流
        SingleOutputStreamOperator<Event> stream =
                env.socketTextStream("hadoop102", 7777)
                        .map(new MapFunction<String, Event>() {
                            @Override
                            public Event map(String value) throws Exception {
                                String[] fields = value.split(" ");
                                return new Event(fields[0].trim(), fields[1].trim(),
                                        Long.valueOf(fields[2].trim()));
                            }
                        })
                        // 方式一:设置 watermark 延迟时间,2 秒钟
                        .assignTimestampsAndWatermarks(WatermarkStrategy.<Event>forBoundedOutOfOrderness(Duration.ofSeconds(2))
                                .withTimestampAssigner(new SerializableTimestampAssigner<Event>() {
                                    @Override
                                    public long extractTimestamp(Event element, long recordTimestamp) {
                                        return element.timestamp;
                                    }
                                }));
        // 定义侧输出流标签
        OutputTag<Event> outputTag = new OutputTag<Event>("late") {
        };
        SingleOutputStreamOperator<UrlViewCount> result = stream.keyBy(data -> data.url)
                .window(TumblingEventTimeWindows.of(Time.seconds(10)))
                // 方式二:允许窗口处理迟到数据,设置 1 分钟的等待时间
                .allowedLateness(Time.minutes(1))
                // 方式三:将最后的迟到数据输出到侧输出流
                .sideOutputLateData(outputTag)
                .aggregate(new UrlViewCountAgg(), new UrlViewCountResult());
        result.print("result");
        result.getSideOutput(outputTag).print("late");
        // 为方便观察,可以将原始数据也输出
        stream.print("input");
        env.execute();
    }

    public static class UrlViewCountAgg implements AggregateFunction<Event, Long, Long> {
        @Override
        public Long createAccumulator() {
            return 0L;
        }

        @Override
        public Long add(Event value, Long accumulator) {
            return accumulator + 1;
        }

        @Override
        public Long getResult(Long accumulator) {
            return accumulator;
        }

        @Override
        public Long merge(Long a, Long b) {
            return null;
        }
    }

    public static class UrlViewCountResult extends ProcessWindowFunction<Long, UrlViewCount, String, TimeWindow> {
        @Override
        public void process(String url, Context context, Iterable<Long> elements, Collector<UrlViewCount> out) throws Exception {
            // 结合窗口信息,包装输出内容
            Long start = context.window().getStart();
            Long end = context.window().getEnd();
            out.collect(new UrlViewCount(url, elements.iterator().next(), start, end));
        }
    }

}

当输入数据[Alice, ./home, 10000]时,时间戳为 10000,由于设置了 2 秒钟的水位线延迟时间,所以此时水位线到达了 8 秒(事实上是 7999 毫秒,这里不再追究减 1 的细节),并没有触发 [0, 10s) 窗口的计算;;所以接下来时间戳为 9000 的数据到来,同样可以直接进入窗口做增量聚合。当时间戳为 12000 的数据到来时,水位线到达了 12000 – 2 * 1000 = 10000,所以 触发了[0, 10s) 窗口的计算,第一次输出了窗口统计结果,如下所示:

这里的count值为3,包括之前输入的时间戳为 1000、2000、9000 的三条数据。不过窗口触发计算之后并没有关闭销毁,而是继续等待迟到数据
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CihoRfvN-1650124323537)(C:\Users\Admin\AppData\Roaming\Typora\typora-user-images\image-20220416233650974.png)]
。之后时间戳为 15000 的数据继续推进水位线,此时时钟已经进展到了 13000ms;此时再来一条时间戳为 9000 的数 据,我们会发现立即输出了一条统计结果:很明显,这仍然是[0, 10s) 的窗口,在之前计数值 3 的基础上继续叠加,更新统计结果为 4。所以允许窗口处理迟到数据之后,相当于窗口有了一段等待时间,在这期间所有的迟到数 据都会立即触发窗口计算,更新之前的结果。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K9KKLdeS-1650124323539)(C:\Users\Admin\AppData\Roaming\Typora\typora-user-images\image-20220416233842966.png)]
我们设置窗口等待的时间为 1 分钟,所以当时间推进到 10000 + 60 * 1000 = 70000 时,窗 口就会真正被销毁。此前的所有迟到数据可以直接更新窗口的计算结果,而之后的迟到数据已 经无法整合进窗口,就只能用侧输出流来捕获了。需要注意的是,这里的“时间”依然是由水位线来指示的,所以时间戳为 70000 的数据到来,并不会触发窗口的销毁
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3QYP4iiJ-1650124323540)(C:\Users\Admin\AppData\Roaming\Typora\typora-user-images\image-20220416234053311.png)]
当时间戳为 72000 的数据到来,水位线推进到了 72000 – 2 * 1000 = 70000,此时窗口真正销毁关闭,之后再来的 迟到数据就会输出到侧输出流了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n97pj5hT-1650124323542)(C:\Users\Admin\AppData\Roaming\Typora\typora-user-images\image-20220416234148508.png)]

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值