一文搞懂 Flink Watermark 设计理念(附源码)

1. Watermark 的核心设计理念

1.1 为什么需要 Watermark?

问题背景:Event Time vs Processing Time

场景:统计每小时的交易金额

Processing Time 方式:
系统时间 10:00-11:00 的所有事件 → 10点窗口
问题:网络延迟、重放历史数据时结果不确定

Event Time 方式:
事件时间戳 10:00-11:00 的所有事件 → 10点窗口  
问题:如何知道"10点的所有事件都到齐了"?
      ↓
    需要 Watermark!

1.2 Watermark 的语义定义

// flink-core/src/main/java/org/apache/flink/api/common/eventtime/Watermark.java

/**
 * Watermark(t) 声明:
 * 事件时间已经到达时间 t,意味着不应该再有时间戳 <= t 的事件到来
 * 
 * 如果后续还有 timestamp <= t 的事件,则为"迟到事件"(Late Event)
 */
public final class Watermark implements Serializable {
   
   
		// 本质就是一个时间戳
    private final long timestamp;  // 毫秒时间戳
    
    public static final Watermark MAX_WATERMARK = new Watermark(Long.MAX_VALUE);
}

关键概念:

Watermark(T) 的含义:
┌─────────────────────────────────────────────────┐
│  时间轴:  ... ─────|─────|─────|─────────>     │
│                    T-2   T-1    T    当前Watermark│
│                                                  │
│  保证:timestamp <= T 的事件"应该"都已到达        │
│  允许:后续可能有迟到事件 (Late Events)           │
└─────────────────────────────────────────────────┘

2. Watermark 核心接口设计

2.1 设计的三层抽象

// 层次1: WatermarkStrategy - 策略工厂
@Public
public interface WatermarkStrategy<T> {
   
   
    // 创建 Watermark 生成器
    WatermarkGenerator<T> createWatermarkGenerator(Context context);
    
    // 创建时间戳提取器
    TimestampAssigner<T> createTimestampAssigner(Context context);
}

// 层次2: WatermarkGenerator - 生成逻辑
@Public
public interface WatermarkGenerator<T> {
   
   
    // 每个事件到来时调用
    void onEvent(T event, long eventTimestamp, WatermarkOutput output);
    
    // 周期性调用(默认200ms)
    void onPeriodicEmit(WatermarkOutput output);
}

// 层次3: WatermarkOutput - 输出接口
public interface WatermarkOutput {
   
   
    void emitWatermark(Watermark watermark);
    void markIdle();      // 标记空闲
    void markActive();    // 标记活跃
}

设计优势:

  • 分离关注点:时间戳提取 vs Watermark 生成
  • 灵活性:支持 Punctuated 和 Periodic 两种模式的统一
  • 可组合:通过装饰器模式扩展功能(Idleness、Alignment)

3. Watermark 生成策略(源码实现)

3.1 策略一:单调递增时间戳

// 适用场景:事件严格按时间顺序到达(如日志文件)
@Public
public class AscendingTimestampsWatermarks<T> implements WatermarkGenerator<T> {
   
   
    
    private long maxTimestamp = Long.MIN_VALUE + 1;

    @Override
    public void onEvent(T event, long eventTimestamp, WatermarkOutput output) {
   
   
        // 每个事件到达,更新最大时间戳
        maxTimestamp = Math.max(maxTimestamp, eventTimestamp);
    }

    @Override
    public void onPeriodicEmit(WatermarkOutput output) {
   
   
        // 周期性发送 Watermark = 当前最大时间戳 - 1
        output.emitWatermark(new Watermark(maxTimestamp - 1));
    }
}

核心思想:

事件流:  [ts=100] [ts=105] [ts=110] [ts=115] ...
           ↓        ↓        ↓        ↓
Watermark:   99      104      109      114
           (紧跟最大时间戳)

3.2 策略二:有界乱序(最常用)

// flink-core/.../BoundedOutOfOrdernessWatermarks.java
@Public
public class BoundedOutOfOrdernessWatermarks<T> implements WatermarkGenerator<T> {
   
   
    
    private long maxTimestamp;                    // 见过的最大时间戳
    private final long outOfOrdernessMillis;      // 允许的最大乱序时间

    public BoundedOutOfOrdernessWatermarks(Duration maxOutOfOrderness) {
   
   
        this.outOfOrdernessMillis = maxOutOfOrderness.toMillis();
        // 初始化为最小值 + 乱序时间 + 1
        this.maxTimestamp = Long.MIN_VALUE + outOfOrdernessMillis + 1;
    }

    @Override
    public void onEvent(T event, long eventTimestamp, WatermarkOutput output) {
   
   
        // 持续跟踪最大时间戳
        maxTimestamp = Math.max(maxTimestamp, eventTimestamp);
    }

    @Override
    public void onPeriodicEmit(WatermarkOutput output) {
   
   
        // Watermark = 最大时间戳 - 允许乱序时间 - 1
        output.emitWatermark(new Watermark(maxTimestamp - outOfOrdernessMillis - 1));
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值