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導致窗口關閉,輸入結果數據