flink中水位线是个时间的概念,每条消息由发出该消息的机器打上了一个时间戳,flink收到这些消息会拿到其中的时间戳,并以收到的所有消息中最大的时间戳作为flink自己的当前时间。
通过DataStream的assignTimestampsAndWatermarks(WatermarkStrategy watermarkStrategy) 方法可以设置水位线策略,水位线策略主要需要重写两个方法
用于设置水位线的生成策略(定时或者断点。定时指flink每隔相同的时间产生一个水位线;断点指flink每收到一条消息就产生一个水位线)
WatermarkGenerator<T> createWatermarkGenerator(WatermarkGeneratorSupplier.Context context);
用于设置如何提取消息中的时间戳,每个实体类中的时间戳属性命名不同,通常是设置取实体类的那个属性作为时间戳
default TimestampAssigner<T> createTimestampAssigner(TimestampAssignerSupplier.Context context)
(1)针对有序流:WatermarkStrategy.forMonotonousTimestamps()
此方法只设置了水位线的生成策略,没有设置如何提取每条消息中的时间戳,所以还需要设置从消息中提取时间戳的策略。举例如下:
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.fromElements(new Event("Jack", 1000L, "./home"),
new Event("Tom", 2000L, "./cart"),
new Event("Jerry", 1300L, "./prod?id=10"))
.assignTimestampsAndWatermarks(WatermarkStrategy.<Event>forMonotonousTimestamps()
.withTimestampAssigner(new SerializableTimestampAssigner<Event>() {
@Override
public long extractTimestamp(Event element, long recordTimestamp) {
return element.getTimestamp();
}
})
);
(2)针对无序流
调用 WatermarkStrategy. forBoundedOutOfOrderness() 方法就可以实现。这个方法需要传入一个 maxOutOfOrderness 参数,表示“最大乱序程度”,它表示数据流中乱序数据时间戳的最大差值;如果我们能确定乱序程度,那么设置对应时间长度的延迟,就可以等到所有的乱序数据了,与有序流一样也还需要设置消息时间戳提取策略,举例如下(延迟2s):
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.fromElements(new Event("Jack", 1000L, "./home"),
new Event("Tom", 2000L, "./cart"),
new Event("Jerry", 1300L, "./prod?id=10"))
.assignTimestampsAndWatermarks(WatermarkStrategy.<Event>forBoundedOutOfOrderness(Duration.ofSeconds(2))
.withTimestampAssigner(new SerializableTimestampAssigner<Event>() {
@Override
public long extractTimestamp(Event element, long recordTimestamp) {
return element.getTimestamp();
}
})
);
自定义水位线策略实际就是重写 WatermarkStrategy 的 createWatermarkGenerator 和 createTimestampAssigner 两个方法,用来设置水位线的生成策略以及时间戳的提取方式,举例如下(水位线):
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.fromElements(new Event("Jack", 1000L, "./home"),
new Event("Tom", 2000L, "./cart"),
new Event("Jerry", 1300L, "./prod?id=10"))
.assignTimestampsAndWatermarks(new WatermarkStrategy<Event>() {
@Override
public TimestampAssigner<Event> createTimestampAssigner(TimestampAssignerSupplier.Context context) {
return new TimestampAssigner<Event>() {
@Override
public long extractTimestamp(Event element, long recordTimestamp) {
return element.getTimestamp();
}
};
}
@Override
public WatermarkGenerator<Event> createWatermarkGenerator(WatermarkGeneratorSupplier.Context context) {
return new WatermarkGenerator<Event>() {
private Long maxTimestamp; //观察到的最大时间
private Long delayTime = 5000L; //延迟时间
@Override
public void onEvent(Event event, long eventTimestamp, WatermarkOutput output) {
//每来一条数据就更新一下flink服务器系统最大时间
maxTimestamp = Math.max(event.getTimestamp(), maxTimestamp);
}
@Override
public void onPeriodicEmit(WatermarkOutput output) {
//产生水位线,默认200ms一次
output.emitWatermark(new Watermark(maxTimestamp - delayTime - 1L));
}
};
}
});
这里都有一个延迟时间的概念,即flink收到的消息中的最大时间不认为是当前的时间,举个例子假如flink收到消息中的最大时间是12s,延迟时间是2s,则flink认为当前时间是10s,如果此时发送水位线也是发送的10s的水位线。这样做的原因是为了等待那些先产生但是迟到的数据。