Flink流计算传输中⽀持多种时间概念:ProcessingTime/EventTime/IngestionTime
如果Flink算⼦使⽤的时候不做特殊设定,默认使⽤的是ProcessingTime。其中和ProcessingTime类似IngestionTime都是由系统⾃动产⽣,不同的是IngestionTime是由DataSource源产⽣⽽ProcessingTime由计算算⼦产⽣。因此以上两种时间策略都不能很好的表达在流计算中事件产⽣时间(考虑⽹络传输延迟)。
Flink中⽀持基于EventTime语义的窗⼝计算,Flink会使⽤Watermarker机制去衡量事件时间推进进度。Watermarker会做为数据流的⼀部分随着数据⽽流动。Watermarker包含有⼀个时间t,这就表明流中不会再有事件时间t’<=t的元素存在。
Watermarker(t)= Max event time seen by Procee Node - MaxAllowOrderless
Watermarker
在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’ 则该数据还可以参与窗⼝计算。
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", 9999)
.map(line=>line.split("\\s+"))
.map(ts=>(ts(0),ts(1).toLong))
.assignTimestampsAndWatermarks(new
UserDefineAssignerWithPunctuatedWatermarks)
.windowAll(TumblingEventTimeWindows.of(Time.seconds(2)))
.allowedLateness(Time.seconds(2))
.apply(new UserDefineAllWindowFucntion)
.print("输出")
env.execute("Tumbling Event Time Window Stream")
}
}
- 如果Watermarker时间t >= 窗⼝EndTime t’’ + t’ 则该数据默认情况下Flink会丢弃。当然⽤户可以将too late数据通过side out输出获取,⼀遍⽤户知道哪些迟到的数据没能加⼊正常计算。
object FlinkEventTimeTumblingWindowTooLateData {
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)
val lateTag= new OutputTag[(String,Long)]("late")
//字符 时间戳
var result=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)))
.allowedLateness(Time.seconds(2))
.sideOutputLateData(lateTag)
.apply(new UserDefineAllWindowFucntion)
result.print("正常")
result.getSideOutput(lateTag).printToErr("太迟")
env.execute("Tumbling Event Time Window Stream")
}
}