注意:本篇博客中的所有解釋都是在滾動窗口的前提下
目錄
1 時間概念類型
1.1 事件生成時間(Event Time)
在事件時間模式下,Flink流式應用處理的所有記錄都必須要包含時間戳。
這個時間戳是記錄所對應事件的發生時間,但實際上我們也可以自定義時間戳,但只要保證流記錄的時間戳會隨着數據流的前進大致遞增即可。
1.2 事件接入時間(Ingestion Time)
Ingestion Time,是數據通過第三方進入到Flink,Flink接收數據的時間,因此如果按照事件的接入時間,來處理數據,是不能處理亂序情況下的數據(如果數據是亂序到達)。
1.3 事件處理時間(Processing Time)
即數據接入Flink後,通過算子處理數據的時間,使用的是當前主機的時間。
1.4 指定時間概念
Flink流式處理中,絕大部分的業務都會使用eventTime,一般只在eventTime無法使用時,纔會考慮其他的時間屬性。
Flink默認採用的時Process Time時間概念
object addSink到kafka {
def main(args: Array[String]): Unit = {
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
val streamLocal = StreamExecutionEnvironment.createLocalEnvironment(3)
//這裏配值我們在處理數據的時候,使用EventTime事件時間
streamLocal.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
streamLocal.setStreamTimeCharacteristic(TimeCharacteristic.IngestionTime)
streamLocal.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime)
import org.apache.flink.api.scala._ //如果數據是有限的(靜態數據集)可以引入這個包
val dataStream = streamLocal.fromElements(("flink_er", 3), ("f", 1), ("c", 2), ("c", 1), ("d", 5))
.map(x => x._1)
//flink-connector-kafka的版本要保持一致,都是1.7.0
val kafkaProducer010 = new FlinkKafkaProducer[String]("master:9092", "ceshi01", new SimpleStringSchema())
dataStream.addSink(kafkaProducer010)
streamLocal.execute()
}
}
2 water_mark(水位線)
通常來講,由於各種原因,包含但不限於網絡、外部系統因素等,事件數據往往不能夠及時傳輸到Flink系統中進行計算,因此,在開啓EventTime的前提下,flink提供了一種依據watermark(水位線)機制結合window(窗口)來實現對亂序數據的處理的方式。
2.1 water_mark是如何生成的?
生成water_mark的方式主要是有兩大類:
- with Periodic water_mark
- with Punctuated water_mark
第一種可以定義一個最大允許亂序的時間,這種情況應用較多。
val watermark: DataStream[(String, Long, String, Int)] = inputMap.assignTimestampsAndWatermarks(new AssignerWithPeriodicWatermarks[(String, Long, String, Int)] {
val maxOutOfOrderness = 10000L //最大允許的亂序時間是10s
var currentMaxTimestamp = 0L
var a: Watermark = _
override def getCurrentWatermark: Watermark = {
a = new Watermark(currentMaxTimestamp - maxOutOfOrderness)
a
}
override def extractTimestamp(element: (String, Long, String, Int), previousElementTimestamp: Long): Long = {
val timestamp = element._2
currentMaxTimestamp = Math.max(timestamp, currentMaxTimestamp)
val end = if (!a.toString.contains("-")) {
val regEx = "[^0-9]";
val p = Pattern.compile(regEx);
val m = p.matcher(a.toString);
val L_number = m.replaceAll("").trim()
format.format(L_number.toLong)
} else a.toString
println("timestamp:" + element._1 + "," + element._2 + "," + element._3 + "|" +
s"${a.toString}($end)" + "|" + format.format(currentMaxTimestamp - maxOutOfOrderness))
val lll: Long = System.currentTimeMillis()
timestamp
}
})
2.2 最大允許亂序時間如何理解?
實際上可以理解爲數據集中的元素沒有按照順序到達,而是其中某個元素延遲到達了,那麼此時整個數據集,就是亂序的;因此纔會有這個“允許最大亂序時間”的概念,即允許事件數據延遲多久到達。
(數據有可能會延遲到達,但我們又不能無限期的等待下去,必須有個機制來保證一個特定的事件後,必須觸發windows去進行當前窗口的數據,這個特別的機制就是water_mark)
2.3 Flink中的water_marker機制
-
每接收一條數據就相當於往水池中添加水,因此水位線的高度只會升高不會降低,每當一個新的數據進來時,會重新計算水位線時間。每條數據中的water_marker記錄的是截至到現在最高的水位線,
每條數據計算水位線的時候如果小於當前水位線時間,則不會更新現有的水位線(如下方的timestamp:4)
-
當水位線到達窗口觸發時間時纔會觸發窗口的計算,water_marker的意義在於數據無序傳遞的時候可以讓其保持一定的容錯率,如果晚來的數據在這個容錯率之內,會當作正常傳遞來的數據進行處理。
輸出數據爲:
當前數據 | 當前數據的水位線 | 計算並更新水位線
timestamp:1,1586947680000
,18:48:00 | Watermark@-10000(Watermark@-10000) | 18:47:50
timestamp:2,1586947740000
,18:49:00 | Watermark@1586947670000(18:47:50) | 18:48:50
timestamp:3,1586947800000,18:50:00 | Watermark@1586947730000(18:48:50) | 18:49:50
timestamp:4,1586947620000,18:47:00 | Watermark@1586947790000(18:49:50) | 18:49:50
timestamp:5,1586947680000,18:48:00|Watermark@1586947790000(18:49:50) | 18:49:50
timestamp:6,1586947260000,18:41:00 | Watermark@1586947790000(18:49:50) | 18:49:50
timestamp:7,1586947980000,18:53:00 | Watermark@1586947790000(18:49:50) | 18:52:50
timestamp:8,1586947920000,18:52:00 | Watermark@1586947970000(18:52:50) | 18:52:50
以上輸出數據的形成過程:
輸入數據:
1,1586947680000
2,1586947740000
… …
water_mark生成方式
本篇博文中的示例 b
代碼所示;
結論:
可以發現我的輸入數據是亂序達到的,有的到達的時間早有的到達的時間晚,但就算是如此,我的水位線依然不受其影響,因此得證,Flink中的水位線只會增高不會降低。