EventTime Window
Flink流计算传输中支持多种时间概念:ProcessingTime
/EventTime
/IngestionTime
。
处理时间(Processing Time)
:处理时间是指执行相应操作的机器的系统时间。
当流程序在处理时间上运行时,所有基于时间的操作(如时间窗口)将使用运行各自操作符的机器的系统时间。
处理时间是最简单的时间概念,不需要流和机器之间的协调。它提供最佳性能和最低延迟。但是,在分布式和异步环境中,处理时间不提供确定性,因为它很容易受到记录到达系统的速度,记录在系统内部操作符之间流动的速度以及中断的影响。摄入时间(Ingestion time)
:摄入时间是事件进入Flink的时间。
在源操作符中,每个记录以时间戳的形式获取源的当前时间,基于时间的操作(如时间窗口)引用该时间戳。
从概念上讲,摄入时间介于事件时间和处理时间之间。与事件时间相比,摄入时间程序不能处理任何无序事件或延迟数据,但程序不必指定如何生成Watermarks,因为在内部,它自动进行时间戳分配和自动Watermarks生成。事件时间(Event Time)
:事件时间是每个单独事件在其生成设备上发生的时间。
这个时间通常在记录输入Flink之前嵌入到记录中,并且可以从每个记录中提取事件时间戳。在事件时间中,时间的进展取决于数据,而不是任何挂钟。
事件时间程序必须指定如何生成事件时间Watermarks,这是表示事件时间进度的机制。
如果Flink在使用的时候不做特殊设定,默认使用的是ProcessingTime。其中和ProcessingTime类似的IngestionTime都是由系统自动产生,不同的是IngestionTime是由DataSource源产生的,而ProcessingTime由计算算子产生。因此以上两种时间策略都不能很好的表达在流计算中时间产生时间(考虑网络传输延迟)。
event time和watermarks
设置时间特征为事件时间的流处理器需要一种方法来衡量事件时间的进度,Flink衡量event time 进度的机制是watermarks,watermark 带有一个时间戳,作为数据流的一部分随数据流流动,Watermark(t) 表示event time 小于等于 t 的都已经到达,watermarks可以解决实时系统中最常见的问题:乱序与延迟。
Watermarker(t)= Max event time seen by Procee Node - MaxAllowOrderless
在Flink中常⻅的⽔位线的计算⽅式:
- 固定频次计算⽔位线(推荐)
class UserDefineAssignerWithPeriodicWatermarks extends
AssignerWithPeriodicWatermarks[(String,Long)] {
var maxAllowOrderness=2000L
var maxSeenEventTime= 0L //不可以取Long.MinValue
var sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
//系统定期的调⽤ 计算当前的⽔位线的值
override def getCurrentWatermark: Watermark = {
new Watermark(maxSeenEventTime-maxAllowOrderness)
}
//更新⽔位线的值,同时提取EventTime
override def extractTimestamp(element: (String, Long), previousElementTimestamp:
Long): Long = {
//始终将最⼤的时间返回
maxSeenEventTime=Math.max(maxSeenEventTime,element._2)
println("ET:"+(element._1,sdf.format(element._2))+"
WM:"+sdf.format(maxSeenEventTime-maxAllowOrderness))
element._2
}
}
- Per Event 计算⽔位线 (不推荐)
class UserDefineAssignerWithPunctuatedWatermarks extends
AssignerWithPunctuatedWatermarks[(String,Long)] {
var maxAllowOrderness=2000L
var maxSeenEventTime= Long.MinValue
var sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
//每接收⼀条记录系统计算⼀次
override def checkAndGetNextWatermark(lastElement: (String, Long),
extractedTimestamp: Long): Watermark = {
maxSeenEventTime=Math.max(maxSeenEventTime,lastElement._2)
println("ET:"+(lastElement._1,sdf.format(lastElement._2))+"
WM:"+sdf.format(maxSeenEventTime-maxAllowOrderness))
new Watermark(maxSeenEventTime-maxAllowOrderness)
}
override def extractTimestamp(element: (String, Long), previousElementTimestamp:
Long): Long = {
//始终将最⼤的时间返回
element._2
}
}
测试案例
object FlinkEventTimeTumblingWindow {
def main(args: Array[String]): Unit = {
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setParallelism(1)//⽅便测试将并⾏度设置为 1
//默认时间特性是ProcessingTime,需要设置为EventTime
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
//设置定期调⽤⽔位线频次 1s
// env.getConfig.setAutoWatermarkInterval(1000)
//字符 时间戳
env.socketTextStream("CentOS", 9999)
.map(line=>line.split("\\s+"))
.map(ts=>(ts(0),ts(1).toLong))
.assignTimestampsAndWatermarks(new
UserDefineAssignerWithPunctuatedWatermarks)
.windowAll(TumblingEventTimeWindows.of(Time.seconds(2)))
.apply(new UserDefineAllWindowFucntion)
.print("输出")
env.execute("Tumbling Event Time Window Stream")
}
}
注意:当流中存在多个Watermarker的时候,取最小值作为水位线。
迟到数据
在Flink中,⽔位线⼀旦没过窗⼝的EndTime,这个时候如果还有数据落⼊到已经被⽔位线淹没的窗⼝,则定义该数据为迟到的数据。这些数据在Spark是没法进⾏任何处理的。在Flink中⽤户可以定义窗⼝元素的迟到时间t’。
- 如果Watermarker时间t < 窗⼝EndTime t’’ + t’ 则该数据还可以参与窗⼝计算。(可以理解为endTime会延迟t’,如果水位线没有超过窗⼝EndTime t’’ + t’ ,则还可以参与窗口计算)
object FlinkEventTimeTumblingWindowLateData {
def main(args: Array[String]): Unit = {
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setParallelism(1)//⽅便测试将并⾏度设置为 1
//默认时间特性是ProcessingTime,需要设置为EventTime
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
//设置定期调⽤⽔位线频次 1s
// env.getConfig.setAutoWatermarkInterval(1000)
//字符 时间戳
env.socketTextStream("CentOS"