Watermark和代码是基于 Flink1.12版本的,概念相似。有时间会陆续完善

Watermark包含几个重要的知识点

抽时间积累点知识,网上文档很多,仅根据自己的理解,概括性的记录下

名称: 水位线 , 水印都行,就一个名字而已

下文中的事件,指代数据库记录,log日志,流量日志,度量信息等等

Watermark应用场景,如果这两个场景不满足,则不太需要考虑Watermark

1. window算子

2. EventTime作为计算时间

注:Flink有三个时间,IngestionTime, ProcessTime和EventTime

IngestionTime:进入Flink的时间,也就是在source里面生成

ProcessTime:算子执行的时间,取机器本地时间,没有特殊要求可以考虑这个,简单暴力

EventTime:事件时间,取事件生成时的创建时间,这个与现实最接近,日常生产考虑使用这个

为什么要用EventTime作为Flink计算时间?

真实场景下,数据可能乱序和迟到,没有window的场景下,只需要经过ETL过程,直接流到下游即可

有window的场景下,需要确定窗口能正常关闭,并执行统计逻辑。

假设ProcessTime作为Flink时间,window范围[1000,5000),机器时间已经>=5000,窗口触发执行,然后来了一条事件(假设事件时间:4500),理论上这个事件属于这个窗口,但是该窗口已经执行完成了,这条事件则未统计到。

所以,为了尽可能的保证生成的数据能被Flink处理到,选择EventTime作为Flink计算时间

Watermark是什么样的?

Flink包含四种类型的数据(继承自抽象类StreamElement),Watermark是其中的一种类型,该类仅包含唯一一个long类型的timestamp变量,该表量记录时间,单调递增。

Watermark的作用

Watermark计算方式

Watermark=max(窗口内所有事件时间) - 延迟执行时间

Window触发的充分必要条件

1. Window内有数据

2. Watermark >= Window的EndTime

开发中涉及Watermark的相关代码,参考如下:

/** 数据样例
         1,zhangsan,18,2-2,61,1000
         1,zhangsan,18,2-2,70.5,6000
         1,zhangsan,18,2-2,20,2000
         1,zhangsan,18,2-2,30,8000
         1,zhangsan,18,2-2,40,9000
         1,zhangsan,18,2-2,50.5,4000
         1,zhangsan,18,2-2,60,10000
         1,zhangsan,18,2-2,100,13000
         1,zhangsan,18,2-2,200,18000
         2,lisi,18,2-2,200,3000
         2,lisi,18,2-2,200,18000
   */

StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
// 设置周期性生成Watermark的时间
env.getConfig().setAutoWatermarkInterval(100);
DataStreamSource<String> source = env.socketTextStream("localhost", 9999);
SingleOutputStreamOperator<Item> mapSource = source.map(new MapFunction<String, Item>() {

            Item item = new Item();

            @Override
            public Item map(String value) throws Exception {
                String[] ss = value.split(",");
                item.setId(Integer.parseInt(ss[0]));
                item.setName(ss[1]);
                item.setAge(Integer.parseInt(ss[2]));
                item.setClassName(ss[3]);
                item.setScore(Float.parseFloat(ss[4]));
                item.setEventTime(Long.parseLong(ss[5]));
                return item;
            }
        });

// 如下给出了几种生成Watermark的方法,常用的就是第一种。forBoundedOutOfOrderness表示window延迟执行的时间
WatermarkStrategy<Item> watermarkStrategy1 = WatermarkStrategy.<Item>forBoundedOutOfOrderness(Duration.ofSeconds(0)).withTimestampAssigner(new SerializableTimestampAssigner<Item>() {
            @Override
            public long extractTimestamp(Item element, long recordTimestamp) {
                return element.getEventTime();
            }
        }).withIdleness(Duration.ofSeconds(10));

WatermarkStrategy<Item> watermarkStrategy2 = WatermarkStrategy.<Item>forBoundedOutOfOrderness(Duration.ofSeconds(5)).withTimestampAssigner((event, timestamp) -> event.getEventTime());

WatermarkStrategy<Item> watermarkStrategy3 = WatermarkStrategy.<Item>forBoundedOutOfOrderness(Duration.ofSeconds(5)).withTimestampAssigner(new TimestampAssignerSupplier<Item>() {
            @Override
            public TimestampAssigner<Item> createTimestampAssigner(Context context) {
                return new TimestampAssigner<Item>() {
                    @Override
                    public long extractTimestamp(Item element, long recordTimestamp) {
                        return element.getEventTime();
                    }
                };

                // return (element, recordTimestamp) -> element.getEventTime();  // lambda表达式
            }
        });

// 应用Watermark
KeyedStream<Item, Integer> keyedStream = mapSource.assignTimestampsAndWatermarks(watermarkStrategy1).keyBy(new KeySelector<Item, Integer>() {
            @Override
            public Integer getKey(Item value) throws Exception {
                return value.getId();
            }
        });
SingleOutputStreamOperator<String> processStream = keyedStream.window(TumblingEventTimeWindows.of(Time.seconds(5)))
                .process(new ProcessWindowFunction<Item, String, Integer, TimeWindow>() {

                    @Override
                    public void process(Integer integer, Context context, Iterable<Item> elements, Collector<String> out) throws Exception {
                        StringBuilder sb = new StringBuilder();
                        float sum = 0;
                        int count = 0;
                        String name = "";
                        for (Item item : elements) {
                            name = item.getName();
                            count += 1;
                            sum += item.getScore();
                        }
                        sb.append(integer).append(",")
                                .append(name).append(",")
                                .append(count).append(",")
                                .append(sum)
                                .append("\t****** 窗口范围:").append(context.window().getStart()).append(" - ").append(context.window().getEnd())
                                .append("\t****** 窗口水位线:").append(context.currentWatermark());
                        out.collect(sb.toString());
                    }
                });
processStream.print();
env.execute("Watermark测试");