之前对于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。不同的时,它可以修复上面情况的延迟,即允许数据在窗口到期后一定时间内在接受当前窗口的数据。