时间处理
Flink中支持三种时间:
- Event Time: 数据生成时间
- Ingestion Time: 数据进入摄入时间
- Processing Time: Flink机器时间
Flink支持基于EventTime的时间处理,并且支持Watermarks过滤迟到数据。
基于EventTime的时间处理能保证事件发生的顺序,因此这种处理方式相对来说会有更多延时和更大的性能开销。
设置使用哪种时间
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
env.setStreamTimeCharacteristic(TimeCharacteristic.IngestionTime);
env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime);
在1.12之后setStreamTimeCharacteristic是过期函数,而是在使用窗口的时候可以选择使用哪种时间:
.window(TumblingEventTimeWindows.of(Time.minutes(10)))
.window(SlidingEventTimeWindows.of(Time.minutes(10), Time.minutes(5)))
.window(TumblingProcessingTimeWindows.of(Time.minutes(10)))
.window(SlidingProcessingTimeWindows.of(Time.minutes(10), Time.minutes(5)))
Watermarks
对于数据流,数据不一定按照事件实际发生的顺序处理,所以需要规定多少时间内的数据是有效数据,多久以前的数据是延迟数据不被计算,Flink就是是通过Watermarks实现的。
Watermarks就是时间线,在它之前的数据被认为是过期数据,这之后的数据都是合法数据。
Fink中设置maxOutOfOrderness,表示最大忍受延迟时间,并默认通过下面公式计算Watermarks:
Watermarks = max(EventTime) - maxOutOfOrderness
如何理解Watermarks
- 比如使用滑动窗口,窗口长度是10分钟,每5分钟滑动一次窗口,maxOutOfOrderness为10分钟,使用Append模式:
可以看一下随着数据不断进来,是如何输出结果的,这里我们使用Flink默认的触发计算Watermarks的方式,即当最大的EventTime进入下一个窗口时触发Watermarks计算:
| 数据 | Max EventTime | Watermarks | 结果 | 解释 |
|---|---|---|---|---|
| 12:07 dog | 12:07 | 11:57 | / | 生成11:55之前窗口的数据,没有数据 |
| 12:08 owl | 12:08 | 11:58 | / | 没有进入新的窗口 |
| 12:14 dog | 12:14 | 12:04 | / | 生成12:00之前窗口的数据,没有数据 |
| 12:09 cat | 12:14 | 12:04 | / | 没有进入新的窗口 |
| 12:15 cat | 12:15 | 12:05 | / | 大于12:15才会进入新的窗口 |
| 12:08 dog | 12:15 | 12:05 | / | 没有进入新的窗口,在12:21去计算结果的时候,Watermark还没有更新 |
| 12:13 owl | 12:15 | 12:05 | / | 没有进入新的窗口 |
| 12:21 owl | 12:21 | 12:11 | 12:00-12:10 (dog 2),(owl 1),(cat 1) | 进入新的窗口,生成12:00-12:10前的数据 |
| 12:04 donkey | 12:21 | 12:11 | / | 没有进入新的窗口,过期数据 |
| 12:26 owl | 12:26 | 12:16 | 12:05-12:15 (dog 3),(owl 2),(cat 2) | 进入新的窗口,生成12:05-12:15前的数据 |
| 12:17 owl | 12:26 | 12:16 | / | 没有进入新的窗口 |
| 12:09 cat | 12:26 | 12:16 | / | 没有进入新的窗口,过期数据 |
- 比如使用滑动窗口,窗口长度是10分钟,每5分钟滑动一次窗口,maxOutOfOrderness为10分钟,使用Update模式:
可以看一下随着数据不断进来,是如何输出结果的,这里我们使用Flink默认的触发计算Watermarks的方式,即当最大的EventTime进入下一个窗口时触发Watermarks计算:
| 数据 | Max EventTime | Watermarks | 有效数据范围(上一个watermark-maxEventTime) | 需要更新的窗口 | 结果 | 解释 |
|---|---|---|---|---|---|---|
| 12:07 dog | 12:07 | 11:57 | -12:07 | 11:50-12:00,11:55-12:05,12:00-12:10,12:05-12:15 | / | 没有进入新的窗口 |
| 12:08 owl | 12:08 | 11:58 | 11:57-12:08 | 11:50-12:00,11:55-12:05,12:00-12:10,12:05-12:15 | / | 没有进入新的窗口 |
| 12:14 dog | 12:14 | 12:04 | 11:58-12:14 | 11:50-12:00,11:55-12:05,12:00-12:10,12:05-12:15,12:10-12:20 | [12:00-12:10 (dog 1),(owl 1)],[12:05-12:15 (dog 1),(owl 1)] | 生成数据 |
| 12:09 cat | 12:14 | 12:04 | 12:04-12:14 | 11:55-12:05,12:00-12:10,12:05-12:15,12:10-12:20 | / | 没有进入新的窗口 |
| 12:15 cat | 12:15 | 12:05 | 12:04-12:15 | 12:00-12:10,12:05-12:15,12:10-12:20,12:15-12:25 | [12:00-12:10 (cat 1)],[12:05-12:15 (dog 2),(cat 1)],[12:10-12:20 (dog 1)] | 生成数据,并且只更新有变化的数据,自己不被统计进去 |
| 12:08 dog | 12:15 | 12:05 | 12:05-12:15 | 11:55-12:05,12:00-12:10,12:05-12:15,12:10-12:20 | / | 没有进入新的窗口 |
| 12:13 owl | 12:15 | 12:05 | 12:05-12:15 | 11:55-12:05,12:00-12:10,12:05-12:15,12:10-12:20 | / | 没有进入新的窗口 |
| 12:21 owl | 12:21 | 12:11 | 12:05-12:21 | 12:00-12:10,12:05-12:15,12:10-12:20,12:15-12:25,12:20-12:30 | [12:00-12:10 (dog 2)],[12:05-12:15 (dog 3),(owl 2),(cat 2)],[12:10-12:20 (owl 1),(cat 1)] | 生成数据,并且只更新有变化的数据,自己不被统计进去 |
| 12:04 donkey | 12:21 | 12:11 | 12:11-12:21 | / | / | 没有进入新的窗口,过期数据 |
| 12:17 owl | 12:21 | 12:11 | 12:11-12:21 | 12:00-12:10,12:05-12:15,12:10-12:20,12:15-12:25,12:20-12:30 | / | 没有进入新的窗口 |
| 12:26 owl | 12:26 | 12:16 | 12:11-12:26 | 12:05-12:15,12:10-12:20,12:15-12:25,12:20-12:30,12:25-12:35 | [12:10-12:20 (owl 2)],[12:15-12:25 (owl 2)] | 生成数据,并且只更新有变化的数据,自己不被统计进去 |
代码设置watermarks
在Flink中watermarks可以在数据源上定义也可以在非数据源上定义。
推荐使用在数据源上定义,因为这种方式可以利用数据源的分区等信息更加精确跟踪watermarks,但是这种方式需要数据源接口支持,比如Kafka source。
Kafka source with watermarks
FlinkKafkaConsumer<MyType> kafkaSource = new FlinkKafkaConsumer<>("myTopic", schema, props);
kafkaSource.assignTimestampsAndWatermarks(
WatermarkStrategy
//为乱序事件序列设置watermarks
.forBoundedOutOfOrderness(Duration.ofSeconds(20))
//为时序单调递增数据设置watermarks
//.forMonotonousTimestamps()
//optional kafka中可以直接从metadata中获取timestamp
.withTimestampAssigner((event, timestamp) -> event.getEventTime())
//optional 某个分区没有一段时间数据时,标记为空闲,防止出错
.withIdleness(Duration.ofMinutes(1)));
DataStream<MyType> stream = env.addSource(kafkaSource);
非数据源定义watermarks
DataStream<MyEvent> withTimestampsAndWatermarks = stream
.filter( event -> event.severity() == WARNING )
.assignTimestampsAndWatermarks(
WatermarkStrategy
.forBoundedOutOfOrderness(Duration.ofSeconds(20))
.withTimestampAssigner((event, timestamp) -> event.getEventTime()));
WatermarkStrategy
1.11版本之后:
- forBoundedOutOfOrderness: 为乱序事件序列设置watermarks
- forMonotonousTimestamps: 为时序单调递增数据设置watermarks
- forGenerator: 自定义watermarks生成器
public class CustomGenerator implements WatermarkGenerator<WatermarksTest.Animal> {
private final long maxOutOfOrderness = 3500; // 3.5 seconds
private long currentMaxTimestamp;
@Override
public void onEvent(WatermarksTest.Animal event, long eventTimestamp, WatermarkOutput output) {
currentMaxTimestamp = Math.max(currentMaxTimestamp, eventTimestamp);
}
@Override
public void onPeriodicEmit(WatermarkOutput output) {
// emit the watermark as current highest timestamp minus the out-of-orderness bound
output.emitWatermark(new Watermark(currentMaxTimestamp - maxOutOfOrderness - 1));
}
}
public class CustomWatermarkStrategy implements WatermarkStrategy<WatermarksTest.Animal> {
@Override
public WatermarkGenerator<WatermarksTest.Animal> createWatermarkGenerator(WatermarkGeneratorSupplier.Context context) {
return new CustomGenerator();
}
}
WatermarkStrategy.forGenerator(new CustomWatermarkStrategy()
.withTimestampAssigner((event, timestamp) -> event.getEventTime())
.withIdleness(Duration.ofMinutes(1)));
1.11版本之前:
env.setstreamSTimeCharacteristic(TimeCharacteristic.EventTime);
dataStream.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessGenerator())
- Periodic: 基于Event time生成WaterMark
public class BoundedOutOfOrdernessGenerator extends AssignerWithPeriodicWatermarks<MyEvent>{
private final long maxOutOfOrderness = 3500;
private long currentMaxTimestamp;
@Override
public long extractTimestamp(MyEvent element, long previousElementTimestamp){
long timestamp = element.getCtreaiontime();
return Math.max(timestamp, currentMaxTimestamp);
}
@Override
public Watermark getCurrentWatermark(){
return new Watermark(currentMaxTimestamp - maxOutOfOrderness);
}
}
- Punctuated: 基于指定的触发逻辑生成WaterMark
public class PunctuatedAssigner extends AssignerWithPunctuatedWatermarks<MyEvent>{
@Override
public long extractTimestamp(MyEvent element, long previousElementTimestamp){
return element.getCreationTime();
}
@Override
public Watermark checkAndGetNextWatermark(MyEvent lastElement, long extractedTimestamp){
return lastElement.hasWatermarkMarker() ? new Watermark(extractedTimestamp) : null;
}
}