flink EventTime中watermark詳解

window對數據的操作

flink在對流數據進行窗口操作時,當eventtime在過了窗口時間後會進行關窗操作。然後在實際生產環境中,由於網絡等各種各樣的原因可能會出現遲到數據,導致數據亂序。如下圖所示,圓圈裏的數字代表時間,這時候如果對數據進行一個5秒的滾動窗口操作,當第6秒的數據進入時,系統會講0-5秒的窗口關閉,這時候4秒的數據就會丟失。

遇到這種情況除了調用allowedLateness和側輸出流外,還可以使用watermark。watermark的使用可以處理亂序數據,通過watermark來定義關窗的時間點。

什麼是watermark

從watermark字面意思可以理解成水位線,水位線在現實生活中可以理解成表示水位高低的刻度,在flink的語義中watermark也可以理解成一個時間刻度。舉一個簡單一點的例子,某個航線每天早上10點爲首班,每隔半個小時有一班航班起飛,每個起飛點都可以理解成watermark,乘客可以理解成一個個事件。如果所有乘客在起飛之前到達則可以正常乘坐飛機到達目的地,就是有序事件的水位線,如下圖所示,1234567都到達了自己的水位線。如果有乘客遲到,飛機不會等他直接飛走,這種情況就比較像亂序事件的水位線,如下圖的45一樣,如果不對45進行處理,這些數據則會丟失。

有序時間水位線

亂序時間水位線

watermark測試

package com.stanley.flink

/**
  * 一個訂單樣例類
  * @param timestamp 事件事件
  * @param category 商品類別
  * @param price 價格
  */
case class Order(timestamp:Long,category:String,price:Double)
package com.stanley.flink


import org.apache.flink.api.java.tuple.{Tuple, Tuple1}
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.scala.function.WindowFunction
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.streaming.api.windowing.windows.TimeWindow
import org.apache.flink.util.Collector

object WatermarkTest {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    //設置爲eventTime
    env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
    //並行度設置爲1
    env.setParallelism(1)
    //創建文本流
    val inputStream:DataStream[String] = env.socketTextStream("node1",9999)
    //轉換成order樣例類
    val dataStream:DataStream[Order] = inputStream.map(str=>{
      val order = str.split(",")
      new Order(order(0).toLong,order(1),order(2).toDouble)})
    val outputStream:DataStream[Order] = dataStream.
    //調用watermark,最大亂序事件設置爲0
      assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[Order](Time.seconds(0)) {
      override def extractTimestamp(t: Order): Long = t.timestamp*1000L
    }).keyBy("category").timeWindow(Time.seconds(5)).apply(new MyWindowFunction)
    dataStream.print("Data")
    outputStream.print("Result")
    env.execute()
  }
}

//自定義Windowfunction,對類別進行聚合,返回時間戳爲窗口的最大事件
class MyWindowFunction() extends WindowFunction[Order,Order,Tuple,TimeWindow]{
  override def apply(key: Tuple, window: TimeWindow, input: Iterable[Order], out: Collector[Order]): Unit = {
    val timestamp = window.maxTimestamp()
    var sum:Double = 0
    for (elem <- input) {
      sum = elem.price+sum
    }
    //java Tuple轉換
    val category:String = key.asInstanceOf[Tuple1[String]].f0
    out.collect(new Order(timestamp,category,sum))
  }
}

輸入以下四條數據,當事件事件爲390時窗口關閉,對385-389這個時間戳內的事件進行分類聚合,在控制檯輸出,390的事件爲下一個窗口的事件。此時的watermark爲390,再次輸入390之前的事件,這些數據如果不做處理將丟失

只對390的food進行聚合,389的數據丟失

如何通過watermark處理遲到數據

watermark的函數可以通過傳入有一個時間來延遲watermark到來的時間,從源碼上就能看到,watermark的是當前的事件時間減去傳入的最大亂序時間

修改最大亂序時間,延遲watermark,

再次輸入同樣的數據,此時控制檯沒有打印result數據,因爲設置了最大亂序時間爲1秒,此時的watermark爲389

當事件時間變爲391時,390的watermark到來,385-389的時間窗口關閉

多任務並行,下游的watermark怎麼定義

當上遊的多個任務並行,下游的watermark以上游最小的watermark爲準

將代碼的並行度設置爲2

再次輸入同樣數據,slot2的watermark已經到達390而slot1的watermark還是389,所以下游的watermark還是389,所以沒有數據輸出

當再次輸入392的事件,此時事件輪詢到slot1,slot1的watermark變爲391,slot2的watermark還是390,下游的watermark爲390導致窗口關閉,輸入結果數據

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