Watermark案例 

步骤

1、获取数据源

2、转化

3、声明水印(watermark)

4、分组聚合,调用window的操作

5、保存处理结果

数据源:

01,1586489566000

01,1586489567000

01,1586489568000

01,1586489569000

01,1586489570000

01,1586489571000

01,1586489572000

01,1586489573000

即为

2020-04-10 11:32:46

2020-04-10 11:32:47

2020-04-10 11:32:48

2020-04-10 11:32:49

2020-04-10 11:32:50

import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.api.java.tuple.Tuple;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.TimeCharacteristic;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.AssignerWithPeriodicWatermarks;
import org.apache.flink.streaming.api.functions.windowing.WindowFunction;
import org.apache.flink.streaming.api.watermark.Watermark;
import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
import org.apache.flink.util.Collector;

import javax.annotation.Nullable;
import java.text.SimpleDateFormat;
import java.util.Iterator;

public class WaterMarkDemo {

    public static void main(String[] args) throws Exception {

        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        // 并行度为1
        env.setParallelism(1);

        // 设定flink程序处理数据的时候用那种时间数据
        env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
        DataStreamSource<String> data = env.socketTextStream("", 7777);
        // MapFunction第一个泛型表示输入的数据类型;第二个泛型表示输出的数据类型(可以自定义)
        SingleOutputStreamOperator<Tuple2<String, Long>> maped = data.map(new MapFunction<String, Tuple2<String, Long>>() {

            public Tuple2<String, Long> map(String value) throws Exception {
                String[] split = value.split(",");
                return new Tuple2<String, Long>(split[0], Long.valueOf(split[1]));
            }
        });

        // 做出水印watermark
        // 将数据源中的每一个数据增加一个水印时间数据
        // AssignerWithPeriodicWatermarks对传入的数据周期性的打印水印
        SingleOutputStreamOperator<Tuple2<String, Long>> watermarks = maped.assignTimestampsAndWatermarks(new AssignerWithPeriodicWatermarks<Tuple2<String, Long>>() {
            
            // 当前时间戳数据
            Long currentMaxStamp = 0L;
            // 允许的最大延迟时间,此处为2秒
            Long maxOrderOut = 2000L;

            @Nullable
            public Watermark getCurrentWatermark() {
                return new Watermark(currentMaxStamp - maxOrderOut);
            }

            /**
             * 生成时间戳 ----- EventTime
             * @param element
             * @param l
             * @return
             */
            public long extractTimestamp(Tuple2<String, Long> element, long l) {
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
                Long f1 = element.f1;
                currentMaxStamp = Math.max(f1, currentMaxStamp);
                System.out.println("EventTime:" + f1 + "...f1.format:" + sdf.format(f1) + "...watermark:" + getCurrentWatermark().getTimestamp() + "...watermark.format:" + sdf.format(getCurrentWatermark().getTimestamp()));
                return f1;
            }
        });

        // 根据二元组第一个元素分组,创建时间滚动窗口,窗口时间大小为2秒
        // WindowFunction第一个泛型就是输入数据的数据类型;第二个泛型就是输出数据的数据类型;
        // 第三个就是key的数据类型;第四个就是窗口的数据类型
        SingleOutputStreamOperator<String> result = watermarks.keyBy(0).window(TumblingEventTimeWindows.of(Time.seconds(2))).apply(new WindowFunction<Tuple2<String, Long>, String, Tuple, TimeWindow>() {
            public void apply(Tuple tuple, TimeWindow timeWindow, Iterable<Tuple2<String, Long>> iterable, Collector<String> collector) throws Exception {

                // 窗口开始时间
                long start = timeWindow.getStart();
                // 窗口结束时间
                long end = timeWindow.getEnd();
                SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
                String startTime = simpleDateFormat.format(start);
                String endTime = simpleDateFormat.format(end);

                Iterator<Tuple2<String, Long>> iterator = iterable.iterator();
                ArrayList<Long> list = new ArrayList<Long>();

                while (iterator.hasNext()) {
                    Tuple2<String, Long> next = iterator.next();
                    String key = next.f0;
                    Long f1 = next.f1;
                    list.add(f1);
                }

                Collections.sort(list);
                // 第一条数据的时间
                String firstTime = simpleDateFormat.format(list.get(0));
                // 最后一条数据的时间
                String lastTime = simpleDateFormat.format(list.get(list.size() - 1));

                collector.collect("startTime:" + startTime + "...endTime:" + endTime + "...firstTime:" + firstTime + "...lastTime:" + lastTime);
            }
        });

        result.print();
        env.execute();
    }
}

注意:

当使用EventTimeWindow时,所有的Window在EventTime的时间轴上进行划分,

也就是说,在Window启动后,会根据初始的EventTime时间每隔一段时间划分一个窗口,

如果Window大小是3秒,那么1分钟内会把Window划分为如下的形式:

[00:00:00,00:00:03)

[00:00:03,00:00:06)

[00:00:03,00:00:09)

[00:00:03,00:00:12)

[00:00:03,00:00:15)

[00:00:03,00:00:18)

[00:00:03,00:00:21)

[00:00:03,00:00:24)

...

[00:00:57,00:00:42)

[00:00:57,00:00:45)

[00:00:57,00:00:48)

...

如果Window大小是10秒,则Window会被分为如下的形式:

[00:00:00,00:00:10)

[00:00:10,00:00:20)

...

[00:00:50,00:01:00)

说明:

1.窗口是左闭右开的,形式为:[window_start_time,window_end_time)。

2.Window的设定基于第一条消息的事件时间,也就是说,Window会一直按照指定的时间间隔进行划分,不论这个Window中有没有数据,EventTime在这个Window期间的数据会进入这个Window。

3.Window会不断产生,属于这个Window范围的数据会被不断加入到Window中,所有未被触发的Window都会等待触发,只要Window还没触发,属于这个Window范围的数据就会一直被加入到Window中,直到Window被触发才会停止数据的追加,而当Window触发之后才接受到的属于被触发Window的数据会被丢弃。

4.Window会在以下的条件满足时被触发执行:

(1)在[window_start_time,window_end_time)窗口中有数据存在

(2)watermark时间 >= window_end_time;

5.一般会设置水印时间,比事件时间小几秒钟,表示最大允许数据延迟达到多久

(即水印时间 = 事件时间 - 允许延迟时间)

当接收到的 水印时间 >= 窗口结束时间且窗口内有数据,则触发计算