在流式计算中.时间是一个影响计算结果非常重要的因素! (窗口函数,定时器等)
Flink 可以根据不同的时间概念处理数据。
System.currentTimeMillis()
是指执行相应操作的机器系统时间(也称为纪元时间,例如 Java 的时间)。是现实世界的时间,时间是单调递增的 env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime); //事件时间
env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime); // 处理时间
env.setStreamTimeCharacteristic(TimeCharacteristic.IngestionTime); // 算子到达时间 其实就是处理时间
上述代码在1.12 版本之前, 已TimeCharacteristic.ProcessingTime
作为默认的时间语义,也可以用上述代码设置时间语义;
1.12 版本之后,flink 以 TimeCharacteristic.EventTime
作为时间语义 ,并且Deprecated上面的代码 在使用需要指定时间语义的API 时,在显示的指定对应的时间语义;
keyedStream.window(SlidingEventTimeWindows.of(Time.seconds(5),Time.seconds(2)));
keyedStream.window(SlidingProcessingTimeWindows.of(Time.seconds(5),Time.seconds(2)));
keyedStream.window(TumblingEventTimeWindows.of(Time.seconds(5)));
keyedStream.window(TumblingProcessingTimeWindows.of(Time.seconds(2)));
也可以禁用 event time 语义
/**
* Sets the interval of the automatic watermark emission. Watermarks are used throughout the
* streaming system to keep track of the progress of time. They are used, for example, for time
* based windowing.
*
* Setting an interval of {@code 0} will disable periodic watermark emission.
*
* @param interval The interval between watermarks in milliseconds.
*/
@PublicEvolving
public ExecutionConfig setAutoWatermarkInterval(long interval) {
Preconditions.checkArgument(interval >= 0, "Auto watermark interval must not be negative.");
this.autoWatermarkInterval = interval;
return this;
}
上面说到1.12 版本之后,flink 以 TimeCharacteristic.EventTime
作为时间语义,Flink 收到数据中的事件时间有可能不是有序的,这就导致会收到迟到的数据,其事件时间是属于过去的窗口;
为了能够基于事件时间进行计算,Flink引入了Watermark的概念
watermark 的产生源头:
一般是来源于source 算子
在watermark 产生的源头算子中,subTask 程序会用一个定时器,去周期性的检查收到的数据的时间的最大值,如果超过了之前记录的最大值,就把这个最大值更新为watermark,并下游算子广播(通过API设置数据中那个字段作为事件时间)
中间算子收到上游算子广播的watermark ,其算子内部也会有一个定时器去定时的检测收到的所有的上游算子的watermark ,并计算其中最小值作为当前算子的watermark,并下游算子广播
注:当其中一个所有算子,不在更新watermark 怎么处理? flink 提供一个机制设置watermark的idletime,意思就是如果在idletime时间内没有收到上游算子广播的watermark,则会自动的往前面推进watermark
Flink 1.12 版本之后,watermark 的生产策略是固定频率周期性的产生
新版API watermark生成策略
简单的测试并发度为1下的算子watermark更新情况
public class _01_Watermark {
public static void main(String[] args) throws Exception {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 获取环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStreamSource<String> dataStreamSource = env.socketTextStream("192.168.141.141", 9000);
WatermarkStrategy<String> watermarkStrategy = WatermarkStrategy.<String>forMonotonousTimestamps( )
.withTimestampAssigner(new SerializableTimestampAssigner<String>() {
@Override
public long extractTimestamp(String element, long recordTimestamp) {
String s = element.split(",")[0];
long time = 0;
try {
time = simpleDateFormat.parse(s).getTime(); //解析字符串值转long 作为时间戳
} catch (ParseException e) {
}
return time;
}
});
SingleOutputStreamOperator<String> streamOperator = dataStreamSource.assignTimestampsAndWatermarks(watermarkStrategy);
SingleOutputStreamOperator<String> processed = streamOperator.process(new ProcessFunction<String, String>() {
@Override
public void processElement(String value, ProcessFunction<String, String>.Context ctx, Collector<String> out) throws Exception {
long currentWatermark = ctx.timerService().currentWatermark();
long processingTime = ctx.timerService().currentProcessingTime();
System.out.println("currentWatermark:" + simpleDateFormat.format(new Date(currentWatermark)));
System.out.println("currentWatermark:" + currentWatermark); //打印watermark
System.out.println("processingTime:" + simpleDateFormat.format(new Date(processingTime)));
out.collect(value);
}
});
processed.print();
env.execute ();
}
}
还是以上面示例为例讲解
主要是 WatermarkStrategy.forMonotonousTimestamps( )和 WatermarkStrategy.forBoundedOutOfOrderness
两者对应的类是都是一样的 均为BoundedOutOfOrdernessWatermarks
,只是 WatermarkStrategy.forMonotonousTimestamps( )对应BoundedOutOfOrdernessWatermarks
中的参数 outOfOrdernessMillis = 0,后续主要是看BoundedOutOfOrdernessWatermarks
类即可
@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();
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));
}
}