Spark-Streaming中DStream得轉換|無狀態轉換操作(transform、join)|有狀態轉換操作(updateStateByKey、WindowOperations)

DStream轉換

DStream上的操作與RDD的類似,分爲Transformations(轉換)和Output Operations(輸出)兩種,此外轉換操作中還有一些比較特殊的原語,如:updateStateByKey()、transform()以及各種Window相關的算子。

無狀態轉化操作

  1. 無狀態轉化操作就是把簡單的RDD轉化操作應用到每個批次上,也就是轉化DStream中的每一個RDD。部分無狀態轉化操作列在了下表中。
  2. 儘管這些函數看起來像作用在整個流上一樣,但事實上每個DStream在內部是由許多RDD(批次)組成,且無狀態轉化操作是分別應用到每個RDD上的。
  3. 例如:reduceByKey()會歸約每個時間區間中的數據,但不會歸約不同區間之間的數據。
  4. 簡而言之是每個批次進行單獨得WordCount,批次與批次之間沒有聯繫,不是真正得WordCount,只是批次內得WordCount

在這裏插入圖片描述

transform(無狀態)

  1. Transform允許DStream上執行任意的RDD-to-RDD函數。即使這些函數並沒有在DStream的API中暴露出來,通過該函數可以方便的擴展Spark API。
  2. 該函數每一批次調度一次。其實也就是對DStream中的RDD應用轉換。
 def main(args: Array[String]): Unit = {

    val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("SparkStreaming_transform")
    val ssc = new StreamingContext(conf,Seconds(3))

    val lineDStream: ReceiverInputDStream[String] = ssc.socketTextStream("hadoop102", 9999)

   // println(Thread.currentThread().getName) // 一次 在Driver端

    // 將Dstream得操作直接轉換爲RDD進行操作
    val wordToCountDstrem: DStream[(String, Int)] = lineDStream.transform {
        //  println(Thread.currentThread().getName) // 每次批次一次 在Driver端
      rdd => {
        rdd.flatMap(_.split(" "))
          .map((_, 1))
          .reduceByKey(_ + _)
       // println(Thread.currentThread().getName) // 每次批次元素個數幾個 在exceator端
      }
    }
    wordToCountDstrem.print()

    ssc.start()
    ssc.awaitTermination()

  }

在這裏插入圖片描述

join(無狀態)

兩個流之間的join需要兩個流的批次大小一致,這樣才能做到同時觸發計算。計算過程就是對當前批次的兩個流中各自的RDD進行join,與兩個RDD的join效果相同。

 def main(args: Array[String]): Unit = {

    val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("SparkStreaming_transform")
    val ssc = new StreamingContext(conf,Seconds(3))

    val lineDStream1: ReceiverInputDStream[String] = ssc.socketTextStream("hadoop102", 9999)
    val lineDStream2: ReceiverInputDStream[String] = ssc.socketTextStream("hadoop102", 8888)

    // 轉換爲kv節奏
    val wordToOneDStrem: DStream[(String, Int)] = lineDStream1.map((_,1))
    val wordToADStrem: DStream[(String, Int)] = lineDStream2.map((_,"a"))

    // 流得Join
    val joinDStream: DStream[(String, (Int, String))] = wordToOneDStrem.join(wordToADStrem)


    joinDStream.print()

    ssc.start()
    ssc.awaitTermination()
  }

updateStateByKey(有狀態)

程序掛掉後不能接着續傳,只能從頭再來,若想實現續傳,需要讀取上一次結果
updateStateByKey用於記錄歷史記錄,有時我們需要在DStream中跨批次維護狀態(例如流計算中累加WordCount)。針對這種情況,updateStateByKey()爲我們提供了對一個狀態變量的訪問,用於鍵值對形式的DStream。給定一個由(鍵,事件)對構成的DStream,並傳遞一個指定如何根據新的事件更新每個鍵對應狀態的函數,它可以構建出一個新的DStream,其內部數據爲(鍵,狀態) 對。
updateStateByKey()的結果會是一個新的DStream,其內部的RDD序列是由每個時間區間對應的(鍵,狀態)對組成的。
updateStateByKey操作使得我們可以在用新信息進行更新時保持任意的狀態。爲使用這個功能,需要做下面兩步:

  1. 定義狀態,狀態可以是一個任意的數據類型。
  2. 定義狀態更新函數,用此函數闡明如何使用之前的狀態和來自輸入流的新值對狀態進行更新。
    更新版的WordCount

在這裏插入圖片描述

 def main(args: Array[String]): Unit = {

    val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("SparkStreaming_transform")
    val ssc = new StreamingContext(conf,Seconds(3))

    // 存儲地方
    ssc.checkpoint("./updata")

    val lineDStream: ReceiverInputDStream[String] = ssc.socketTextStream("hadoop102", 9999)
    val wordToCount: DStream[(String, Int)] = lineDStream.flatMap(_.split(" ")).map((_,1))

    // updateFunc(Seq[V],Option[S]) => Option[S]
    // Seq -> 當前得數據集  ; Option -> 以前得數據集
    val updataFunc= (seq:Seq[Int],state:Option[Int]) =>{
        val sum: Int = seq.sum
        val lastSum: Int = state.getOrElse(0)
        Some(sum + lastSum)
      }

    val wordToCountDStream: DStream[(String, Int)] = wordToCount.updateStateByKey(updataFunc)

    wordToCountDStream.print()

    ssc.start()
    ssc.awaitTermination()

  }

在這裏插入圖片描述

WindowOperations

Window Operations可以設置窗口的大小和滑動窗口的間隔來動態的獲取當前Steaming的允許狀態。所有基於窗口的操作都需要兩個參數,分別爲窗口時長以及滑動步長。
(1)窗口時長:計算內容的時間範圍;
(2)滑動步長:隔多久觸發一次計算。
這兩者都必須爲批次大小的整數倍。

下圖爲:窗口大小爲批次的2倍,滑動步等於批次大小。

在這裏插入圖片描述

 def main(args: Array[String]): Unit = {

    val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("SparkStreaming_windowOperations")
    val ssc = new StreamingContext(conf,Seconds(3))

    val lineDStream: ReceiverInputDStream[String] = ssc.socketTextStream("hadoop102", 9999)

    // 開窗 計算時間和批次時間默認一致,
    // 批次時間爲3 則計算時間(滑動步長)也爲3 意思爲每批次都處理一次
    // (窗口大小)9秒除以批次時間3 => 爲3 則每次計算處理三個批次

/*    val windowDStream: DStream[String] = lineDStream.window(Seconds(9))

    windowDStream.flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_).print()*/

    // 計算得過程中 進行開窗
    lineDStream.flatMap(_.split(" "))
        .map((_,1))
        .reduceByKeyAndWindow((x:Int,y:Int)=>x+y,Seconds(9),Seconds(6))

    ssc.start()
    ssc.awaitTermination()

  }

在這裏插入圖片描述

Window其他操作

(1)window(windowLength, slideInterval): 基於對源DStream窗化的批次進行計算返回一個新的Dstream;
(2)countByWindow(windowLength, slideInterval): 返回一個滑動窗口計數流中的元素個數;
(3)reduceByWindow(func, windowLength, slideInterval): 通過使用自定義函數整合滑動區間流元素來創建一個新的單元素流;
(4)reduceByKeyAndWindow(func, windowLength, slideInterval, [numTasks]): 當在一個(K,V)對的DStream上調用此函數,會返回一個新(K,V)對的DStream,此處通過對滑動窗口中批次數據使用reduce函數來整合每個key的value值。
(5)reduceByKeyAndWindow(func, invFunc, windowLength, slideInterval, [numTasks]): 這個函數是上述函數的變化版本,每個窗口的reduce值都是通過用前一個窗的reduce值來遞增計算。通過reduce進入到滑動窗口數據並”反向reduce”離開窗口的舊數據來實現這個操作。
(6)countByWindow()和countByValueAndWindow()作爲對數據進行計數操作的簡寫。countByWindow()返回一個表示每個窗口中元素個數的DStream,而countByValueAndWindow()返回的DStream則包含窗口中每個值的個數。

在這裏插入圖片描述

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