推薦大家去看原文博主的文章,條理清晰閱讀方便,轉載是爲了方便以後個人查閱
https://my.oschina.net/u/2969788/blog/3082677
flink 多流join 觸發時機詳解
flink多流join代碼很簡單,但是對於初學者可能會遇到window窗口計算不能觸發的"假象",這往往是由於對flink window eventtime processtime理解不到位引起的,以下示例將詳述join在不同時間下的觸發過程.
join+window+processtime
代碼
import java.text.SimpleDateFormat
import cn.chinaunicom.ops.common.Utils
import org.apache.flink.api.common.functions.MapFunction
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.windowing.assigners.{TumblingEventTimeWindows, TumblingProcessingTimeWindows}
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.util.Collector
/**
* @Auther: huowang
* @Date: 14:15 2019/08/02
* @DES: 演示 flink join window在不同時間條件下的觸發
* flink 版本1.7 scala 2.11
* 環境介紹 通過flink監聽虛擬機192.168.217.128兩個網絡端口 9000和9001
* 之後通過 nc -lk 9000 命令向端口發送網絡數據 發送格式爲 字符串,時間戳
* 例如 a1,1564713277270
* b1,1564713277270
* 之後格式化字符串爲二元組
* @Modified By:
*/
object JoinTest {
def main(args: Array[String]): Unit = {
// if(args.length != 3){
// System.err.println("缺少必要參數")
// return
// }
// val hostname = args(0)
// val port1 = args(1).toInt
// val port2 = args(2).toInt
val hostname ="192.168.217.128"
val port1 = 9000
val port2 = 9001
//指定運行環境
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime)
// 接入兩個網絡輸入流
val a = env.socketTextStream(hostname, port1)
val b = env.socketTextStream(hostname, port2)
//將字符串轉換成二元組
val as = a.map(x=>{
println("a=>"+x+",時間=>"+Utils.getStringDate3(x.split(",")(1).toLong))
("key",x.split(",")(0),x.split(",")(1).toLong)
})
// .assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[(String, String, Long)](Time.seconds(1)) {
// override def extractTimestamp(t: (String, String, Long)): Long = t._3
// })
val bs = b.map(x=>{
println("b=>"+x+",時間=>"+Utils.getStringDate3(x.split(",")(1).toLong))
("key",x.split(",")(0),x.split(",")(1).toLong)
})
// .assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[(String, String, Long)](Time.seconds(1)) {
// override def extractTimestamp(t: (String, String, Long)): Long = t._3
// })
val joinedStreams = as
.join(bs)
.where(_._1)
.equalTo(_._1)
.window(TumblingProcessingTimeWindows.of(Time.seconds(10)))
.apply{
(t1 : (String,String, Long), t2 : (String,String, Long), out : Collector[(String,String, Long,Long)]) =>
println("t1=>"+t1+",時間=>"+Utils.getStringDate3(t1._3))
println("t2=>"+t2+",時間=>"+Utils.getStringDate3(t2._3))
// 直接拼接到一起
out.collect((t1._2,t2._2,t1._3,t2._3))
}.name("joinedStreams")
// 結果輸出
joinedStreams.print()
// 執行程序
env.execute("joinedStreams")
}
}
前提我們的三元組 ("key",string,st),的第一個元素是固定的,並且我們的join條件就是第一個元素
不觸發的情況,key不相等,和明顯這種情況在上述代碼中不存在,因爲key都是固定的
不觸發的情況,a流和b流的速率不一樣,沒有數據可以落到同一個時間窗口上,我們現在的時間窗口是30秒,如果我們調成3秒然後再Linux上向流中輸入數據,手速慢一點,兩個窗口的數據數據時間間隔 大於3秒,這樣就不會觸發計算 因爲同一窗口內沒有滿足條件數據
觸發的情況,key相等並且在同一時間窗口上有相關數據
join+window+eventtime
如果使用eventtime需要注意的事情比較多,否則會出現十分詭異的不觸發計算的情況,直接看如下示例代碼
package cn.chinaunicom.ops.test.flinkJoin
import java.text.SimpleDateFormat
import cn.chinaunicom.ops.common.Utils
import org.apache.flink.api.common.functions.MapFunction
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.windowing.assigners.{TumblingEventTimeWindows, TumblingProcessingTimeWindows}
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.util.Collector
/**
* @Auther: huowang
* @Date: 14:15 2019/08/02
* @DES: 演示 flink join window在不同時間條件下的觸發
* flink 版本1.7 scala 2.11
* 環境介紹 通過flink監聽虛擬機192.168.217.128兩個網絡端口 9000和9001
* 之後通過 nc -lk 9000 命令向端口發送網絡數據 發送格式爲 字符串,時間戳
* 例如 a1,1564713277270
* b1,1564713277270
* 之後格式化字符串爲二元組
* @Modified By:
*/
object JoinTest {
def main(args: Array[String]): Unit = {
// if(args.length != 3){
// System.err.println("缺少必要參數")
// return
// }
// val hostname = args(0)
// val port1 = args(1).toInt
// val port2 = args(2).toInt
val hostname ="192.168.217.128"
val port1 = 9000
val port2 = 9001
//指定運行環境
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
env.setParallelism(1);
// 這裏 一定要設置 並行度爲1
// 接入兩個網絡輸入流
val a = env.socketTextStream(hostname, port1)
val b = env.socketTextStream(hostname, port2)
//將字符串轉換成二元組
val as = a.map(x=>{
println("a=>"+x+",時間=>"+Utils.getStringDate3(x.split(",")(1).toLong))
("key",x.split(",")(0),x.split(",")(1).toLong)
}).assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[(String, String, Long)](Time.seconds(1)) {
override def extractTimestamp(t: (String, String, Long)): Long = t._3
})
val bs = b.map(x=>{
println("b=>"+x+",時間=>"+Utils.getStringDate3(x.split(",")(1).toLong))
("key",x.split(",")(0),x.split(",")(1).toLong)
}).assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[(String, String, Long)](Time.seconds(1)) {
override def extractTimestamp(t: (String, String, Long)): Long = t._3
})
as
.join(bs)
.where(_._1)
.equalTo(_._1)
.window(TumblingEventTimeWindows.of(Time.seconds(3)))
.apply{
(t1 : (String,String, Long), t2 : (String,String, Long), out : Collector[(String,String, Long,Long)]) =>
println("t1=>"+t1+",時間=>"+Utils.getStringDate3(t1._3))
println("t2=>"+t2+",時間=>"+Utils.getStringDate3(t2._3))
// 直接拼接到一起
out.collect((t1._2,t2._2,t1._3,t2._3))
}.name("joinedStreams")
.map(x=>{
println("結果輸出=>"+x)
})
// 執行程序
env.execute("joinedStreams")
}
}
要觸發eventtime window計算需要以下條件缺一不可
- env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime) 設置eventtime爲flink時間特性
- 註冊 數據中的時間戳爲事件時間,注意這裏必須數據本身當中包含時間戳,且必須是毫秒時間戳,不能是秒
- 同 processtime 必須關聯key相等 並且在同一個時間窗口上有符合要求的兩個流的數據
- 注意即使滿足了上述3點,eventtime的window計算還是不會觸發,因爲eventtime 需要我們自己控制時間線,事件的水位線必須要大於window的end time纔會觸發計算,也就是說 如果你兩個網絡端口只各自模擬一條數據 是永遠不會觸發計算的,必須要有下一條滿足條件的數據到達,並且把水位線升高到end time以上,纔會觸發計算,結合實際考慮 如果你的數據流數據不是連續到達或者中間有較大間隔,eventtime的滾動窗口可能不適合,因爲"最後一個"window可能不能及時觸發,注意需要join的兩個流都有數據水位線高於window endtime纔會觸發
- 滿足了上述4點 在本地調試的時候還是可能會觸發不了window,這是爲什麼呢??!!!,因爲如果本地idea環境下如果不設置並行度,會默認cpu的核數爲並行度,這樣流的數據可能被隨機分配到不同的pipeline中去執行,因此匹配不到數據就無法滿足window的觸發條件,如果是產線環境我們勢必要多並行,可以根據keyby,把目標數據分到同一個pipeline中
最終正常的調試結果