關於flink watermark的一些理解

關於flink watermark的一些理解

1 首先介紹flink中的時間概念:

flink中有三種時間概念,分別是Event time、ingestion time、processing time

從下圖可以很方便的看出來
在這裏插入圖片描述
eventTime 指的是事件本身的固有屬性,是包含在數據之中的(比如事件發生的時間戳,事件本身被生產的時間),經常用到,尤其是後面搭配window處理亂序事件

ingestionTime 指的是事件進入flink系統的時間,一般用不到

processingTime 指的是flink對事件進行處理的時間,一般指的就是processingTime

2 爲什麼需要watermark:

由於數據事件在傳輸中,難免會遇到帶寬,網絡,存儲問題或者通過kafak這種管道(同一topic的同一partition中有序)時,則會導致最終到達flink的數據是亂序的,而有些場景需要對時間的準確性有很高的要求,所以就要對亂序數據進行處理,所謂亂序,指的是事件並沒按照數據產生的時間(EventTime)到達flink,所以對亂序數據進行處理,首先需要設置flink的時間語意爲EventTime

val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment 
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
env.getConfig.setAutoWatermarkInterval(100L) //默認watermark產生間隔時間爲200毫秒,通過這個方法可以自定義設置

那麼問題來了,時間屬性被設置爲EventTime該怎麼推進數據的前進呢,比如這個場景,每隔5秒計算之中的最小值,該怎麼判斷這個5秒窗口的關閉呢,觸發窗口計算呢???,此時watermark就登場了,

watermark用來推進EventTime數據的前進,結合window用來處理亂序數據(遇到一個時間戳達到了窗口關閉時間,不應該立刻觸發窗口計算,而是等待一段時間,等遲到的數據來了再關閉窗口),

watermark是一種衡量eventTime進展的機制,可以設定延遲觸發(比如設置延時時間爲一秒,此時5秒的數據到達,因爲設置延時爲一秒,watermark=maxTimestamp-bound,watermark=5-1=4) 則認爲4秒之前的數據都已經到達了

數據流中的watermark用於表示timestamp小於watermark的數據都已經到達了,計算公式爲當前最大的時間戳減去延時

watermark的延時設置:太久收到結果的速度就會變慢,實時性不夠好,解決辦法在水位線到達之前輸出一個近似結果

太早,則會導致收到的結果不正確,一些數據沒有收集進來,解決辦法:可以通過側輸出流來解決

有兩種方式生成watermar

1 繼承AssignerWithPeriodicWatermarks

2 繼承AssignerWithPunctuatedWatermarks

兩者區別 前者定時生成watermark默認週期200毫秒按照生成watermark的方式定時插入到數據流中

後者特定事件才產生watermark,一般不常用

class MyAssigner() extends AssignerWithPeriodicWatermarks[SensorReading]{

​ //定義固定延遲爲3秒,毫秒展示

val bound:Long=3*1000L

//定義當前收到的最大的時間戳

var maxTs:Long=Long.MinValue

Override def getCurrentWatermark:Watermark={

//生成watermark的方式,watermark=當前最大的時間戳-延時,比如當前最大的時間戳爲5,延時爲一秒,watermark 爲4,則認爲4秒之前的數據已經全部到達

new Watermark(maxTs-bound)

}

Override def extractTimestamp(element:SensorReading,previousElementTimestamp:Long):Long={

maxTs=maxTs.max(element.timestamp*1000L)

Element.timestamp*1000L

}

}

但是上面實現方式過於煩碎,一般採用其簡化實現的方法,直接new實例化 new BoundedOutOfOrdernessTimestampExtractor 並且在原始數據進行一次map 或者filter操作之後立刻產生水印watermark,例子如下

val dataStream = inputStream
.map(
data => {
val dataArray = data.split(",")
SensorReading(dataArray(0).trim, dataArray(1).trim.toLong, dataArray(2).trim.toDouble)
}
)
// .assignAscendingTimestamps(.timestamp * 1000L)
.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractorSensorReading {
override def extractTimestamp(element: SensorReading): Long = element.timestamp * 1000L
})
// .assignTimestampsAndWatermarks( new MyAssigner() )
.map(data => (data.id, data.temperature))
.keyBy(
._1)
// .process( new MyProcess() )
.timeWindow(Time.seconds(10), Time.seconds(3))
.reduce((result, data) => (data._1, result._2.min(data._2))) // 統計10秒內的最低溫度值


完整代碼如下

import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.AssignerWithPeriodicWatermarks
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.watermark.Watermark
import org.apache.flink.streaming.api.windowing.time.Time
//定義傳感器樣例類
case class SensorReading(id: String, timeStamp: Long, temperature: Double)

object windowTest {
  def main(args: Array[String]): Unit = {
    val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    env.setParallelism(1)
    env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
    env.getConfig.setAutoWatermarkInterval(100L)

    val stream: DataStream[String] = env.socketTextStream("localhost", 999)

    val dataStream: DataStream[SensorReading] = stream.map(data => {
      val dataArray: Array[String] = data.split(",")
      SensorReading(dataArray(0).trim, dataArray(1).trim.toLong, dataArray(2).trim.toDouble)
    })
      .assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[SensorReading](Time.seconds(1)) {
        override def extractTimestamp(t: SensorReading): Long =t.timeStamp*1000L
      })
//      .assignTimestampsAndWatermarks(new MyAssigner())

    val minTempWindowStream: DataStream[(String, Double)] = dataStream
      .map(data => (data.id, data.temperature))
      .keyBy(0)
      .timeWindow(Time.seconds(10))
      .reduce((d1, d2) => (d1._1, d1._2.min(d2._2)))

    minTempWindowStream.print("min temp")
    dataStream.print("input data")

    env.execute("window test")
  }

  class MyAssigner() extends AssignerWithPeriodicWatermarks[SensorReading]{
    val bound:Long=10000
    var maxTs=Long.MinValue

    override def getCurrentWatermark: Watermark =new Watermark(maxTs-bound)

    override def extractTimestamp(t: SensorReading, l: Long): Long = {
      maxTs=maxTs.max(t.timeStamp)
      t.timeStamp*1000
    }
  }


}

大致就是採用滾動時間窗口結合periodic watermark來處理亂序時間

運行結果如下

在這裏插入圖片描述

可以看出上面的數據有些是亂序的,但只要在窗口範圍內都是沒有問題的

當數據的時間戳達到1547718201時前面的一個窗口關閉,即watermark爲1547718200 輸出前10秒的一個窗口的溫度最小值,左閉右開,即當前窗口不包括最右邊的節點,也就是前一個窗口不包括1547718200的數據,也就是最小值爲1547718199的溫度值而不是1547718200的溫度值,1547718200的溫度被納入到了下一個窗口

因爲上一個窗口關閉時水位線watermark爲1547718200 數據爲1547718201,又因爲是事件時間滾動窗口每隔10秒計算上一個窗口的最小值,所以下一個水位線watermark達到1547718210即數據達到1547718211時關閉上一個窗口,計算上一個窗口的溫度最小值,且當前窗口的最後一個數據不納入當前窗口(左閉右開),即數據爲1547718210的不納入計算,並且不同sensor_id 也能促進watermark的增長,因爲生成時間戳是在數據產生進行一次map或者filter之後立即產生的,在keyby之前所以,不同的id也能促進watermark的增長,代碼如下

val dataStream: DataStream[SensorReading] = stream.map(data => {
  val dataArray: Array[String] = data.split(",")
  SensorReading(dataArray(0).trim, dataArray(1).trim.toLong, dataArray(2).trim.toDouble)
})
  .assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[SensorReading](Time.seconds(1)) {
    override def extractTimestamp(t: SensorReading): Long =t.timeStamp*1000L
  })

上面是滾動窗口,接下來演示滑動窗口

只需將 .timeWindow(Time.seconds(10)) 改爲 .timeWindow(Time.seconds(10),Time.seconds(5))

每隔5秒計算前10秒的數據,需要注意的是這個爲·地方說的5秒 10秒都是EventTime,因爲之前已經全局設置過時間語意了,env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)

運行結果如下

在這裏插入圖片描述

其實和上面的滾動窗口差不多,只是這個滑動只是每隔5秒計算前10秒的數據,數據達到201,即watermark達到200 關閉前面的窗口計算最小值,不包括最右邊的節點,所以最小值爲34,接下來,水位線爲200,每隔5秒計算前10秒的數據,所以水位線需要漲到200+5=205時,即數據達到206時計算前10秒的數據,嗯,就這樣

其他一些要點:

watermark的傳遞多個分區將最小的watermark傳遞出去

keyby之後緊接着window操作 timeWindow等

assignerTimestampWithWatermark用在數據進行第一次操作(map filter 等之後)

id_1,1547718199,35.80018327300259 (id,時間戳,溫度值)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章