Flink Time 之 AllowedLateness 深度剖析:从本质定义到内部机制与实战最佳实践

前面几篇讲了 Watermark 的原理、乱序问题处理、周期性和断点式 Watermark 生成方式,以及企业级 Watermark 案例分析。Watermark 解决了"什么时候触发窗口"的问题,但有一个场景 Watermark 本身解决不了:窗口触发后,迟到的数据怎么办?

默认情况下,窗口触发后状态立即清除,之后到达的迟到数据直接被丢弃。但生产环境中,迟到数据是常态------网络延迟、上游重试、跨地域传输都会导致数据迟到。如果直接丢弃,统计结果就会偏小,业务方不接受。

Flink 提供了 allowedLateness() 机制来解决这个问题:窗口触发后,状态不立即清除,而是再保留一段时间(allowedLateness 时长),在这段时间内到达的迟到数据会重新触发窗口计算,更新结果。这篇从本质定义、时间线可视化、三阶段对比、内部执行流程、状态生命周期、与 Watermark 的关系、完整代码实现、六个常见坑、八条最佳实践,把 AllowedLateness 一次性讲透。


一、AllowedLateness 本质定义

AllowedLateness 的本质一句话概括:窗口触发后,状态再保留多久,允许迟到数据重新触发窗口。

下面这张图把 AllowedLateness 的本质定义、时间线可视化、三阶段对比放在一起展示。

1.1 默认行为 vs 设置 AllowedLateness 后的行为

默认情况下(allowedLateness=0),当 Watermark 超过窗口结束时间时:

  1. 窗口触发计算,输出结果
  2. 立即清除窗口状态
  3. 之后到达的迟到数据直接丢弃

设置 allowedLateness(Time) 后:

  1. 窗口触发计算,输出结果
  2. 不清除状态,继续保留
  3. 在 allowedLateness 时长内到达的迟到数据,重新触发窗口计算,输出更新后的结果
  4. 当 Watermark 超过 窗口结束时间 + allowedLateness 时,状态才真正清除
  5. 之后到达的迟到数据,设置了 sideOutputLateData 则侧输出,否则丢弃

1.2 核心公式和三个关键时间点

核心公式: 状态清理时间 = 窗口结束时间 + allowedLateness

三个关键时间点:

  • 窗口结束时间:Watermark 超过这个时间点,窗口首次触发计算
  • 窗口结束时间 + allowedLateness:Watermark 超过这个时间点,状态真正清除
  • 之后:迟到数据侧输出(sideOutputLateData)或直接丢弃

1.3 三大核心特点

第一,延迟容忍。 允许数据在窗口触发后延迟到达,重新触发计算,更新结果。这是 AllowedLateness 最核心的价值------处理迟到数据,保证结果准确性。

第二,状态保留。 窗口触发后状态不立即清除,保留 allowedLateness 时长。这是代价------状态保留期间持续占用内存,Lateness 越大内存占用越久。

第三,重新触发。 迟到数据到达时重新触发窗口函数,输出更新后的结果。注意是"重新输出",不是"更新之前的输出"------下游会收到多条结果,需要幂等处理或去重。


二、AllowedLateness 时间线可视化

以 5 分钟滚动窗口 + allowedLateness(10分钟) 为例,看完整的时间线:

makefile 复制代码
10:00:00  窗口开始,数据正常进入窗口,累积状态
10:03:00  正常数据到达,追加到窗口状态
10:05:00  窗口结束,WM超过结束时间,首次触发计算,输出结果,状态保留(不清除)
10:08:00  迟到数据到达(事件时间10:04),追加到状态,重新触发计算,输出更新后的结果
10:12:00  又一条迟到数据到达,再次重新触发,再次输出更新结果
10:15:00  WM超过 10:05 + 10分钟 = 10:15,状态真正清除
10:20:00  超期数据到达(事件时间10:04),状态已清除,侧输出到late流或丢弃

关键观察:

  • 10:05 首次触发后,状态没有清除,而是继续保留
  • 10:08 和 10:12 的迟到数据都重新触发了窗口,输出了更新后的结果(下游收到 3 条结果)
  • 10:15 是状态清除时间点(10:05 + 10分钟),之后到达的数据不再重新触发
  • 10:20 的超期数据被侧输出或丢弃

三、三阶段数据处理方式对比

AllowedLateness 将数据处理分为三个阶段,每个阶段的处理方式完全不同:

阶段 时间范围 处理方式 状态变化 输出
正常数据 WM < 窗口结束时间 数据正常进入窗口,累积状态 窗口状态持续累积 无(等待触发)
迟到数据 窗口结束 < WM < 结束+Lateness 迟到数据追加到状态,重新触发计算 窗口状态更新(追加迟到数据) 重新输出更新结果(可能重复)
超期数据 WM > 结束+Lateness 状态已清除,侧输出或丢弃 无(状态已清除) 侧输出流(需设置sideOutputLateData),否则丢弃

重点理解第二阶段(迟到数据):

  • 迟到数据到达时,窗口状态还在(因为设置了 allowedLateness)
  • 迟到数据追加到窗口状态中
  • 重新调用窗口函数计算,输出更新后的结果
  • 每条迟到数据都可能触发一次重新计算,迟到数据多时性能开销大
  • 下游会收到多条结果(首次 + 每次迟到更新),需要幂等处理

四、AllowedLateness 内部机制

理解了本质和时间线,下面深入内部机制,看 AllowedLateness 到底是怎么工作的。

下面这张图把六步内部执行流程、状态生命周期管理、与 Watermark 的关系放在一起展示。

4.1 六步内部执行流程

从数据到达到状态清除,AllowedLateness 的完整执行流程分为六步:

第一步:数据到达。 数据到达窗口算子,提取事件时间戳,判断属于哪个窗口。如果是新窗口(第一条数据),创建窗口状态。

第二步:判断窗口状态。 检查窗口是否已触发、状态是否已清除。这一步决定了数据的处理方式:

  • 窗口未触发 → 正常数据,走第三步的"正常处理"
  • 窗口已触发但状态未清除(在 allowedLateness 内)→ 迟到数据,走第三步的"迟到处理"
  • 窗口已触发且状态已清除(超过 allowedLateness)→ 超期数据,侧输出或丢弃

第三步:正常/迟到处理。 正常数据追加到窗口状态中累积;迟到数据也追加到窗口状态中(状态还在),但会触发第四步。

第四步:重新触发计算。 迟到数据到达时,重新调用窗口函数计算,输出更新后的结果。注意是"重新输出",不是"修改之前的输出"。

第五步:WM 推进检查。 每次 Watermark 推进时,检查是否有窗口超过了"结束时间 + allowedLateness"。如果有,准备清除这些窗口的状态。

第六步:状态清除。 Watermark 超过窗口结束时间 + allowedLateness 时,清除窗口状态、窗口元数据、定时器。之后到达的迟到数据不再重新触发,而是侧输出或丢弃。

4.2 状态生命周期管理

AllowedLateness 下,窗口状态的完整生命周期是:创建 → 累积 → 首次触发 → 保留 → 重新触发 → 清除。

状态创建与累积: 第一条属于该窗口的数据到达时创建窗口状态。正常数据到达时追加到状态中(ListState/ReducingState/AggregatingState)。状态内容是窗口内的所有数据(或聚合后的中间状态)。

首次触发与状态保留: Watermark 超过窗口结束时间时,首次触发窗口计算。默认行为是触发后立即清除状态,但设置了 allowedLateness 后,不清除状态,继续保留,等待可能的迟到数据。保留时长从窗口结束时间开始算,保留 allowedLateness 时长。

迟到数据重新触发: Watermark 在(窗口结束, 结束+Lateness)区间内,迟到数据到达时追加到窗口状态中,重新调用窗口函数计算,输出更新后的结果。每条迟到数据都可能触发一次重新计算,迟到数据多时性能开销大。ProcessWindowFunction 可以用 context.currentWatermark() > window.getEnd() 判断是否是迟到触发。

状态清除与超期处理: Watermark 超过窗口结束时间 + allowedLateness 时,窗口状态、窗口元数据、定时器全部清除。之后到达的超期数据,设置了 sideOutputLateData 则侧输出到单独流,否则直接丢弃。状态清除后,Checkpoint 中也不再包含该窗口状态,重启后不会恢复。

4.3 与 Watermark 的关系

AllowedLateness 的所有行为都由 Watermark 的推进 驱动,没有 WM 推进就没有窗口触发、没有状态清除。三个关键时间点都与 WM 相关:

  • 首次触发时间点: Watermark > 窗口结束时间
  • 状态清理时间点: Watermark > 窗口结束时间 + allowedLateness
  • 迟到数据区间: 窗口结束时间 < Watermark < 窗口结束时间 + allowedLateness

第一,WM 驱动窗口首次触发。 当 WM 超过窗口结束时间时,窗口首次触发计算。这是窗口的第一次输出,此时状态不清除(如果设置了 allowedLateness)。

第二,WM 决定数据是否被视为"迟到"。 WM 本身不触发重新计算,是迟到数据触发的。但 WM 决定了数据是否被视为"迟到"------如果数据的事件时间 < 当前 WM,且 WM > 窗口结束时间,这条数据就是迟到数据。

第三,WM 驱动状态清除。 当 WM 超过窗口结束时间 + allowedLateness 时,窗口状态被清除。这是最关键的时间点,之后到达的迟到数据不会再重新触发。

注意: 如果 WM 长时间不推进(如空闲分区、断点式标记事件丢失、Source 消费异常),窗口状态会一直保留,内存占用持续增大。这也是为什么必须设置 withIdleness 的原因之一。


五、AllowedLateness 完整代码实现

下面是一个完整的 AllowedLateness 实现,包含 allowedLateness + sideOutputLateData + 迟到数据侧输出补算 + ProcessWindowFunction 标记迟到触发。

下面这张图把完整代码实现、六个常见坑、八条最佳实践放在一起展示。

java 复制代码
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.datastream.DataStream;
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 AllowedLatenessExample {

    // 订单事件
    public static class OrderEvent {
        public String userId;
        public long orderId;
        public double amount;
        public long eventTime;

        public OrderEvent() {}

        public String getUserId() { return userId; }
        public long getEventTime() { return eventTime; }
    }

    // 统计结果
    public static class OrderStat {
        public String userId;
        public long count;
        public double totalAmount;
        public boolean isLateTrigger;  // 标记是否迟到触发

        public OrderStat() {}
        public OrderStat(String userId, long count, double totalAmount, boolean isLateTrigger) {
            this.userId = userId;
            this.count = count;
            this.totalAmount = totalAmount;
            this.isLateTrigger = isLateTrigger;
        }
    }

    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = 
            StreamExecutionEnvironment.getExecutionEnvironment();

        // 1. 定义迟到数据侧输出标签
        OutputTag<OrderEvent> lateOrderTag = new OutputTag<OrderEvent>("late-order") {};

        DataStream<OrderEvent> source = env.addSource(new OrderSource())
            .assignTimestampsAndWatermarks(
                WatermarkStrategy.<OrderEvent>forBoundedOutOfOrderness(Duration.ofSeconds(30))
                    .withIdleness(Duration.ofMinutes(1))
                    .withTimestampAssigner((event, ts) -> event.eventTime)
            );

        // 2. 窗口聚合,设置 allowedLateness 和 sideOutputLateData
        SingleOutputStreamOperator<OrderStat> result = source
            .keyBy(OrderEvent::getUserId)
            .window(TumblingEventTimeWindows.of(Time.minutes(5)))
            .allowedLateness(Time.minutes(10))  // 允许10分钟迟到
            .sideOutputLateData(lateOrderTag)    // 超期迟到数据侧输出
            .process(new OrderProcessWindowFunction());

        // 3. 获取迟到数据侧输出流,单独补算
        DataStream<OrderEvent> lateStream = result.getSideOutput(lateOrderTag);
        lateStream.addSink(new LateOrderSink());  // 写入补数表,T+1批量补算

        // 4. 主流结果输出(包含首次触发和迟到触发的结果)
        result.addSink(new OrderStatSink());

        env.execute("AllowedLateness Example");
    }

    // ProcessWindowFunction:区分首次触发和迟到触发
    public static class OrderProcessWindowFunction 
        extends ProcessWindowFunction<OrderEvent, OrderStat, String, TimeWindow> {

        @Override
        public void process(String key, Context ctx, Iterable<OrderEvent> elements, 
                            Collector<OrderStat> out) {
            // 判断是否迟到触发:WM超过窗口结束时间 = 迟到触发
            long currentWatermark = ctx.currentWatermark();
            long windowEnd = ctx.window().getEnd();
            boolean isLateTrigger = currentWatermark > windowEnd;

            // 统计
            long count = 0;
            double totalAmount = 0.0;
            for (OrderEvent event : elements) {
                count++;
                totalAmount += event.amount;
            }

            // 输出结果,标记是否迟到触发
            out.collect(new OrderStat(key, count, totalAmount, isLateTrigger));
        }
    }
}

代码关键点:

  1. OutputTag 定义 :new OutputTag<OrderEvent>("late-order") {},注意必须用匿名类(带 {}),因为 OutputTag 是泛型类,需要类型信息。

  2. allowedLateness 设置 :.allowedLateness(Time.minutes(10)),允许 10 分钟迟到。窗口触发后状态保留 10 分钟,期间迟到数据重新触发。

  3. sideOutputLateData 设置 :.sideOutputLateData(lateOrderTag),超过 allowedLateness 的迟到数据侧输出到 late-order 标签流,不直接丢弃。

  4. 获取侧输出流 :result.getSideOutput(lateOrderTag),获取迟到数据流,单独写入补数表,T+1 批量补算。

  5. ProcessWindowFunction 标记迟到触发 :用 ctx.currentWatermark() > ctx.window().getEnd() 判断是否是迟到触发。WM 超过窗口结束时间 = 迟到触发,WM 未超过 = 首次触发。下游可以根据 isLateTrigger 字段区分首次结果和更新结果,做幂等处理。


六、sideOutputLateData 配合使用

sideOutputLateData 是 AllowedLateness 的最佳搭档,两者配合使用形成"三层迟到数据处理方案":

第一层:有界乱序(forBoundedOutOfOrderness)。 处理大部分正常范围内的乱序数据,数据在乱序容忍时间内到达,正常进入窗口触发计算。这是主力,处理 95% 以上的数据。

第二层:allowedLateness。 处理少量迟到数据,窗口触发后状态保留一段时间,迟到数据重新触发窗口计算,更新结果。这是兜底,处理 4~5% 的迟到数据。

第三层:sideOutputLateData。 处理超过 allowedLateness 的超期数据,侧输出到单独流,写入补数表,T+1 批量补算。这是最后防线,处理 1% 以内的超期数据,保证不丢数据。

java 复制代码
// 三层迟到数据处理方案完整配置
WatermarkStrategy<OrderEvent> strategy = WatermarkStrategy
    .<OrderEvent>forBoundedOutOfOrderness(Duration.ofSeconds(30))  // 第一层:30秒乱序容忍
    .withIdleness(Duration.ofMinutes(1));

SingleOutputStreamOperator<OrderStat> result = source
    .assignTimestampsAndWatermarks(strategy)
    .keyBy(OrderEvent::getUserId)
    .window(TumblingEventTimeWindows.of(Time.minutes(5)))
    .allowedLateness(Time.minutes(10))  // 第二层:10分钟迟到重新触发
    .sideOutputLateData(lateOrderTag)    // 第三层:超期数据侧输出补算
    .process(new OrderProcessWindowFunction());

// 第三层:超期数据补算
result.getSideOutput(lateOrderTag).addSink(new LateOrderSink());

生产环境建议三层都配置,这样既能保证低延迟(有界乱序正常触发),又能保证结果准确(allowedLateness 重新触发更新),还能保证不丢数据(sideOutputLateData 补算)。


七、六个常见坑

7.1 坑一:allowedLateness 设置过大导致内存溢出

现象: 设置 allowedLateness(1小时),大促期间 TaskManager OOM,作业失败。

根因: 窗口触发后状态保留 1 小时,同时有大量窗口状态保留,内存占用持续增长。高吞吐 + 大窗口 + 大 Lateness = 内存爆炸。假设 5 分钟窗口、10 万 QPS、每条数据 1KB,1 小时 Lateness 意味着同时保留 12 个窗口的状态,每个窗口 3000 万条数据,状态大小约 30GB(未压缩),OOM 是必然的。

解决方案: allowedLateness 不要设置过大,建议 1~10 分钟。需要更长时间补数据用 sideOutputLateData + 离线补算,不要靠 allowedLateness。allowedLateness 是兜底机制,处理少量迟到数据,不是主力。

7.2 坑二:迟到数据重复输出未处理

现象: 下游统计结果偏大,同一窗口数据被统计多次。

根因: allowedLateness 期间每条迟到数据都重新触发窗口,输出更新后的结果。下游如果直接累加,会导致重复统计。例如首次触发输出 count=100,迟到数据到达后重新触发输出 count=105,下游如果两条都累加,结果变成 205,而正确结果是 105。

解决方案: 下游需要幂等处理(按窗口 ID 覆盖更新)或去重(只取最新结果)。在 ProcessWindowFunction 中用 isLateTrigger 标记是否迟到触发,下游据此处理。写入支持幂等的存储(如 Redis/HBase 按 key 覆盖),不要写入只支持追加的存储(如普通文件、Kafka 直接消费累加)。

7.3 坑三:未设置 sideOutputLateData 导致超期数据丢失

现象: 监控发现部分数据丢失,但没有任何日志或告警。

根因: 超过 allowedLateness 的迟到数据,如果没有设置 sideOutputLateData,Flink 直接丢弃,没有任何记录。生产环境数据丢失不可接受,尤其是金融、计费等场景。

解决方案: 生产环境必须设置 sideOutputLateData,将超期迟到数据写入补数表,T+1 批量补算。监控侧输出数据量,异常增加时告警(可能是上游补数据或乱序容忍设置过小)。

7.4 坑四:GlobalWindow 不支持 allowedLateness

现象: 使用 GlobalWindow + allowedLateness,编译报错或运行时异常。

根因: GlobalWindow 没有结束时间(全局窗口,永远不结束,除非 Trigger 触发),allowedLateness 基于窗口结束时间计算状态清理时间,GlobalWindow 无法计算。Flink 对 GlobalWindow 的 allowedLateness 支持有限。

解决方案: GlobalWindow 不要使用 allowedLateness。如果需要迟到处理,改用自定义 Trigger + 自定义清理逻辑,或改用有界窗口(滚动/滑动/会话)。GlobalWindow 适合全局聚合(如累计 GMV),不需要事件时间窗口,用 ProcessingTime 更合适。

7.5 坑五:会话窗口 allowedLateness 行为异常

现象: 会话窗口设置 allowedLateness 后,窗口合并行为异常,迟到数据导致窗口反复合并。

根因: 会话窗口的窗口边界是动态的(由 gap 决定),迟到数据可能导致窗口合并(两个会话窗口之间的 gap 被迟到数据填充,合并为一个窗口)。而 allowedLateness 期间状态保留,合并逻辑更复杂,可能导致窗口反复合并、重复触发。

解决方案: 会话窗口谨慎使用 allowedLateness。如果需要,建议 gap 不要设置过大,allowedLateness 不要超过 gap。测试验证迟到数据场景下的窗口合并行为,确认符合预期。会话窗口本身比滚动/滑动窗口复杂,加上 allowedLateness 更复杂,能不用就不用。

7.6 坑六:WM 不推进导致状态永远不清除

现象: 作业运行一段时间后内存持续增长,Checkpoint 越来越大,作业越来越慢。

根因: WM 长时间不推进(空闲分区未设置 withIdleness、断点式标记事件丢失、Source 消费异常),窗口状态永远不清除(因为状态清除由 WM 推进驱动),内存和 Checkpoint 持续增长。这是生产环境最隐蔽的坑------作业看起来正常,但内存慢慢涨,最后 OOM。

解决方案: 必须设置 withIdleness。监控 WM Lag,超过阈值告警。断点式 WM 必须配合周期性兜底。定期检查各子任务 WM 是否一致,发现异常及时排查。


八、八条最佳实践 Checklist

上线前逐条检查:

  1. allowedLateness 不要过大: 建议 1~10 分钟,超过 30 分钟需要评估内存影响。需要更长补数据用 sideOutputLateData + 离线补算,不要靠 allowedLateness。

  2. 必须设置 sideOutputLateData: 生产环境必须设置,超期迟到数据写入补数表,T+1 批量补算。监控侧输出数据量,异常增加时告警。

  3. 下游幂等处理重复输出: allowedLateness 期间迟到数据重新触发会重复输出,下游必须幂等处理(按窗口 ID 覆盖)或去重(只取最新)。写入支持幂等的存储。

  4. 配合 withIdleness 使用: 必须设置 withIdleness,避免空闲分区导致 WM 不推进、状态永远不清除、内存持续增长。

  5. ProcessWindowFunction 标记迟到触发: 用 context.currentWatermark() > window.getEnd() 判断是否迟到触发,下游据此区分首次结果和更新结果,做不同处理。

  6. 监控状态大小和迟到数据量: 监控窗口状态大小(Checkpoint 大小)、迟到数据量、重新触发频率,异常增加时告警排查。

  7. GlobalWindow 和会话窗口谨慎使用: GlobalWindow 不支持 allowedLateness,会话窗口迟到合并行为复杂,需要充分测试验证。能用滚动/滑动窗口就不用这两个。

  8. 优先用有界乱序处理大部分乱序: allowedLateness 是兜底机制,处理少量迟到数据。大部分乱序应该用 forBoundedOutOfOrderness 处理,不要靠 allowedLateness 处理大量乱序。三层方案:有界乱序(主力)→ allowedLateness(兜底)→ sideOutputLateData(最后防线)。


九、总结与下一篇预告

AllowedLateness 深度剖析要点回顾:

第一,本质:窗口触发后状态再保留 allowedLateness 时长,允许迟到数据重新触发窗口计算,更新结果。核心公式:状态清理时间 = 窗口结束时间 + allowedLateness。三大特点:延迟容忍、状态保留、重新触发。

第二,时间线:以 5 分钟窗口 + 10 分钟 Lateness 为例,10:05 首次触发(状态保留)→ 10:08/10:12 迟到数据重新触发(重复输出)→ 10:15 状态清除(10:05+10分钟)→ 10:20 超期数据侧输出或丢弃。

第三,三阶段对比:正常数据(WM < 窗口结束,累积状态,无输出)→ 迟到数据(结束 < WM < 结束+Lateness,重新触发,重复输出)→ 超期数据(WM > 结束+Lateness,侧输出或丢弃)。

第四,内部六步流程:数据到达 → 判断窗口状态 → 正常/迟到处理 → 重新触发计算 → WM 推进检查 → 状态清除。

第五,状态生命周期:创建 → 累积 → 首次触发 → 保留 → 重新触发 → 清除。设置 allowedLateness 后,首次触发不清除状态,保留到 WM 超过结束+Lateness 才清除。

第六,与 WM 的关系:所有行为由 WM 推进驱动。首次触发(WM > 结束)、迟到区间(结束 < WM < 结束+Lateness)、状态清除(WM > 结束+Lateness)。WM 不推进则状态永远不清除。

第七,完整代码实现:OutputTag 定义 + allowedLateness + sideOutputLateData + getSideOutput 补算 + ProcessWindowFunction 标记迟到触发(currentWatermark() > window.getEnd())。

第八,三层迟到处理方案:有界乱序(主力,处理 95%)→ allowedLateness(兜底,处理 4~5%)→ sideOutputLateData(最后防线,处理 1%,T+1 补算)。

第九,六个常见坑:Lateness 过大 OOM、重复输出未处理、未设置侧输出丢数据、GlobalWindow 不支持、会话窗口合并异常、WM 不推进状态不清除。

第十,八条最佳实践:Lateness 不要过大、必须设置侧输出、下游幂等处理、配合 withIdleness、标记迟到触发、监控状态大小、GlobalWindow/会话窗口谨慎用、优先有界乱序处理大部分乱序。

AllowedLateness 是 Flink 迟到数据处理的核心机制,但它是兜底机制,不是主力。生产环境的正确做法是:优先用有界乱序处理大部分乱序,allowedLateness 处理少量迟到,sideOutputLateData 处理超期数据补算,三层配合,既保证低延迟,又保证结果准确,还保证不丢数据。

相关推荐
starzy19902 小时前
Flink SlotPool 申请资源源码跟踪:从调度器请求到Task部署的完整链路
大数据·flink
yydbjx2 小时前
数据驱动的正畸:黄岩隐形矫正方案的数字化流水线
大数据
q243622 小时前
2026 企业 AI 办公工具选型指南:如何匹配业务场景完成端到端任务
大数据·人工智能
智商网输送线配件3 小时前
自动化流水线配件采购实战:从应急补货到非标定制的供应链优化方案
大数据·运维·人工智能·自动化·智商网·流水线设备配件
q243623 小时前
2026 企业 AI 办公工具选型指南:企业团队 AI 平台评估与场景适配
大数据·人工智能
龙亘川3 小时前
系统监控业务建模:从运行状态监测到异常处置恢复闭环
大数据·人工智能·智慧城市·开源软件·数据可视化
枯木◊靠推文躺平版3 小时前
2026企业AI办公工具选型指南:单点工具与 Work Agent 如何取舍
大数据·人工智能
MiYi124063 小时前
2026深度体验:智能体办公工具助力企业协同效率提升实测
大数据·人工智能
198******126343 小时前
2026年企业AI办公工具选型指南:如何匹配业务场景完成评估
大数据·运维·人工智能