flink 多流join 觸發時機詳解

推薦大家去看原文博主的文章,條理清晰閱讀方便,轉載是爲了方便以後個人查閱

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計算需要以下條件缺一不可

  1. env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime) 設置eventtime爲flink時間特性
  2. 註冊 數據中的時間戳爲事件時間,注意這裏必須數據本身當中包含時間戳,且必須是毫秒時間戳,不能是秒
  3. 同 processtime 必須關聯key相等 並且在同一個時間窗口上有符合要求的兩個流的數據
  4. 注意即使滿足了上述3點,eventtime的window計算還是不會觸發,因爲eventtime 需要我們自己控制時間線,事件的水位線必須要大於window的end time纔會觸發計算,也就是說 如果你兩個網絡端口只各自模擬一條數據 是永遠不會觸發計算的,必須要有下一條滿足條件的數據到達,並且把水位線升高到end time以上,纔會觸發計算,結合實際考慮 如果你的數據流數據不是連續到達或者中間有較大間隔,eventtime的滾動窗口可能不適合,因爲"最後一個"window可能不能及時觸發,注意需要join的兩個流都有數據水位線高於window endtime纔會觸發
  5. 滿足了上述4點 在本地調試的時候還是可能會觸發不了window,這是爲什麼呢??!!!,因爲如果本地idea環境下如果不設置並行度,會默認cpu的核數爲並行度,這樣流的數據可能被隨機分配到不同的pipeline中去執行,因此匹配不到數據就無法滿足window的觸發條件,如果是產線環境我們勢必要多並行,可以根據keyby,把目標數據分到同一個pipeline中

最終正常的調試結果

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