Flink水涨船高:EventTime和WaterMark

之前对于ProcessingTime,作为一种当前计算机的处理时间,我们不用在意它数据到来的顺序,一切以我当前计算机的时间为准,不用协调其他的计算机节点。
但是呢对于EventTime,拿我们Web网站的日志来说,EventTime即是日志中的时间戳,但是发送数据的情况不可能总是那么理想,到达Flink的顺序不可能刚好是时间戳的顺序,为了控制这种乱序的情况,引入了WaterMark,中文翻译暂时是水位线,也可以翻译成水印,但是个人忠于水位线
它作为一种衡量EventTime时间的标准,也作为事件传输在流中


串行中的WaterMark

在这里插入图片描述
**上面这个图中,方框中的数字代表EventTime,而WaterMark插入在这些时间之中,它代表所有<=T的时间戳都来了。比如W(11)就说明<=11的时间的事件都来了。但是这样也难免会出现一些问题,比如硬是有一些之前的数据延迟到达,


并行中的WaterMark

在这里插入图片描述
对于AB:Map算子和CD的window算子,而AB是同一算子并行计算,CD是同一算子并行计算。
在A点的watermark是29,在B的watermark是17,那么通过到下个算子后,因为中间有个过程类似于分流或者说shuffle,导致A和B的watermark都进入了C和D
对于C和D来说,他们都接受到多个WaterMark,这时候他们会选择最小的那个作为自己的WaterMark,然后将自己的WaterMark发给下游,也就四下一个算子。


原函数发出Watermark

这是一个原函数,随机发出数据了并带有时间戳和watermark

package flinkjava.source;

import flinkjava.entity.elements;
import org.apache.flink.streaming.api.functions.source.SourceFunction;
import org.apache.flink.streaming.api.watermark.Watermark;

import java.util.Random;

public class source1 implements SourceFunction<elements> {
    private boolean isRunning=true;
    @Override
    public void run(SourceContext<elements> ctx) throws Exception {
        String[] names = {"ha","jack","rose","ongbo","li","zhou"};
        Random random = new Random();
        while(isRunning) {
            Long timestamp = System.currentTimeMillis();
            elements elem = new elements(names[random.nextInt(names.length)],timestamp.toString());
            //输送元素和时间戳
            ctx.collectWithTimestamp(elem,timestamp);
            //发射水位线
            ctx.emitWatermark(new Watermark(timestamp));
            Thread.sleep(1000);
        }
    }
    @Override
    public void cancel() {
        isRunning=false;
    }
}

运行代码

package flinkjava.Task;

import flinkjava.entity.elements;
import flinkjava.source.source1;
import org.apache.flink.api.java.functions.KeySelector;
import org.apache.flink.streaming.api.TimeCharacteristic;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.windowing.ProcessWindowFunction;
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 java.util.Iterator;

public class task1 {
    public static void main(String[] args) {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
        //设置事件时间
        env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);

        //引入soruce
        source1 source1 = new source1();
        DataStreamSource<elements> sourcestream = env.addSource(source1);
        sourcestream.keyBy(new KeySelector<elements, String>() {
            @Override
            public String getKey(elements value) throws Exception {
               return value.getName();
            }
        }).timeWindow(Time.seconds(15))
                .process(new ProcessWindowFunction<elements, String, String, TimeWindow>() {
                    @Override
                    public void process(String s, Context context, Iterable<elements> elements, Collector<String> out) throws Exception {
                        StringBuilder stringBuilder = new StringBuilder();
                        stringBuilder.append("触发了一次窗口了:");
                        Iterator<elements> iterator = elements.iterator();
                        while(iterator.hasNext()){

                            stringBuilder.append("currentWaterMark:"+context.currentWatermark()+"   starttime:"+context.window().getStart()+"   endtime:"+
                                    context.window().getEnd()+"  "+iterator.next()+"  ");
                        }
                        out.collect(stringBuilder.toString());
                    }
                }).print();
        try {
            env.execute("test");
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

在这里插入图片描述

可以看到了,每一行输出都是一个window,只是相同时间的window的key不同而已了,但是呢,我们输出的时候会是以一个window时间输出所有key的window,由此看出,这个watermark是保存在了某一个机器上,而不是每个key一个watermark,在同一个机器上同一task共享这个,所以呢,但最迟的数据,也就是某一个window时间窗口的最后面那个值来了,那么就是要出发所有的窗口了,所以所有的key共享了同一个task了,但是只限于分配到同一个task的key。


自定义发出WaterMark

通过实现AssignerWithPeriodicWatermarks或者AssignerWithPunctuatedWatermarks接口来自己发出时间戳。前者是定期发出,后者是基于某些特殊的元素发出。


Flink中提供的WaterMark实现

Assigners with ascending timestamps

递增的时间戳实现:

DataStream<MyEvent> stream = ...

DataStream<MyEvent> withTimestampsAndWatermarks =
    stream.assignTimestampsAndWatermarks(new AscendingTimestampExtractor<MyEvent>() {

        @Override
        public long extractAscendingTimestamp(MyEvent element) {
            return element.getCreationTime();
        }
});

在这里插入图片描述
看他是实现了AssignerWithPeriodicWatermarks,它是一种理想状况,即所有的元素都按序到达了,

Assigners allowing a fixed amount of lateness

某些情况下,比如当前节点WaterMark为19,所以认为<19的事件都认为到了,但是就是有可能有个17的延迟了,所以这时候就需要我们允许这种延迟的情况

DataStream<MyEvent> stream = ...

DataStream<MyEvent> withTimestampsAndWatermarks =
    stream.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor<MyEvent>(Time.seconds(10)) {

        @Override
        public long extractTimestamp(MyEvent element) {
            return element.getCreationTime();
        }
});

在这里插入图片描述
它是基于AssignerWithPeriodicWatermarks接口实现,和上面一样,都是周期性的发出WaterMark。不同的时,它可以修复上面情况的延迟,即允许数据在窗口到期后一定时间内在接受当前窗口的数据。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 游动-白 设计师:白松林 返回首页