1. 时间语义
- 事件时间 业务发生时的时间。
- 获取时间 flink中DataSource拿到数据的时间。
- 处理时间 flink开始处理业务的时间。
//指定时间语义
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
1.12版本之前,默认的时间语义是处理时间(ProcessingTime)
1.12版本之后,默认的时间语义是事件时间(EventTime)
2. 水位线
2.1 事件时间与窗口
应用场景:每隔5分钟统计一次页面访问次数(PV)
用户 | 事件 | 事件时间 | 页面 | 停留时长(分钟) |
user1 | view | 2022-05-22 08:01 | page1 | 3 |
user2 | view | 2022-05-22 08:03 | page1 | 1 |
user3 | view | 2022-05-22 08:05 | page1 | 1 |
user4 | view | 2022-05-22 08:06 | page1 | 1 |
基于事件时间的业务顺序数据(理想)
基于事件时间的乱序情况下的数据(实际)
时间乱序问题
2.2 什么是水位线
理论上“先产生的数据先被处理”,这要求我们需要保证数据到达的顺序。但是由于分布式系统中网络传输延迟的不确定性,实际应用中我们要面对的数据流往往是乱序的。在这种情况下,就不能简单地把数据自带的时间戳当作时钟了,而需要用另外的标志来表示事件时间进展,在 Flink 中把它叫作事件时间的“水位线”(Watermarks)
2.2.1 有序流中的水位线
逐个式生成水位线
周期性生成水位线
2.2.2 乱序流中的水位线
逐个式生成水位线
保证水位线的时间标记是单调递增
如果遇到迟到的数据,使用最大的时间戳。
周期性生成水位线
区间:[0,9)
2.2.3 乱序流产生的问题
水位线的目的:告知程序当前的时间,以便触发窗口的关闭操作。当水位线的时间大于窗口的关闭时间时,即触发窗口的关闭操作。
产生的问题: 迟到的数据未统计进来。
为了解决了这个问题,增加了一种规则,允许将水位线的时间调慢。
乱序流中的水位线时间调慢2秒
这里只是调慢了窗口关闭的时间,窗口的时间区间不变。
窗口区间: [8:00, 8:05) [8:05, 8:10)
水位线的特征总结:
- 水位线是插入到数据流中的一个标记,可以认为是一种特殊的数据
- 水位线的主要内容是一个时间戳,用来表示当前事件时间的进展
- 水位线是基于数据的时间戳生成
- 水位线的时间戳必须单调递增,以确保任务的事件时间一直向前推进
- 水位线可以通过设置延迟,来保证正确的处理乱序数据
- 一个水位线,表示在当前流中事件时间已经达到了时间戳t,这代表t之前的所有数据都到齐了,之后流中不会出现时间戳<=t的数据。
水位线是Flink流处理中保证结果正确的核心机制,它往往会跟窗口一起配合,完成对乱序数据的正确处理。
2.3 如何生成水位线
2.3.1 水位线生成原则
水位线一旦生成,就表示这个时间之前的数据已经全部到齐,之后再也不会出现了。如果后面出现,就会出现丢弃的情况。
如何设置水位线的延迟时间
- 个人经验
- 根据历史的迟到数据的延迟时间估算
2.3.2 水位线生成策略
在程序中实现方式:
val ds = env.readTextFile(file)
.map{x=>
val arr = x.split(",")
UserBehavior(arr(0),arr(1),arr(2),arr(3),arr(4).toInt)
}
.assignTimestampsAndWatermarks(
WatermarkStrategy.forBoundedOutOfOrderness(Duration.ofMinutes(0)) //指定水位线策略
.withTimestampAssigner(new SerializableTimestampAssigner[UserBehavior] {
override def extractTimestamp(element: UserBehavior, recordTimestamp: Long): Long = {
DateUtil.getTimestamp(element.dt) //时间戳
}
内置水位线生成策略:
(1)有序流 forMonotonousTimestamps
static <T> WatermarkStrategy<T> forMonotonousTimestamps() {
return (ctx) -> new AscendingTimestampsWatermarks<>();
}
@Public
public class AscendingTimestampsWatermarks<T> extends BoundedOutOfOrdernessWatermarks<T> {
/** Creates a new watermark generator with for ascending timestamps. */
public AscendingTimestampsWatermarks() {
super(Duration.ofMillis(0));
}
}
(2)乱序流 forBoundedOutOfOrderness
参数: 延迟时间
static <T> WatermarkStrategy<T> forBoundedOutOfOrderness(Duration maxOutOfOrderness) {
return (ctx) -> new BoundedOutOfOrdernessWatermarks<>(maxOutOfOrderness);
}
源码:
@Public
public class BoundedOutOfOrdernessWatermarks<T> implements WatermarkGenerator<T> {
private long maxTimestamp;
private final long outOfOrdernessMillis;
public BoundedOutOfOrdernessWatermarks(Duration maxOutOfOrderness) {
checkNotNull(maxOutOfOrderness, "maxOutOfOrderness");
checkArgument(!maxOutOfOrderness.isNegative(), "maxOutOfOrderness cannot be negative");
this.outOfOrdernessMillis = maxOutOfOrderness.toMillis();
// start so that our lowest watermark would be Long.MIN_VALUE.
this.maxTimestamp = Long.MIN_VALUE + outOfOrdernessMillis + 1;
}
@Override
public void onEvent(T event, long eventTimestamp, WatermarkOutput output) {
maxTimestamp = Math.max(maxTimestamp, eventTimestamp);
}
@Override
public void onPeriodicEmit(WatermarkOutput output) {
output.emitWatermark(new Watermark(maxTimestamp - outOfOrdernessMillis - 1));
}
}
2.3 3 水位线生成频率
默认200ms
//设置周期性watermark生成的间隔
env.getConfig.setAutoWatermarkInterval(500)
2.3.4 自定义水位线生成策略
.assignTimestampsAndWatermarks(
WatermarkStrategy.forGenerator[UserBehavior](context => new MyWatermarkWatermarkCreate())
.withTimestampAssigner(new SerializableTimestampAssigner[UserBehavior](){
override def extractTimestamp(element: UserBehavior, recordTimestamp: Long): Long = {
DateUtil.getTimestamp(element.dt)
}
})
)
(1)自定义周期性的生成水位线:
class MyWatermarkWatermarkCreate extends WatermarkGenerator[UserBehavior]{
val delayTime = 5000L //延迟时间
var maxTime = Long.MinValue + delayTime + 1L //最大时间戳
override def onEvent(event: UserBehavior, eventTimestamp: Long, output: WatermarkOutput): Unit = {
//每来一个事件,更新下最大的时间戳
maxTime = DateUtil.getTimestamp(event.dt).max(maxTime)
}
override def onPeriodicEmit(output: WatermarkOutput): Unit = {
//发射水位线,默认200ms一次
val time = maxTime - delayTime - 1L
println("send watermark==>" + time +" "+ DateUtil.getDateTime(time.toString))
output.emitWatermark(new Watermark(time))
}
}
(2)自定义断点式生成水位线
class MyWatermarkWatermarkCreate2 extends WatermarkGenerator[UserBehavior]{
override def onEvent(event: UserBehavior, eventTimestamp: Long, output: WatermarkOutput): Unit = {
val time = eventTimestamp - 5000L - 1L
println("send watermark==>" + time +" "+ DateUtil.getDateTime(time.toString))
new Watermark(time)
// if(event.event.equals("view")){ //当某个事件等于'view'时才生成水位线
// new Watermark(time)
// }
}
override def onPeriodicEmit(output: WatermarkOutput): Unit = {
//
}
}
2.4 水位线的传递
每个任务中都会维护上游的任务传递过来的水位线的状态。
当前任务的水位线 取决于上游任务的水位线状态中最小的值。