一、WaterMark作用
在使用 EventTime 处理 Stream 数据的时候会遇到数据乱序的问题,流处理从 Event(事 件)产生,流经 Source,再到 Operator,这中间需要一定的时间。虽然大部分情况下,传输到 Operator 的数据都是按照事件产生的时间顺序来的,但是也不排除由于网络延迟等原因而导致乱序的产生,特别是使用 Kafka 的时候,多个分区之间的数据无法保证有序。因此, 在进行 Window 计算的时候,不能无限期地等下去,必须要有个机制来保证在特定的时间后, 必须触发 Window 进行计算,这个特别的机制就是 Watermark(水位线)。Watermark 是用于 处理乱序事件的。
二、原理
在 Flink 的窗口处理过程中,如果确定全部数据到达,就可以对 Window 的所有数据做 窗口计算操作(如汇总、分组等),如果数据没有全部到达,则继续等待该窗口中的数据全 部到达才开始处理。这种情况下就需要用到水位线(WaterMarks)机制,它能够衡量数据处 理进度(表达数据到达的完整性),保证事件数据(全部)到达 Flink 系统,或者在乱序及 延迟到达时,也能够像预期一样计算出正确并且连续的结果。当任何 Event 进入到 Flink 系统时,会根据当前最大事件时间产生 Watermarks 时间戳。
如何计算WaterMark的值?
Watermark = 进入 Flink 的最大的事件时间(mxtEventTime)— 指定的延迟时间(t)
有Watermark 的 Window 是怎么触发窗口函数?
如果有窗口的停止时间等于或者小于maxEventTime – t(当时的 warkmark),那么 这个窗口被触发执行
Watermark 的使用存在三种情况:
1. 本来有序的 Stream 中的 Watermark
如果数据元素的事件时间是有序的,Watermark 时间戳会随着数据元素的事件时间按顺 序生成,此时水位线的变化和事件时间保持一直(因为既然是有序的时间,就不需要设置延 迟了,那么 t 就是 0。所以 watermark=maxtime-0 = maxtime),也就是理想状态下的水位 线。当 Watermark 时间大于 Windows 结束时间就会触发对 Windows 的数据计算,以此类推,下一个 Window 也是一样。

2.乱序事件中的 Watermark
现实情况下数据元素往往并不是按照其产生顺序接入到 Flink 系统中进行处理,而频繁出现乱序或迟到的情况,这种情况就需要使用 Watermarks 来应对。比如下图,设置延迟时 间t为2

3.并行数据流中的 Watermark
在多并行度的情况下,Watermark 会有一个对齐机制,这个对齐机制会取所有 Channel中最小的 Watermark。

三、Watermark 运用
1.有序的Watermark
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
object WaterMark 1 {
def main(args : Array[String]) : Unit = {
val env = StreamExecutionEnvironment.getExecutionEnvironment
// 使用eventTime
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
val stream = env.socketTextStream( "flink101" , 8888 )
.map(line = > {
var arr = line.split( "," )
Log(arr( 0 ).trim,arr( 1 ).trim, arr( 2 ).trim, arr( 3 ).trim, arr( 4 ).trim.toLong, arr( 5 ).trim.toLong)
}).assignAscendingTimestamps( _ .callTime) // 数据有序的升序watermark
.filter( _ .callType.equals( "success" ))
.keyBy( _ .sid)
.timeWindow(Time.seconds( 10 ), Time.seconds( 5 ))
.reduce( new MyReduceFunction(), new ReturnMaxTimeWindowFunction)
env.execute( "assignAscendingTimestampsDemo" )
}
|
2.无序的Watermark
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
object Watermark 2 {
def main(args : Array[String]) : Unit = {
val env = StreamExecutionEnvironment.getExecutionEnvironment
// 使用eventTime
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
// 周期引入watermart的设置,默认是100毫秒
env.getConfig.setAutoWatermarkInterval( 100 L)
val stream = env.socketTextStream( "flink101" , 8888 )
.map(line = > {
var arr = line.split( "," )
Log(arr( 0 ).trim,arr( 1 ).trim, arr( 2 ).trim, arr( 3 ).trim, arr( 4 ).trim.toLong, arr( 5 ).trim.toLong)
})
// 数据是乱序的,延迟时间为3秒,周期性watermark
/**
* 第一种实现
*/
val ds = stream.assignTimestampsAndWatermarks( new BoundedOutOfOrdernessTimestampExtractor[Log](Time.seconds( 3 )){
override def extractTimestamp(element : Log) : Long = {
element.callTime // EventTime
}
})
/**
* 第二种实现
*/
val ds 2 = stream.assignTimestampsAndWatermarks( new AssignerWithPeriodicWatermarks[Log] {
var maxEventTime : Long =_
override def getCurrentWatermark : Watermark = {
// 周期性生成watermark
new Watermark(maxEventTime - 3000 L)
}
// 设定EventTime是哪个属性
override def extractTimestamp(element : Log, previousElementTimestamp : Long) : Long = {
maxEventTime = maxEventTime.max(element.callTime)
element.callTime
}
})
env.execute( "assignTimestampsAndWatermarksDemo" )
}
|
3.With Punctuated(间断性的) Watermark
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
val env = StreamExecutionEnvironment.getExecutionEnvironment
// 使用eventTime
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
//读取文件数据
val data = env.socketTextStream( "flink101" , 8888 )
.map(line = >{
var arr = line.split( "," ) new
StationLog(arr( 0 ).trim,arr( 1 ).trim,arr( 2 ).trim,arr( 3 ).trim,arr( 4 ).trim.toLong,arr( 5 ).trim.to Long)
})
// 生成watermark
data.assignTimestampsAndWatermarks(
new MyCustomerPunctuatedWatermarks( 3000 L)) //自定义延迟
}
class MyCustomerPunctuatedWatermarks(delay : Long) extends AssignerWithPunctuatedWatermarks[StationLog]{
var maxTime : Long = 0
override def checkAndGetNextWatermark(element : StationLog, extractedTimestamp : Long) : Watermark = {
if (element.sid.equals( "station_1" )){ //当ID为:station_1 才生成水位线 maxTime =maxTime.max(extractedTimestamp)
new Watermark(maxTime-delay)
} else {
return null //其他情况下不返回水位线
} }
override def extractTimestamp(element : StationLog, previousElementTimestamp : Long) : Long = {
element.callTime //抽取EventTime的值 }
}
|