Flink之Watermark

一、watermark如何生成

punctuated:每条数据后都会插入当前事件时间解析出来的watermark
periodic:周期性生成,默认是200m生成一个watermark
在新版本中punctuated已经被标记为过时(当前版本1.18.1)

        DataStreamSource.assignTimestampsAndWatermarks(WatermarkStrategy.
                <Order>forBoundedOutOfOrderness(Duration.ofSeconds(3))
                .withTimestampAssigner(new SerializableTimestampAssigner<String>() {
                    @Override
                    public long extractTimestamp(Order element, long recordTimestamp) {
                        return element.getTs();
                    }
                })
        );

watermark的构造:
1.forMontonousTimestamps:时间戳单调递增策略
2.forBoundedOutOfOrderness:为乱序数据创建水位线策略
3.forGenerator:自定义策略

在assignTimestampsAndWatermarks<>传入的参数是WaterMarkStrategy匿名内部类进入查看水位线生成策略WaterMarkStrategy接口 包含时间分配器TimestampAssigner和WatermarkGenerator水位线生成器

public interface WatermarkStrategy<T>
        extends TimestampAssignerSupplier<T>, WatermarkGeneratorSupplier<T> {
    // 主要负责按照既定的方式,基于时间戳生成水位线
    @Override
    WatermarkGenerator<T> createWatermarkGenerator(WatermarkGeneratorSupplier.Context context);

 	// 负责从数据元素中提取时间戳,并分配给元素 时间戳分配器是生成水位线的基础
    @Override
    default TimestampAssigner<T> createTimestampAssigner(
            TimestampAssignerSupplier.Context context) {
  
        return new RecordTimestampAssigner<>();
    }
 	
 	// 内置谁水位线 单调递增 对于有序流 其实是乱序流的一种特殊情况 里边设置的延迟等待时间是0L
    static <T> WatermarkStrategy<T> forMonotonousTimestamps() {
        return (ctx) -> new AscendingTimestampsWatermarks<>();
    }

	// 乱序流 设置等待时间 水位线策略 maxOutOfOrderness最大乱序时间
    static <T> WatermarkStrategy<T> forBoundedOutOfOrderness(Duration maxOutOfOrderness) {
        return (ctx) -> new BoundedOutOfOrdernessWatermarks<>(maxOutOfOrderness);
    }
	// 自定义水位线策略 其实就是继承WatermarkGenerator
    static <T> WatermarkStrategy<T> forGenerator(WatermarkGeneratorSupplier<T> generatorSupplier) {
        return generatorSupplier::createWatermarkGenerator;
    }


    static <T> WatermarkStrategy<T> noWatermarks() {
        return (ctx) -> new NoWatermarksGenerator<>();
    }
}

watermark的生成策略 WatermarkGenerator接口:

public interface WatermarkGenerator<T> {

	// 数据来一条调用一次
    void onEvent(T event, long eventTimestamp, WatermarkOutput output);
	// 定时调用,默认是200ms
    void onPeriodicEmit(WatermarkOutput output);
}

以forBoundedOutOfOrderness的实现为例

public class BoundedOutOfOrdernessWatermarks<T> implements WatermarkGenerator<T> {
    // 接收到的最大时间戳
    private long maxTimestamp;
	// 乱序程度 代码中手动设置的
    private final long outOfOrdernessMillis;

    public BoundedOutOfOrdernessWatermarks(Duration maxOutOfOrderness) {
        checkNotNull(maxOutOfOrderness, "maxOutOfOrderness");
        checkArgument(!maxOutOfOrderness.isNegative(), "maxOutOfOrderness cannot be negative");

        this.outOfOrdernessMillis = maxOutOfOrderness.toMillis();
        // 默认值Long的最小值 + 乱序程度 +1 
        this.maxTimestamp = Long.MIN_VALUE + outOfOrdernessMillis + 1;
    }

    @Override
    public void onEvent(T event, long eventTimestamp, WatermarkOutput output) {
    	// 来一条数据处理一条 每条数据都会更新 maxTimestamp
        maxTimestamp = Math.max(maxTimestamp, eventTimestamp);
    }
	// 发送watermark的逻辑
    @Override
    public void onPeriodicEmit(WatermarkOutput output) {
        output.emitWatermark(
        // maxTimestamp - outOfOrdernessMillis(为了修正乱序数据) 减1ms是为了后续开窗,保证窗口是左闭右开的状态。保证窗口关闭时数据只会落在一个窗口区间
        new Watermark(maxTimestamp - outOfOrdernessMillis - 1));
    }
}

二、水位线的在DataStream API中的处理逻辑


    public SingleOutputStreamOperator<T> assignTimestampsAndWatermarks(
            WatermarkStrategy<T> watermarkStrategy) {
        final WatermarkStrategy<T> cleanedStrategy = clean(watermarkStrategy);

        final int inputParallelism = getTransformation().getParallelism();
        final TimestampsAndWatermarksTransformation<T> transformation =
                new TimestampsAndWatermarksTransformation<>(
                        "Timestamps/Watermarks",
                        inputParallelism,
                        getTransformation(),
                        cleanedStrategy,
                        false);
        getExecutionEnvironment().addOperator(transformation);
        return new SingleOutputStreamOperator<>(getExecutionEnvironment(), transformation);
    }

assignTimestampsAndWatermarks本质上就是DataStream中的一个算子
将用户自定义的操作封装到Operator中 再将Operator封装进Transformation ,最后将Transformation添加到env的集合中去。并且 new Transformation 都有与之对应的TransformationOperator 上述代码 new TimestampsAndWatermarksTransformation() 因此去查看TimestampsAndWatermarksOperator

// TimestampsAndWatermarksOperator类中的open()
    @Override
    public void open() throws Exception {
        super.open();
		// 初始化用户定义的水印生成逻辑,如果需要定时发送水印会注册一个定时器
        timestampAssigner = watermarkStrategy.createTimestampAssigner(this::getMetricGroup);
        watermarkGenerator =
                emitProgressiveWatermarks
                        ? watermarkStrategy.createWatermarkGenerator(this::getMetricGroup)
                        : new NoWatermarksGenerator<>();

        wmOutput = new WatermarkEmitter(output);

        watermarkInterval = getExecutionConfig().getAutoWatermarkInterval();
        if (watermarkInterval > 0 && emitProgressiveWatermarks) {
            final long now = getProcessingTimeService().getCurrentProcessingTime();
            getProcessingTimeService().registerTimer(now + watermarkInterval, this);
        }
    }

首先从配置获取定时生成watermark间隔参数并创建当前时间(处理时间) + 间隔的定时器定义了第一个watermark是如何生成的。定时器会自动执行onProcessingTime()

	// 先调用用户定义的onPeriodicEmit()发送水印,然后获取当前时间,最后注册当前时间加水位线电视发送间隔的定时器触发,等待下次触发该方法
    @Override
    public void onProcessingTime(long timestamp) throws Exception {
    	// 这里调用发送一次watermark,随后再次创建下一次的定时器,作为一个算子肯定会接收处理数据 那么肯定会存在processElement()方法
        watermarkGenerator.onPeriodicEmit(wmOutput);

        final long now = getProcessingTimeService().getCurrentProcessingTime();
        getProcessingTimeService().registerTimer(now + watermarkInterval, this);
    }
	// 当元素到达算子后会调用processElements()
 @Override
    public void processElement(final StreamRecord<T> element) throws Exception {
    /**
    	对数据的处理逻辑什么都不做直接像下游发送,然后调用onEvent记录最大时间戳 其实就是flink先发送数据在生成watermark watermark在生成他的数据之后
		
	
    */
        final T event = element.getValue();
        // 	如果元素已经被注册了时间,就直接获取或者设置为Long.MIN_VALUE
        final long previousTimestamp =
                element.hasTimestamp() ? element.getTimestamp() : Long.MIN_VALUE;
                // 从数据中提取时间戳再将时间写入元素中
        final long newTimestamp = timestampAssigner.extractTimestamp(event, previousTimestamp);

        element.setTimestamp(newTimestamp);
        output.collect(element);
        // 调用用户定义的onEvent()根据用户的逻辑选择刷新水印以及是否发送水印
        watermarkGenerator.onEvent(event, newTimestamp, wmOutput);
    }

调用流程
watermark生成器本质上就是一个算子,在生命周期方法Open()中会注册定时器,并在定时器中发送记录最大时间戳的watermark并继续注册定时器,算子对业务数据不做任务处理直接发送给下游后记录当前数据的时间与记录的最大时间作比较。

三、watermark的传递规则

Watermark继承自StreamElement

@PublicEvolving
public final class Watermark extends StreamElement {

    public static final Watermark MAX_WATERMARK = new Watermark(Long.MAX_VALUE);
 
    public static final Watermark UNINITIALIZED = new Watermark(Long.MIN_VALUE);

    public Watermark(long timestamp) {
        this.timestamp = timestamp;
    }

    public long getTimestamp() {
        return timestamp;
    }
    @Override
    public boolean equals(Object o) {
        return this == o
                || o != null
                        && o.getClass() == Watermark.class
                        && ((Watermark) o).timestamp == timestamp;
    }

    @Override
    public int hashCode() {
        return (int) (timestamp ^ (timestamp >>> 32));
    }

    @Override
    public String toString() {
        return "Watermark @ " + timestamp;
    }
}

StreamElement用于算子间的数据流动 不包含checkpoint的barrier 分别有四个子类
1.StreamRecord:业务数据
2.Watermark:用于表示事件时间的特殊数据
3.LatencyMarker:特殊记录数据,记录创建时间,算子id,subtask编号
4.WatermarkStatus:用于标记是否为空闲流,即IDLE和ACTIVE

watermark的处理逻辑:
当算子接收到watermark时首先会对其进行操作并发送接收到最小的watermark到下游,在多并行下传递watermark发送接收到最小的那个

  • 25
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Flink中,Watermark是一种用于衡量Event Time进展的机制。它用于处理实时数据中的乱序问题,并常与窗口结合使用来实现事件的有序处理。Watermark可以被理解为一种时间戳标记,表示在该标记之前的事件都已经到达,因此可以触发窗口计算。Watermark的生成可以通过实现WatermarkGenerator接口来实现,该接口包含了onEvent和onPeriodicEmit两个方法来生成Watermark。在onEvent方法中,可以观察流事件数据,并在获取到带有Watermark信息的特殊事件元素时发出Watermark。而在onPeriodicEmit方法中,可以根据需要周期性地发出Watermark。同时,在Flink中,还可以通过WatermarkStrategy接口来定义Watermark的生成策略,该接口包含了createTimestampAssigner方法用于实例化一个可分配的时间戳生成器,以及createWatermarkGenerator方法用于实例化一个Watermark生成器。总之,WatermarkFlink中起到了衡量事件时间进展和触发窗口计算的重要作用。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [Flink:Watermark](https://blog.csdn.net/qq_24325581/article/details/117515189)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [1分钟理解FlinkWatermark机制](https://blog.csdn.net/m0_68470434/article/details/126742410)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值