• 处理迟到数据


    有了事件时间、水位线和窗口的相关知识,现在就可以系统性地讨论一下怎样处理迟到数据了。所谓的“迟到数据”(late data),是指某个水位线之后到来的数据,它的时间戳其实是在水位线之前的。所以只有在事件时间语义下,讨论迟到数据的处理才是有意义的。事件时间里用来表示时钟进展的就是水位线(watermark)。对于乱序流,水位线本身就可以设置一个延迟时间;而做窗口计算时,又可以设置窗口的允许延迟时间;另外窗口还有将迟到数据输出到测输出流的用法。

    1、设置水位线延迟时间

    水位线是事件时间的进展,它是整个应用的全局逻辑时钟。水位线生成之后,会随着数据在任务间流动,从而给每个任务指明当前的事件时间。所以从这个意义上讲,水位线是一个覆盖万物的存在,它并不只针对事件时间窗口有效。之前讲到触发器时曾提到过“定时器”,时间窗口的操作底层就是靠定时器来控制触发的。既然是底层机制,定时器自然就不可能是窗口的专利了;事实上它是Flink底层API——处理函数(process function)的重要部分。所以水位线其实是所有事件时间定时器触发的判断标准。那么水位线的延迟,当然也就是全局时钟的滞后,相当于是上帝拨动了琴弦,所有人的表都变慢了。既然水位线这么重要,那一般情况就不应该把它的延迟设置得太大,否则流处理的实时性就会大大降低。因为水位线的延迟主要是用来对付分布式网络传输导致的数据乱序,而网络传输的乱序程度一般并不会很大,大多集中在几毫秒至几百毫秒。所以实际应用中,往往会给水位线设置一个“能够处理大多数乱序数据的小延迟”,视需求一般设在毫秒~秒级。当设置了水位线延迟时间后,所有定时器就都会按照延迟后的水位线来触发。如果一个数据所包含的时间戳,小于当前的水位线,那么它就是所谓的“迟到数据”。

    2、允许窗口处理迟到数据

    水位线延迟设置的比较小,那之后如果仍有数据迟到该怎么办?对于窗口计算而言,如果水位线已经到了窗口结束时间,默认窗口就会关闭,那么之后再来的数据就要被丢弃了。自然想到,Flink的窗口也是可以设置延迟时间,允许继续处理迟到数据的。这种情况下,由于大部分乱序数据已经被水位线的延迟等到了,所以往往迟到的数据不会太多。这样,会在水位线到达窗口结束时间时,先快速地输出一个近似正确的计算结果;然后保持窗口继续等到延迟数据,每来一条数据,窗口就会再次计算,并将更新后的结果输出。这样就可以逐步修正计算结果,最终得到准确的统计值了。类比班车的例子,可以这样理解:大多数人是在发车时刻前后到达的,所以只要把表调慢,稍微等一会儿,绝大部分人就都上车了,这个把表调慢的时间就是水位线的延迟;到点之后,班车就准时出发了,不过可能还有该来的人没赶上。于是我们就先慢慢往前开,这段时间内,如果迟到的人抓点紧还是可以追上的;如果有人追上来了,就停车开门让他上来,然后车继续向前开。当然我们的车不能一直慢慢开,需要有一个时间限制,这就是窗口的允许延迟时间。一旦超过了这个时间,班车就不再停留,开上高速疾驰而去了。所以我们将水位线的延迟和窗口的允许延迟数据结合起来,最后的效果就是先快速实时地输出一个近似的结果,而后再不断调整,最终得到正确的计算结果。回想流处理的发展过程,这不就是著名的Lambda架构吗?原先需要两套独立的系统来同时保证实时性和结果的最终正确性,如今Flink一套系统就全部搞定了。

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

    即使有了前面的双重保证,可窗口不能一直等下去,最后总要真正关闭。窗口一旦关闭,后续的数据就都要被丢弃了。那如果真的还有漏网之鱼又该怎么办呢?那就要用到最后一招了:用窗口的侧输出流来收集关窗以后的迟到数据。这种方式是最后“兜底”的方法,只能保证数据不丢失;因为窗口已经真正关闭,所以是无法基于之前窗口的结果直接做更新的。只能将之前的窗口计算结果保存下来,然后获取侧输出流中的迟到数据,判断数据所属的窗口,手动对结果进行合并更新。尽管有些烦琐,实时性也不够强,但能够保证最终结果一定是正确的。如果还用赶班车来类比,那就是车已经上高速开走了,这班车是肯定赶不上了。不过我们还留下了行进路线和联系方式,迟到的人如果想办法辗转到了目的地,还是可以和大部队会合的。最终,所有该到的人都会在目的地出现。所以总结起来,Flink处理迟到数据,对于结果的正确性有三重保障:水位线的延迟,窗口允许迟到数据,以及将迟到数据放入窗口侧输出流。我们可以回忆一下之前6.3.5小节统计每个url浏览次数的代码UrlViewCountExample,稍作改进,增加处理迟到数据的功能。具体代码如下。

    public class LaterDataTest {
        public static void main(String[] args) throws Exception {
            StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
    
            //设置全局并行度
            env.setParallelism(1);
            //设置水位线生成间隔
            env.getConfig().setAutoWatermarkInterval(100);
            SingleOutputStreamOperator<Event> eventStream = env.socketTextStream("hadoop103", 9999).map(
                    new MapFunction<String, Event>() {
                        @Override
                        public Event map(String value) throws Exception {
                            String[] split = value.split(",");
                            return new Event(split[0].trim(), split[1].trim(), Long.valueOf(split[2].trim()));
                        }
                    }
            ).returns(new TypeHint<Event>() {
            }).assignTimestampsAndWatermarks(WatermarkStrategy
                    .<Event>forBoundedOutOfOrderness(Duration.ofSeconds(2))
                    .withTimestampAssigner(new SerializableTimestampAssigner<Event>() {
                        @Override
                        public long extractTimestamp(Event element, long recordTimestamp) {
                            return element.timestamp;
                        }
                    }));
    
            eventStream.print("  input  ");
    
            //定义输出标签
            OutputTag<Event> later = new OutputTag<Event>("later") {
            };
    
            //统计 url 访问量
    
    
            SingleOutputStreamOperator<UrlViewCount> result = eventStream.keyBy(data -> data.url)
                    .window(TumblingEventTimeWindows.of(Time.seconds(10)))
    
                    .allowedLateness(Time.minutes(1)) //1 min 延迟
                    //迟到数据输出到册数出列
                    .sideOutputLateData(later)
                    .aggregate(new UrlCountViewExample.UrlViewCountAgg(), new UrlCountViewExample.UrlViewCountResult());
    
    
            result.print("  result  ");
            //侧输出流
            result.getSideOutput(later).print("later datas");
    
            env.execute();
        }
  • 相关阅读:
    [Cogs727] [网络流24题#2] 太空飞行计划 [网络流,最小割]
    [Cogs14] [网络流24题#1] 飞行员分配方案 [网络流,最大流,二分图匹配]
    [Poj2112][USACO2003 US OPEN] Optimal Milking [网络流,最大流][Dinic+当前弧优化]
    基本高精度模板
    Dinic模板
    [poj1698]Alice's Chance[网络流]
    [cf 599D] Spongebob and Squares
    [cf 599C] Day at the Beach
    [BZOJ1004]Cards
    [BZOJ1007]水平可见直线
  • 原文地址:https://www.cnblogs.com/wdh01/p/16443769.html
Copyright © 2020-2023  润新知