1、概述
flink中比较重要的是时间和状态,学习flink的过程中对水位线的理解一直模糊,经过一段时间的消化,在此总结总结。本文主要把水位线是什么,怎么来的,有什么用描述清楚。
2、不太好理解的水位线
有些人喜欢把水位线叫成水印,不管是水印还是水位线,中文翻译过来一点都不贴切我们的生活,比较抽象,让人难得理解。在我们生活中水位线类似家中挂在墙上的一个挂钟,类似我们的手表。下面来聊聊如下的话题:
1,到底是如何产生。
2,既然是一个挂钟,钟表有哪些特点呢,钟表每隔1s秒针往前走一小步,时间是不是越来越大,这些特点水位线是不是也有呢。
3,挂钟有什么用处啊?晚上看看手表发现12点,我们肯定自我暗示:“应该睡觉了”,通过时间让我们知道什么时间该干什么事情。
3、什么叫水位线
3.1、水位线的定义
水位线就是一个逻辑时钟,为什么叫逻辑时钟?正常时间是有cpu产生的,周期而固定的往前走,但是我们这个时钟的时间是程序员计算出来,根据"事件时间"动态计算出来(至于什么是时间事件,有什么使用场景这里就不讲了),如某一时刻计算的结果为x,x值为2022-10-10 10:10:10对应的时间戳为1665367810000,x的值随着事件时间的变大而变大,可能的结果为x,x+1,x+2,x+3,x+4 … 连续的越来越大的时间戳是不是类似钟表每隔1s往前走一步呢。
3.2、水位线(逻辑时钟)的组成
水位线由一串连续的时间戳组成,越来越大,每个时间戳都是根据事件时间动态计算出来的。时钟也是由一连续的时间组成,也是越来越大,如2022-10-10 10:10:10,2022-10-10 10:10:11,2022-10-10 10:10:12,2022-10-10 10:10:13 。。。等,水位线就是类似生活中的时钟,所以我把这个水位线称为逻辑时钟,逻辑时钟就是水位线,水印机制。
3.3、逻辑时钟当前时间
类似时钟的当前时间,此处此刻为几点几分几秒,这个当前时间比较重要,窗口的闭合,定时任务的触发都是根据当前时间来判断的。
当前值特点:越来越大,流刚刚产生的时候插入负无穷大值,结束是插入正无穷大的值。
个人觉得这个当前值类似一个指针类型的变量,他的指向是不停的变化的(个人理解)。
3.4、当前时间的计算公式
时钟的"当前时间"对应一个具体的时间戳。时钟的当前值xxx = 事件时间 - 最大延迟时间 - 1毫秒。
3.5、来一个案例
案例描述:从socket读取数据,并打印当前水位的具体值。
package com.deepexi.sql;
import org.apache.flink.api.common.eventtime.SerializableTimestampAssigner;
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.api.common.typeinfo.Types;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.KeyedProcessFunction;
import org.apache.flink.util.Collector;
import java.time.Duration;
public class ExampleTest {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
env
//从socket读取数据
.socketTextStream("192.168.117.211", 9999)
.map(r -> Tuple2.of(r.split(" ")[0], Long.parseLong(r.split(" ")[1])))
.returns(Types.TUPLE(Types.STRING, Types.LONG))
.assignTimestampsAndWatermarks(
//5s延迟时间
WatermarkStrategy.<Tuple2<String, Long>>forBoundedOutOfOrderness(Duration.ofSeconds(5))
.withTimestampAssigner(new SerializableTimestampAssigner<Tuple2<String, Long>>() {
@Override
public long extractTimestamp(Tuple2<String, Long> element, long recordTimestamp) {
//提取事件时间
return element.f1;
}
})
)
//分流
.keyBy(r -> r.f0)
.process(new KeyedProcessFunction<String, Tuple2<String, Long>, String>() {
@Override
public void processElement(Tuple2<String, Long> value, Context ctx, Collector<String> out) throws Exception {
out.collect("当前的水位线是:" + ctx.timerService().currentWatermark());
}
})
.print();
env.execute();
}
}
nc -lk 9999 开启socket服务,监听9999端口
命令行输入:a 1000
[root@localhost ~]# nc -lk 9999 a 1000
idea控制台打印
当前的水位线是:-9223372036854775808 //-9223372036854775808是一个无穷大的数字
命令行输入:a 2000
idea控制台打印:
当前的水位线是:-4001 //当前水位线的值 = 事件时间 - 最大延迟时间 -1 = 1000 - 5000 -1 = -4000
为什么用1000- 5000 -1而用2000 - 5000 -1? flink会周期往流中插入水位线,水位线也是流中的一个元素,还是看下图理解吧。
命令行输入:a 3000
idea控制台打印:当前的水位线是:-3001 //2000 - 5000 -1 = -2000
命令行输入:a 10000
idea控制台打印:当前的水位线是:-2001 //3000 - 5000 -1 = -2000
命令行输入:a 1000
idea控制台打印:当前的水位线是:4999 //10000 - 5000 -1 = 4999
命令行输入:a 1000
idea控制台打印:当前的水位线是:4999 //10000 - 5000 -1 = 4999
命令行输入:a 2000
idea控制台打印:当前的水位线是:4999 //10000 - 5000 -1 = 4999
通过控制台的打印结果发现水位线的和钟表一样,值总是越来越大的,随着事件时间的变化而变化,但是不会变小,也可能会停止某一刻,如输入a 1000后在输入a 1000,a 2000水位线的值始终是4999。
整个打印过程
命令行窗口:
[root@master ~]# nc -lk 9999
a 1000
a 2000
a 3000
a 10000
a 1000
a 1000
a 2000
idea打印:
当前的水位线是:-9223372036854775808
当前的水位线是:-4001
当前的水位线是:-3001
当前的水位线是:-2001
当前的水位线是:4999
当前的水位线是:4999
当前的水位线是:4999
4、如何产生的
水位线本质就是一个时间戳,这个时间戳是程序员根据事件时间动态计算出来,直接来一个案例吧。
案例1
自定义水位线的产生逻辑,实现WatermarkStrategy接口,flink会每隔200毫秒的调用onPeriodicEmit方法。
public class ExampleTest2 {