Flink 使用watermark

自定义一个watermark生成策略

watermark生成策略一般有两种,一种是自定义周期性的watermark,另一种是触发式的watermark。
WatermarkGenerator接口代码如下:

@Public
public interface WatermarkGenerator<T> {

    /**
     * 每来一条事件数据调用一次,可以检查或者记录事件的时间戳,或者也可以基于事件数据本身去生成 watermark。
     */
    void onEvent(T event, long eventTimestamp, WatermarkOutput output);

    /**
     * 周期性的调用,也许会生成新的 watermark,也许不会。
     *
     * <p>调用此方法生成 watermark 的间隔时间由 {@link ExecutionConfig#getAutoWatermarkInterval()} 决定。
     * env.getConfig.getAutoWatermarkInterval();
     * <p>可以通过 env.getConfig.getAutoWatermarkInterval() 设置触发间隔
     */
    void onPeriodicEmit(WatermarkOutput output);
}

自定义watermark

package org.example.watermark;

import org.apache.flink.api.common.eventtime.Watermark;
import org.apache.flink.api.common.eventtime.WatermarkGenerator;
import org.apache.flink.api.common.eventtime.WatermarkOutput;

public class MyStrategy implements WatermarkGenerator<Integer> {
    long cur;
    @Override
    public void onEvent(Integer integer, long l, WatermarkOutput watermarkOutput) {
        // 每来一条数据即更新一下当前的event-time
        cur = Math.max(cur, l);
    }

    @Override
    public void onPeriodicEmit(WatermarkOutput watermarkOutput) {
    	// 触发生成新的watermark
        System.out.println("生成新的watermark: " + cur);
        watermarkOutput.emitWatermark(new Watermark(cur));
    }
}

使用watermark

package org.example.watermark;

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;

public class MyDemo1 {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
        env.getConfig().setAutoWatermarkInterval(5000);

        // 1. 输入100,此时watermark是100
        // 2. 再输入3000,此时触发window计算;输出100,watermark是3000
        // 3. 再输入300,此时watermark是3000
        // 4. 再输入6000,此时触发window计算;输出3000,watermark是6000,300已经被丢弃
        DataStream<String> strSource = env.socketTextStream("127.0.0.1", 9000);

        DataStream<Integer> source = strSource.map(Integer::valueOf);

		// 使用自定义的watermark策略
        DataStream<Integer> sourceWithWatermark = source.assignTimestampsAndWatermarks(
                ((WatermarkStrategy<Integer>) context -> new MyStrategy())
                        .withTimestampAssigner((elm, l) -> elm)
        );

        sourceWithWatermark.keyBy(k -> k % 5)
                .window(TumblingEventTimeWindows.of(Time.seconds(3)))
                .reduce(Integer::sum)
                .print();


        env.execute();
    }

}

另一个案例

/* MyDemo1.java */
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
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;

public class MyDemo1 {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
        // 设置每5s生成一个watermark
        env.getConfig().setAutoWatermarkInterval(5000);

        DataStream<String> strSource = env.socketTextStream("127.0.0.1", 9901);

        // {"id": 1, "name": "tom", "val": 1, "ts": "2023-09-28 17:53:00"}
        DataStream<MyObject> source = strSource.map(
                str -> {
                    Gson gson = new Gson();
                    return gson.fromJson(str, new TypeToken<MyObject>(){}.getType());
                }
        );

        DataStream<MyObject> sourceWithWatermark = source.assignTimestampsAndWatermarks(
                ((WatermarkStrategy<MyObject>) context -> new MyStrategy())
                        .withTimestampAssigner((elm, l) -> elm.getTs().getTime())
        );

        sourceWithWatermark.keyBy(MyObject::getId)
                .window(TumblingEventTimeWindows.of(Time.seconds(3)))
                .reduce((r1, r2) -> r1.val > r2.val ? r1 : r2)
                .print();

        env.execute();
    }

}

/* MyStrategy.java */
import org.apache.flink.api.common.eventtime.Watermark;
import org.apache.flink.api.common.eventtime.WatermarkGenerator;
import org.apache.flink.api.common.eventtime.WatermarkOutput;

import java.text.SimpleDateFormat;
import java.util.Date;

public class MyStrategy implements WatermarkGenerator<MyObject> {
    long cur;
    Date date;
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    @Override
    public void onEvent(MyObject myObject, long l, WatermarkOutput watermarkOutput) {
        cur = Math.max(cur, myObject.getTs().getTime());
    }

    @Override
    public void onPeriodicEmit(WatermarkOutput watermarkOutput) {
        long mark = cur - 60000;
        date = new Date(mark);
        String format = sdf.format(date);
        System.out.println("生成新的watermark: " + format);
        watermarkOutput.emitWatermark(new Watermark(mark));
    }
}

/* MyObject.java */
@Data
@ToString
public class MyObject implements Serializable {
    Integer id;
    String name;
    Double val;
    Timestamp ts;
}

在这个例子中,使用了自定义的watermark strategy,可接受数据的最大时延为1分钟。对数据按id hash后,添加了一个3秒的滑动窗口。算窗口内的value最大值。并输出。

知识点1

3秒1个点窗口是怎么划分的?

  • 此处,当我们输入了第一条数据,例如为:{"id": 1, "name": "tom", "val": 1, "ts": "2023-09-28 17:53:00"}
  • 那么窗口就是:2023-09-28 17:53:00 ~ 2023-09-28 17:53:03,注意是前闭后开。(注意,这里窗口范围是我随口邹的,实际窗口起点是有flink的内置算法结束的。实际的窗口范围计算方式,可以看org.apache.flink.streaming.api.windowing.assigners.WindowAssigner#assignWindows类的子类实现方式)
  • 下一个窗口是:2023-09-28 17:53:03 ~ 2023-09-28 17:53:06

何时会触发窗口计算

  • 两个必要条件:
    1. watermark >= window end time,注意窗口是前闭后开。所以当等于时,就已经离开窗口了
    2. 窗口内有数据

watermark怎么确定的?

  • 根据source算子,1. 所定义的watermark策略;2. 所实际接受到的数据的event time。共同计算得出。例如:第一条数据为:{"id": 1, "name": "tom", "val": 1, "ts": "2023-09-28 17:53:00"},那么此时生成的watermark就是1695894720000,对应的物理时间就是:2023-09-28 17:52:00(因为我们有1分钟的延迟哦)。

迟到数据怎么算?

  • 假如按照demo中的策略,有如下几条数据,输出结果是什么?
    • 输入:{"id": 1, "name": "tomA1", "val": 1, "ts": "2023-09-28 17:53:00"}
    • 输入:{"id": 1, "name": "tomA2", "val": 2, "ts": "2023-09-28 17:53:01"}
    • 输入:{"id": 1, "name": "tomX", "val": 2, "ts": "2023-09-28 17:50:00"}
    • 输入:{"id": 1, "name": "tomB", "val": 1, "ts": "2023-09-28 17:52:30"}
    • 输入:{"id": 1, "name": "tomC", "val": 1, "ts": "2023-09-28 17:53:33"}
    • 触发窗口计算,输出:{"id": 1, "name": "tomB", "val": 1, "ts": "2023-09-28 17:52:30"}
    • 输入:{"id": 1, "name": "tomD", "val": 1, "ts": "2023-09-28 17:54:03"}
    • 触发窗口计算,输出:{"id": 1, "name": "tomA2", "val": 2, "ts": "2023-09-28 17:53:01"}
  • 总结一下大概的思路就是:
    • 输入一条数据之后,结合策略,看是否要更新watermark。
    • 结合watermark策略来看,这条数据是否过期:event time < watermark。注意,等于也不算过期。过期就丢弃,未过期,就暂存到对应窗口中。
    • 看watermark是否>=窗口结束时间,若大于,就触发一次计算。否则,不管是当前窗口的,还是未来窗口的,都在内存中暂存着,等待watermark触发
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值