文章目录


1.时间语义概述

对于一台机器而言,“时间”自然就是指系统时间。但我们知道,Flink 是一个分布式处理系统。分布式架构最大的特点,就是节点彼此独立、互不影响,这带来了更高的吞吐量和容错性;但有利必有弊,最大的问题也来源于此。

Flink_Time_时间语义_水平线_数据

在事件发生之后,生成的数据被收集起来,首先进入分布式消息队列,然后被 Flink 系统中的 Source 算子读取消费,进而向下游的转换算子(窗口算子)传递,最终由窗口算子进行计算处理。

  1. 事件时间Event Time: 事件时间,是指每个事件在对应的设备上发生的时间,也就是数据生成的时间。
  2. 处理时间Process Time : 处理时间的概念非常简单,就是指执行处理操作的机器的系统时间。

2.水平线 Watermark

2.1 Watermark 介绍

流处理从事件产生,到流经 source,再到 operator,中间是有一个过程和时间的,由于网络、分布式等原因,可能导致乱序或迟到的产生。所谓乱序,就是指 Flink 接收到的事件的先后顺序不是严格按照事件的 Event Time 顺序排列的。

水平线简单理解就是发生事件的时间的一个区域

Watermark 是用于处理乱序事件的,通常用Watermark 机制结合 window 来正确处理乱序事件;

[比如]

假如我们设置10s的时间窗口(window),那么0-10s,10-20s都是一个窗口,以0-10s为例,0位start-time,10为end-time。假如有4个数据的event-time分别是8(A),12.5(B),9(C ),13.5(D),我们设置Watermarks为当前所有到达数据event-time的最大值减去end-time,即3.5秒,也就是说对于迟到的数据,我们只等你3.5秒。

当A到达的时候,Watermarks为max{8}-3.5=8-3.5 = 4.5 < 10,不会触发计算
当B到达的时候,Watermarks为max(12.5,8)-3.5=12.5-3.5 = 9 < 10,不会触发计算
当C到达的时候,Watermarks为max(12.5,8,9)-3.5=12.5-3.5 = 9 < 10,不会触发计算
当D到达的时候,Watermarks为max(13.5,12.5,8,9)-3.5=13.5-3.5 = 10 = 10,触发计算
触发计算的时候,会将ABC(因为他们都小于10)都计算进去

通过上面这种方式,我们就将迟到的C计算进去了

这里的延迟3.5s是我们假设一个数据到达的时候,比他早3.5s的数据肯定也都到达了,这个是需要根据经验推算的,假如D到达以后有到达了一个Eevent-time=6(E),但是由于0~10的时间窗口已经开始计算了,所以E就丢了。

从这里上面E的丢失说明,水位线也不是万能的,但是如果根据我们自己的生产经验+侧道输出等方案,可以做到数据不丢失。

2.2 Watermark 特点

  1. 水位线是插入到数据流中的一个标记,可以认为是一个特殊的数据
  2. 水位线主要的内容是一个时间戳,用来表示当前事件时间的进展
  3. 水位线是基于数据的时间戳生成的
  4. 水位线的时间戳必须单调递增,以确保任务的事件时间时钟一直向前推进
  5. 水位线可以通过设置延迟,来保证正确处理乱序数据
  6. 一个水位线 Watermark(t),表示在当前流中事件时间已经达到了时间戳 t, 这代表 t 之前的所有数据都到齐了,之后流中不会出现时间戳 t’ ≤ t 的数据

水位线是 Flink 流处理中保证结果正确性的核心机制,它往往会跟​​窗口​​一起配合,完成对乱序数据的正确处理。

2.3 如何生成水位线

public SingleOutputStreamOperator<T> assignTimestampsAndWatermarks(
WatermarkStrategy<T> watermarkStrategy)
DataStream<Event> stream = env.addSource(new ClickSource());
DataStream<Event> withTimestampsAndWatermarks =
stream.assignTimestampsAndWatermarks(<watermark strategy>);

.assignTimestampsAndWatermarks()方法需要传入一个 WatermarkStrategy 作为参数,这就是 所 谓 的“ 水 位 线 生 成 策 略 ” 。 WatermarkStrategy 中 包 含 了 一 个 “ 时 间 戳 分 配器”TimestampAssigner 和一个“水位线生成器”WatermarkGenerator。

  1. TimestampAssigner:主要负责从流中数据元素的某个字段中提取时间戳,并分配给元素。时间戳的分配是生成水位线的基础。
  2. WatermarkGenerator:主要负责按照既定的方式,基于时间戳生成水位线。在WatermarkGenerator 接口中,主要又有两个方法:onEvent()和 onPeriodicEmit()。
  3. onEvent:每个事件(数据)到来都会调用的方法,它的参数有当前事件、时间戳,以及允许发出水位线的一个 WatermarkOutput,可以基于事件做各种操作
  4. onPeriodicEmit:周期性调用的方法,可以由 WatermarkOutput 发出水位线。周期时间为处理时间,可以调用环境配置的.setAutoWatermarkInterval()方法来设置,默认为200ms。

2.4 有序流

.assignTimestampsAndWatermarks(WatermarkStrategy.<Event>forMonotonousTimestamps()
.withTimestampAssigner(new SerializableTimestampAssigner<Event>() {
@Override
public long extractTimestamp(Event event, long l) {
return event.timestamp;
}
})
);

forMonotonousTimestamps

2.5 无序流

.assignTimestampsAndWatermarks(WatermarkStrategy.<Event>forBoundedOutOfOrderness(Duration.ofSeconds(2))
.withTimestampAssigner(new SerializableTimestampAssigner<Event>() {
@Override
public long extractTimestamp(Event event, long l) {
return event.timestamp;
}
})
);

forBoundedOutOfOrderness(Duration.ofSeconds(2)

2.6 完整代码

public class WatermarkTest {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment environment = StreamExecutionEnvironment.getExecutionEnvironment();
environment.setParallelism(1);

environment.fromElements(
new Event("1", "1", 1L),
new Event("2", "2", 1L),
new Event("3", "3", 1L)
)
// 有序流
.assignTimestampsAndWatermarks(WatermarkStrategy.<Event>forMonotonousTimestamps()
.withTimestampAssigner(new SerializableTimestampAssigner<Event>() {
@Override
public long extractTimestamp(Event event, long l) {
return event.timestamp;
}
})
);
// 乱序流
environment.fromElements(
new Event("1", "1", 1L),
new Event("2", "2", 1L),
new Event("3", "3", 1L)
)
.assignTimestampsAndWatermarks(WatermarkStrategy.<Event>forBoundedOutOfOrderness(Duration.ofSeconds(2))
.withTimestampAssigner(new SerializableTimestampAssigner<Event>() {
@Override
public long extractTimestamp(Event event, long l) {
return event.timestamp;
}
})
);
// 自定义水平线
environment.execute();
}
}

2.7 Watermark 总结

水位线在事件时间的世界里面,承担了时钟的角色。也就是说在事件时间的流中,水位线是唯一的时间尺度。如果想要知道现在几点,就要看水位线的大小。后面讲到的窗口的闭合,以及定时器的触发都要通过判断水位线的大小来决定是否触发。
水位线是一种特殊的事件,由程序员通过编程插入的数据流里面,然后跟随数据流向下游流动。

水位线的默认计算公式:水位线 = 观察到的最大事件时间 – 最大延迟时间 – 1 毫秒。