watermark是为解决事件流乱序问题,如果,A,B两个端,A把10点15日志发送到服务端,B发送10.12的日志,但是因为B网络延迟,造成服务器在10点16时候做数据统计的时候A的数据到了,B的数据没有到造成数据丢失。
watermark 是一个触发计算的阀门,事件流来的时候,都会根据事件的时间创建或者更新这个阀门(取最大的),一旦阀门值大于等于流窗口结束时间,就会触发计算。
比如:下图,每行数据就是一个事件流,事件时间(eventTime)和上一个事件最大时间比较取最大值(MaxTImesTamp),减去3秒作为watermark。窗口结束时间为20000,当watermark等于或者大于20000时触发计算。
再说watermark的时候,中间插播一个东西,就是时间,flink中分为三个时间
事件流时间 | flink时间 | flink处理时间 |
事件流生成的时间,一般由事件产生源携带过来 | 进入flik的时间 | 使用flink算子进行计算的时间 |
Watermark的产生方式:
Punctuated - 数据流中每一个递增的EventTime都会产生一个Watermark。在实际的生产中Punctuated方式在TPS很高的场景下会产生大量的Watermark在一定程度上对下游算子造成压力,所以只有在实时性要求非常高的场景才会选择Punctuated的方式进行Watermark的生成。
接口定义AssignerWithPunctuatedWatermarks:Watermark checkAndGetNextWatermark(T lastElement, long extractedTimestamp);
Periodic - 周期性的(一定时间间隔或者达到一定的记录条数)产生一个Watermark。在实际的生产中Periodic的方式必须结合时间和积累条数两个维度继续周期性产生Watermark,否则在极端情况下会有很大的延时。
接口定义AssignerWithPeriodicWatermarks:Watermark getCurrentWatermark();
周期性:
ev.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);//设置时间类型
/**
* This generator generates watermarks assuming that elements arrive out of order,
* but only to a certain degree. The latest elements for a certain timestamp t will arrive
* at most n milliseconds after the earliest elements for timestamp t.
*/
public class BoundedOutOfOrdernessGenerator implements AssignerWithPeriodicWatermarks
private final long maxOutOfOrderness = 3000; // 3.0 seconds允许延迟的时间
private long currentMaxTimestamp;
@Override
public long extractTimestamp(MyEvent element, long previousElementTimestamp) {
long timestamp = element.getCreationTime();//获取事件使时间
currentMaxTimestamp = Math.max(timestamp, currentMaxTimestamp); //比较获取最大时间
return timestamp;
}
@Override
public Watermark getCurrentWatermark() {
// return the watermark as current highest timestamp minus the out-of-orderness bound
return new Watermark(currentMaxTimestamp - maxOutOfOrderness);//生产wm
//此处说明一下,如果延迟比较固定,可以使用系统当前时间代替currentMaxTimestamp
}
}
递增模式:
使用 AssignerWithPunctuatedWatermarks
在某个事件指定生成新的水印的时候生成水印。这种情况下,Flink 首先会调用 extractTimestamp(...)
方法为数据分配时间戳,然后立即调用 checkAndGetNextWatermark(...)
。
checkAndGetNextWatermark(...)
方法传递在 extractTimestamp(...)
生成的时间戳,并且界定是否要生成水印。每当 checkAndGetNextWatermark(...)
方法返回非空水印,并且该水印大于先一个水印时,将向后发出新水印。
class PunctuatedAssigner extends AssignerWithPunctuatedWatermarks[MyEvent] {
override def extractTimestamp(element: MyEvent, previousElementTimestamp: Long): Long = {
element.getCreationTime
}
override def checkAndGetNextWatermark(lastElement: MyEvent, extractedTimestamp: Long): Watermark = {
if (lastElement.hasWatermarkMarker()) new Watermark(extractedTimestamp) else null
}
}
每个事件都可以生成水印。但是,由于水印会导致一些后续的计算,因此过多的水印会降低性能。