Flink Watermark原理

Watermark简介

在 Apache Flink 中,水印(Watermark) 是一种用于处理事件时间(Event Time)流数据的机制。它代表了流处理系统中对事件时间进度的理解,用来标识数据流中的时间点,从而帮助确定何时窗口应该关闭并触发计算。

水印的作用
控制窗口计算:Flink 使用水印来决定何时关闭基于事件时间的窗口。例如,在一个 10 分钟的滚动窗口中,只有当水印超过窗口结束时间时,Flink 才会认为该窗口内的所有数据都已经到达,并且可以安全地进行计算。
处理迟到数据:通过设置允许的最大延迟(Allowed Lateness),Flink 可以接收那些晚于预期到达的数据。水印使得我们可以定义如何处理这些迟到的数据,比如将它们添加到已经计算过的窗口中或忽略它们。


水印的工作原理
水印是按照事件时间戳生成的特殊标记,通常表示“在这个时间之前的所有事件都已经到达”。具体来说:

事件时间戳:每个事件都有一个时间戳,这个时间戳反映了事件实际发生的时间,而不是它被处理的时间。
水印生成器(Watermark Generator):这是负责生成水印的组件。它根据输入数据流中的事件时间戳来创建水印。最常见的是使用 forBoundedOutOfOrderness 方法来指定最大乱序程度,即允许的最大延迟时间。

推进水印:随着更多事件的到来,水印逐渐推进。理想情况下,水印应该是递增的,但实际应用中可能会有乱序的情况,因此我们需要设定一个合理的最大乱序时间。

WatermarkStrategy<OrderItem> watermarkStrategy = WatermarkStrategy
    .<OrderItem>forBoundedOutOfOrderness(Duration.ofSeconds(5)) // 允许最多5秒的乱序
    .withTimestampAssigner((event, timestamp) -> event.updateTime); // 提取事件时间戳的方法

 

水印的一个重要特性是它可以和允许的最大延迟一起工作,以便处理迟到的数据。例如,如果你设置了一个 1 小时的滚动窗口,并且允许最多 5 分钟的迟到数据,那么即使水印超过了窗口的结束时间,只要数据的事件时间不超过窗口结束时间加上 5 分钟,这些迟到的数据仍然会被包含在窗口计算中。

 

一、构造数据源

import org.apache.flink.streaming.api.functions.source.SourceFunction;

import java.util.Random;
import java.util.concurrent.TimeUnit;

public class OrderItemSource implements SourceFunction<OrderItemSource.OrderItem> {

    private volatile boolean isRunning = true;

    @Override
    public void run(SourceContext<OrderItem> sourceContext) throws Exception {
        // 每一秒生生一条数据
        for (int i=0; i < 100; i++) {
            OrderItem orderItem = new OrderItem(i, i % 2, 1, "商品A", new Random().nextInt(100), 1, System.currentTimeMillis());
            System.out.println(orderItem);
            sourceContext.collect(orderItem);
            TimeUnit.MILLISECONDS.sleep(1000);
        }
    }

    @Override
    public void cancel() {
        isRunning = false;
    }

    public static class OrderItem {
        public int id;
        public int orderId;
        public String shopName;
        public double paidPrice;
        public long updateTime;

        public OrderItem() {}

        public OrderItem(int id, int orderId, int shopId, String shopName, double paidPrice, int shopStatus, long updateTime) {
            this.id = id;
            this.orderId = orderId;
            this.shopName = shopName;
            this.paidPrice = paidPrice;
            this.updateTime = updateTime;
        }

        @Override
        public String toString() {
            return "OrderItem{" +
                    "id=" + id +
                    ", orderId=" + orderId +
                    ", shopName='" + shopName + '\'' +
                    ", paidPrice=" + paidPrice +
                    ", updateTime=" + updateTime +
                    '}';
        }
    }
}

 

二、核心代码

import com.aitogether.bigdata.onemetrics.runner.source.OrderItemSource;
import com.aitogether.bigdata.onemetrics.runner.source.OrderItemSource.OrderItem;
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.util.OutputTag;

import java.time.Duration;


public class WindowWatermarkStrategy2 {

    public static void main(String[] args) throws Exception {
        final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1); // 并行度

        // 接入数据源
        DataStream<OrderItem> orderItemStream = env.addSource(new OrderItemSource())
        // 配置生成Watermark方式,设置最大乱序时间5秒,时间戳按updateTime获取(此时系统按EventTime计算Watermark)
        .assignTimestampsAndWatermarks(WatermarkStrategy
                        .<OrderItem>forBoundedOutOfOrderness(Duration.ofSeconds(5)) // 最大乱序时间(maxOutOfOrderness)
                        .withTimestampAssigner((event, timestamp) -> event.updateTime)); // 获取时间戳方式,内部会自动跟踪已观察到的最大时间戳
        
        // 按orderId分区,开启一个10秒的滚动时间窗口,并计算窗口内的最大ID
        orderItemStream.keyBy(element -> element.orderId)
                .window(TumblingEventTimeWindows.of(Time.seconds(10))) // 滚动【事件时间】窗口
                .reduce((a, b) -> a.id > b.id ? a : b) // 求窗口内最大ID
                .print("result");

        env.execute(WindowWatermarkStrategy2.class.getSimpleName());
    }

}

 

三、结果分析

OrderItem{id=0, orderId=0, shopName='商品A', paidPrice=45.0, updateTime=1736238573687}
OrderItem{id=1, orderId=1, shopName='商品A', paidPrice=91.0, updateTime=1736238574692}
OrderItem{id=2, orderId=0, shopName='商品A', paidPrice=79.0, updateTime=1736238575696}
OrderItem{id=3, orderId=1, shopName='商品A', paidPrice=25.0, updateTime=1736238576699}
OrderItem{id=4, orderId=0, shopName='商品A', paidPrice=67.0, updateTime=1736238577701}
OrderItem{id=5, orderId=1, shopName='商品A', paidPrice=18.0, updateTime=1736238578706}
OrderItem{id=6, orderId=0, shopName='商品A', paidPrice=7.0, updateTime=1736238579710}
OrderItem{id=7, orderId=1, shopName='商品A', paidPrice=88.0, updateTime=1736238580714}
OrderItem{id=8, orderId=0, shopName='商品A', paidPrice=61.0, updateTime=1736238581719}
OrderItem{id=9, orderId=1, shopName='商品A', paidPrice=62.0, updateTime=1736238582724}
OrderItem{id=10, orderId=0, shopName='商品A', paidPrice=58.0, updateTime=1736238583726}
OrderItem{id=11, orderId=1, shopName='商品A', paidPrice=83.0, updateTime=1736238584729}
OrderItem{id=12, orderId=0, shopName='商品A', paidPrice=34.0, updateTime=1736238585732} // 12秒消息到达,此时Watermark:12-5=7
result> OrderItem{id=6, orderId=0, shopName='商品A', paidPrice=7.0, updateTime=1736238579710} // 触发7秒前的窗口计算,求出最大值6
result> OrderItem{id=5, orderId=1, shopName='商品A', paidPrice=18.0, updateTime=1736238578706} // 触发7秒前的窗口计算,求出最大值5

OrderItem{id=13, orderId=1, shopName='商品A', paidPrice=50.0, updateTime=1736238586738}
OrderItem{id=14, orderId=0, shopName='商品A', paidPrice=23.0, updateTime=1736238587743}
OrderItem{id=15, orderId=1, shopName='商品A', paidPrice=22.0, updateTime=1736238588747}
OrderItem{id=16, orderId=0, shopName='商品A', paidPrice=17.0, updateTime=1736238589752}
OrderItem{id=17, orderId=1, shopName='商品A', paidPrice=36.0, updateTime=1736238590758}
OrderItem{id=18, orderId=0, shopName='商品A', paidPrice=94.0, updateTime=1736238591763}
OrderItem{id=19, orderId=1, shopName='商品A', paidPrice=51.0, updateTime=1736238592768}
OrderItem{id=20, orderId=0, shopName='商品A', paidPrice=94.0, updateTime=1736238593773}
OrderItem{id=21, orderId=1, shopName='商品A', paidPrice=2.0, updateTime=1736238594778}
OrderItem{id=22, orderId=0, shopName='商品A', paidPrice=39.0, updateTime=1736238595782} // 22秒消息到达,此时Watermark:22-5=17
result> OrderItem{id=15, orderId=1, shopName='商品A', paidPrice=22.0, updateTime=1736238588747} // 触发17秒前的窗口计算,求出最大值15
result> OrderItem{id=16, orderId=0, shopName='商品A', paidPrice=17.0, updateTime=1736238589752} // 触发17秒前的窗口计算,求出最大值16

OrderItem{id=23, orderId=1, shopName='商品A', paidPrice=20.0, updateTime=1736238596784}
OrderItem{id=24, orderId=0, shopName='商品A', paidPrice=87.0, updateTime=1736238597790}
OrderItem{id=25, orderId=1, shopName='商品A', paidPrice=1.0, updateTime=1736238598794}
OrderItem{id=26, orderId=0, shopName='商品A', paidPrice=13.0, updateTime=1736238599797}
OrderItem{id=27, orderId=1, shopName='商品A', paidPrice=92.0, updateTime=1736238600801}
OrderItem{id=28, orderId=0, shopName='商品A', paidPrice=97.0, updateTime=1736238601804}
OrderItem{id=29, orderId=1, shopName='商品A', paidPrice=60.0, updateTime=1736238602806}
OrderItem{id=30, orderId=0, shopName='商品A', paidPrice=16.0, updateTime=1736238603811}
OrderItem{id=31, orderId=1, shopName='商品A', paidPrice=91.0, updateTime=1736238604813}
OrderItem{id=32, orderId=0, shopName='商品A', paidPrice=64.0, updateTime=1736238605817} // 后面数据以此类推
result> OrderItem{id=25, orderId=1, shopName='商品A', paidPrice=1.0, updateTime=1736238598794}
result> OrderItem{id=26, orderId=0, shopName='商品A', paidPrice=13.0, updateTime=1736238599797}
OrderItem{id=33, orderId=1, shopName='商品A', paidPrice=14.0, updateTime=1736238606821}
OrderItem{id=34, orderId=0, shopName='商品A', paidPrice=1.0, updateTime=1736238607825}

 

 

文档 https://apachecn.github.io/flink-doc-zh/#/docs/1.7-SNAPSHOT/README

posted on 2024-12-20 18:29  wzyy  阅读(76)  评论(0)    收藏  举报