Spark學習(七):SparkStreaming

目錄

1  什麼是SparkStreaming

1.1 SparkStreaming簡介、

1.2 SparkStreaming與Storm的區別

2  SparkStreaming初始

2.1 官方自帶的WordCount程序

2.2 IDEA編程

2.3 StreamingContext的cores配置

2.4 DStream中的transformation和action算子

2.4.1 transformation算子

2.4.2 action算子

2.5 Driver HA(Standalone或者Mesos)

3.kafka

3.1 簡介:

3.2 kafka中的術語

3.3 kafka集羣的部署

4  SparkStreaming+kafka

4.1 streaming和kafka整合

4.2 代碼實現

4.3 streaming-kafka-wordcount


1  什麼是SparkStreaming

1.1 SparkStreaming簡介、

官網:http://spark.apache.org/streaming/

特點:

  • 便於使用
  • 容錯
  • spark集成

SparkStreaming是流式處理框架,是Spark API的擴展,支持可擴展、高吞吐量、容錯的實時數據流處理,實時數據的來源可以是:Kafka, Flume, Twitter, ZeroMQ或者TCP sockets,並且可以使用高級功能的複雜算子來處理流數據。例如:map,reduce,join,window 。最終,處理後的數據可以存放在文件系統,數據庫等,方便實時展現。

和Spark基於RDD的概念很相似,Spark Streaming使用離散化流(discretized stream)作爲抽象表示,叫作DStream。DStream 是隨時間推移而收到的數據的序列。在內部,每個時間區間收到的數據都作爲 RDD 存在,而 DStream 是由這些 RDD 所組成的序列(因此 得名“離散化”)。

DStream 可以從各種輸入源創建,比如 Flume、Kafka 或者 HDFS。創建出來的DStream 支持兩種操作,一種是轉化操作(transformation),會生成一個新的DStream,另一種是輸出操作(output operation),可以把數據寫入外部系統中。DStream 提供了許多與 RDD 所支持的操作相類似的操作支持,還增加了與時間相關的新操作,比如滑動窗口。

Discretized Stream or DStream 是 Spark Streaming 提供的基本抽象. 它代表了一個連續的數據流, 無論是從 source(數據源)接收到的輸入數據流, 還是通過轉換輸入流所產生的處理過的數據流. 在內部, 一個 DStream 被表示爲一系列連續的 RDDs, 在一個 DStream 中的每個 RDD 包含來自一定的時間間隔的數據,所以說應用與DStream的任何操作轉化在底層來說都是對於RDDs的操作:如下圖所示.

1.2 SparkStreaming與Storm的區別

  1. Storm是純實時的流式處理框架,SparkStreaming是準實時的處理框架(微批處理)。因爲微批處理,SparkStreaming的吞吐量比Storm要高。
  2. Storm 的事務機制要比SparkStreaming的要完善。
  3. Storm支持動態資源調度。(spark1.2開始和之後也支持)
  4. SparkStreaming擅長複雜的業務處理,Storm不擅長複雜的業務處理,擅長簡單的彙總型計算。

2  SparkStreaming初始

2.1 官方自帶的WordCount程序

[root@hadoop103 ~]# nc -lk 9999

-bash: nc: command not found     //如果出現這個提示

[root@hadoop103 ~]# yum -y install nc     //安裝即可

 然後在另一臺節點上監聽hadoop103節點的9999端口,運行下面spark自帶的WordCount程序

$SPARK_HOME/bin/run-example streaming.NetworkWordCount hadoop103 9999

注意:如果虛擬機的cores 只有一個,sparkstreaming的程序就不能讀取數據,詳解往下面看

2.2 IDEA編程

操作SparkStreaming首先導入下面的依賴

<!-- 導入SparkStreaming的依賴 -->
<dependency>
    <groupId>org.apache.spark</groupId>
    <artifactId>spark-streaming_2.11</artifactId>
    <version>2.2.0</version>
</dependency>

// 配置日誌的顯示級別,
Logger.getLogger("org").setLevel(Level.ERROR)   //具體日誌信息問題:請點這裏

def main(args: Array[String]): Unit = {
  val conf = new SparkConf().setMaster("local[*]").setAppName(this.getClass.getSimpleName)
  val sc = new SparkContext(conf)

  val ssc: StreamingContext = new StreamingContext(sc,Seconds(2))  //Seconds() 設置一個批次的時間間隔

  //讀取socket端口的數據
  val textStream: ReceiverInputDStream[String] = ssc.socketTextStream("hadoop103",9999)

  //對讀取到的數據進行處理
  val result: DStream[(String, Int)] = textStream.flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_)

  //調用action算子
  result.print()  //默認打印10

  //Spark Streaming 程序的必備
  ssc.start()
  //阻塞程序,掛起
  ssc.awaitTermination()
}

2.3 StreamingContext的cores配置

如果程序給定的cores 只有一個:

// master 需要至少 2 個核, 以防止飢餓情況(starvation scenario.

val conf = new SparkConf().setMaster("local[1]").setAppName(this.getClass.getSimpleName)

//如果給一個cores,會警告如下:

19/04/06 13:38:27 WARN StreamingContext: spark.master should be set as local[n], n > 1 in local mode if you have receivers to get data, otherwise Spark jobs will not get resources to process the received data.

一個StreamingContext創建多個input Dstream,會創建多個Receiver,Spark會爲每個Receiver 分配一個core用於其運行。 

故若SparkStreaming 程序一共分配了k個core,運行n個Receiver,應保證k>n,這時會有n個core用於運行Receiver接收外部數據,k-n個core用於真正的計算。 

一個Receiver 佔用了一個core,這裏兩個Receiver佔用了2個core,如果這個job的啓動資源是 --master "local[4]" 那麼真正能用於運算的core只有兩個了。

val ssc: StreamingContext = new StreamingContext(sc,Seconds(2))
// 讀取socket端口的數據,  需要一個cores
val textStream: ReceiverInputDStream[String] = ssc.socketTextStream("hadoop103",9999)
// 佔用一個cores  
val textStream2: ReceiverInputDStream[String] = ssc.socketTextStream("hadoop102",9999)

2.4 DStream中的transformation和action算子

2.4.1 transformation算子

lazy執行,生成新的DStream算子,一些常用的轉換算子

transform(func)

通過對源 DStream 的每個 RDD 應用 RDD-to-RDD 函數

無狀態轉化:

比如:map(),flatMap(),filiter(),repartition()

有狀態轉化

updateStateByKey

作用:

  1. 爲SparkStreaming中每一個Key維護一份state狀態,state類型可以是任意類型的,可以是一個自定義的對象,更新函數也可以是自定義的。
  2. 通過更新函數對該key的狀態不斷更新,對於每個新的batch(時間片段)而言,SparkStreaming會在使用updateStateByKey的時候爲已經存在的key進行state的狀態更新。

使用到updateStateByKey要開啓checkpoint機制和功能。

多久會將內存中的數據寫入到磁盤一份?

如果batchInterval設置的時間小於10秒,那麼10秒寫入磁盤一份。如果batchInterval設置的時間大於10秒,那麼就會batchInterval時間間隔寫入磁盤一份。

import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.{Seconds, StreamingContext}

object StateFulWordCount {

  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName(this.getClass.getSimpleName)
    val ssc = new StreamingContext(conf,Seconds(3))
    //設置checkpoint路徑(持久化)
    ssc.checkpoint("spark_day01/WordCount")

    val lines: ReceiverInputDStream[String] = ssc.socketTextStream("hadoop103",9999)

    val pairs = lines.flatMap(_.split(" ")).map((_,1))
    //val result = pairs.reduceByKey(_+_)
    //使用updateStateByKey替換reduceBykey來更新狀態,統計從運行開始以來單詞總的次數
    val result: DStream[(String, Int)] = pairs.updateStateByKey(updateFunc)
    result.print()

    ssc.start()
    ssc.awaitTermination()

  }
  //定義更新狀態方法,參數values爲當前批次單詞頻度,state爲以往批次單詞的頻度
  def updateFunc = (values: Seq[Int], state: Option[Int]) => {
    val currentCount = values.sum
    val previousCount = state.getOrElse(0)
    Some(currentCount + previousCount)
  }
}

窗口操作

窗口操作理解圖

假設每隔5s 1個batch,上圖中窗口長度爲15s,窗口滑動間隔10s。(即每隔10s打印一次窗口內聚合的結果)

  1. 窗口長度和滑動間隔必須是batchInterval的整數倍。如果不是整數倍會檢測報錯。
  2. 優化後的window操作要保存狀態所以要設置checkpoint路徑,沒有優化的window操作可以不設置checkpoint路徑。
  3. 優化後的window窗口操作示意圖:

import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.{Seconds, StreamingContext}

object SparkWindow {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName(this.getClass.getSimpleName)
    val ssc = new StreamingContext(conf, Seconds(3))
    //設置持久化checkpoint的路徑
    ssc.checkpoint("spark_day01/WordCount1")

    val lines: ReceiverInputDStream[String] = ssc.socketTextStream("hadoop103", 9999)
    val pairs: DStream[(String, Int)] = lines.flatMap(_.split(" ")).map((_, 1))
    //窗口大小爲9s,步長爲6s
    //無優化的窗口操作當使用無優化的窗口操作可以不設置checkpoint
    //val stateDStream: DStream[(String, Int)] = pairs.reduceByKeyAndWindow(
    // (a: Int,b: Int) => a + b,Seconds(9),Seconds(6))


    //使用窗口操作進行優化把單詞作爲key,次數作爲value,會把輸入的所有單詞當前窗口狀態輸出
    val stateDStream: DStream[(String, Int)] = pairs.reduceByKeyAndWindow(
      (a: Int, b: Int) => a + b, (a: Int, b: Int) => a - b, Seconds(9), Seconds(6))
    stateDStream.print()

    ssc.start()
    ssc.awaitTermination()
  }
}

2.4.2 action算子

一些常用的action算子,只有當觸發action算子後纔會執行任務。

2.5 Driver HA(Standalone或者Mesos)

因爲SparkStreaming是7*24小時運行,Driver只是一個簡單的進程,有可能掛掉,所以實現Driver的HA就有必要(如果使用的Client模式就無法實現Driver HA ,這裏針對的是cluster模式)。Yarn平臺cluster模式提交任務,AM(AplicationMaster)相當於Driver,如果掛掉會自動啓動AM。這裏所說的DriverHA針對的是Spark standalone和Mesos資源調度的情況下。實現Driver的高可用有兩個步驟:

第一:提交任務層面,在提交任務的時候加上選項 --supervise,當Driver掛掉的時候會自動重啓Driver。

第二:代碼層面,使用JavaStreamingContext.getOrCreate(checkpoint路徑,JavaStreamingContextFactory)

Driver中元數據包括:

  1. 創建應用程序的配置信息。
  2. DStream的操作邏輯。
  3. job中沒有完成的批次數據,也就是job的執行進度。

3.kafka

3.1 簡介:

Apache Kafka是分佈式發佈-訂閱消息系統(分佈式消息隊列)。特點是生產者消費者模式,先進先出(FIFO)保證順序,自己不丟數據,默認每隔7天清理數據。消息列隊常見場景:系統之間解耦合、峯值壓力緩衝、異步通信。

MQ: message queue

傳統消息中間件服務RabbitMQ、Apache ActiveMQ(免費)等。IBMMQ (收費)

Apache Kafka與傳統消息系統相比,有以下不同:

  1. 它是分佈式系統,易於向外擴展;
  2. 它同時爲發佈和訂閱提供高吞吐量;
  3. 它支持多訂閱者,當失敗時能自動平衡消費者;
  4. 它將消息持久化到磁盤,因此可用於批量消費,例如ETL,以及實時應用程序。

3.2 kafka中的術語

術語

解釋

Broker

Kafka集羣包含一個或多個服務器,這種服務器被稱爲broker

Topic

 

每條發佈到Kafka集羣的消息都有一個類別,這個類別被稱爲Topic。(物理上不同Topic的消息分開存儲,邏輯上一個Topic的消息雖然保存於一個或多個broker上但用戶只需指定消息的Topic即可生產或消費數據而不必關心數據存於何處)

Partition

Partition是物理上的概念,每個Topic包含一個或多個Partition.

Producer

負責發佈消息到Kafka broker

Consumer

消息消費者,向Kafka broker讀取消息的客戶端

Consumer Group

每個Consumer屬於一個特定的Consumer Group(可爲每個Consumer指定group name,若不指定group name則屬於默認的group)

replica

partition 的副本,保障 partition 的高可用

leader

replica 中的一個角色, producer 和 consumer 只跟 leader 交互

follower

replica 中的一個角色,從 leader 中複製數據

controller

Kafka 集羣中的其中一個服務器,用來進行 leader election 以及各種 failover

3.3 kafka集羣的部署

  1. 上傳kafka_2.10-0.8.2.2.tgz包到三個不同節點上,解壓。
  2. 配置../ kafka_2.10-0.8.2.2/config/server.properties文件
    1. broker.id=1  

      delete.topic.enable=true     

      listeners=PLAINTEXT://hadoop101:9092

      log.dirs=/opt/module/kafka_2.11-0.11.0.0/logs/

      num.partitions=3

      zookeeper.connect=hadoop101:2181,hadoop102:2181,hadoop103:2181

  3. 分發安裝包,然後修改broker.id以及listeners
  4. 啓動zookeeper集羣。
  5. 在三個節點上啓動kafka

    bin/kafka-server-start.sh   config/server.properties

  6. kafka相關命令

創建topic:

./kafka-topics.sh --zookeeper hadoop101:2181,hadoop102:2181,hadoop103:2181  --create --topic first --partitions 3  --replication-factor 3

用一臺節點控制檯來當kafka的生產者:

./kafka-console-producer.sh  --topic  topic2017 --broker-list hadoop101:2181,hadoop102:2181,hadoop103:2181

用另一臺節點控制檯來當kafka的消費者:

./kafka-console-consumer.sh --zookeeper hadoop101:2181,hadoop102:2181,hadoop103:2181 --topic first

查看kafka中topic列表:

./kafka-topics.sh  --list --zookeeper hadoop101:2181,hadoop102:2181,hadoop103:2181

查看kafka中topic的描述:

./kafka-topics.sh --describe --zookeeper node3:2181,node4:2181,node5:2181  --topic first

查看zookeeper中topic相關信息:

啓動zookeeper客戶端:

./zkCli.sh

查看topic相關信息:

ls /brokers/topics/

查看消費者相關信息:

ls /consumers

刪除kafka中的數據

./kafka-topics.sh --zookeeper hadoop101:2181,hadoop102:2181,hadoop103:2181 --delete --topic first

在每臺broker節點上刪除當前這個topic對應的真實數據。

進入zookeeper客戶端,刪除topic信息

rmr /brokers/topics/first

刪除zookeeper中被標記爲刪除的topic信息

rmr /admin/delete_topics/first

4  SparkStreaming+kafka

4.1 streaming和kafka整合

官方文檔:http://spark.apache.org/docs/2.2.0/streaming-kafka-integration.html

在kafka0-10版本已經不再支持receiver DStream模式

導入下面的依賴:

<!--streaming 集成  kafka的依賴jar包-->
<dependency>
    <groupId>org.apache.spark</groupId>
    <artifactId>spark-streaming-kafka-0-10_2.11</artifactId>
    <version>2.2.0</version>
</dependency>

val kafkaParams = Map[String, Object](

//要連接的 服務器:端口號
    "bootstrap.servers" -> "Hadoop101:9092,hadoop102:9092,hadoop103:9092",

//設置消費(k,v)格式的數據類型

//rdd中的數據類型是 ConsumerRecord[數據類型, 數據類型]
    "key.deserializer" -> classOf[StringDeserializer],
    "value.deserializer" -> classOf[StringDeserializer],

//消費者組
    "group.id" -> "hello_topic_group",

//可以設置從哪裏讀取數據
    "auto.offset.reset" -> "earliest",

// 是否可以自動提交偏移量   自定義
    "enable.auto.commit" -> (false: java.lang.Boolean)
  )

4.2 代碼實現

import org.apache.kafka.clients.consumer.ConsumerRecord
import org.apache.kafka.common.serialization.StringDeserializer
import org.apache.spark.SparkConf
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.streaming.kafka010._
import org.apache.spark.streaming.kafka010.LocationStrategies.PreferConsistent
import org.apache.spark.streaming.kafka010.ConsumerStrategies.Subscribe

object SparkAndKafka {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[*]")
    val ssc = new StreamingContext(conf,Seconds(2))

    val kafkaParams = Map[String, Object](
      "bootstrap.servers" -> "hadoop101:9092",
      "key.deserializer" -> classOf[StringDeserializer],
      "value.deserializer" -> classOf[StringDeserializer],
      "group.id" -> "use_a_separate_group_id_for_each_stream",
      "auto.offset.reset" -> "latest",
      "enable.auto.commit" -> (false: java.lang.Boolean)
    )

    //可以設置讀取多個主題
    val topics = Array("first","second")
    val stream = KafkaUtils.createDirectStream[String, String](
      ssc,
      //在可用的executors上均勻分區
      PreferConsistent,
      Subscribe[String, String](topics, kafkaParams)
    )

    stream.foreachRDD(rdd => {
      rdd.foreach(println)
    })

    ssc.start()
    ssc.awaitTermination()
  }
}

groupId:

同一個組中,可以有多個consumer,但是多個consumer只能消費不同分區的數據,

也就是說一條數據,只能被一個consumer去消費。

還可以爲每一個group中的consumer指定一個名稱,即group.name

4.3 streaming-kafka-wordcount

在hadoop101節點啓動kafka生產者,向topic second中寫數據

在hadoop102節點上向主題first中寫入數據

 在IDEA中編寫程序,主題first和second中的數據

import org.apache.kafka.clients.consumer.ConsumerRecord
import org.apache.kafka.common.serialization.StringDeserializer
import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.InputDStream
import org.apache.spark.streaming.kafka010.{KafkaUtils, LocationStrategies}
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.streaming.kafka010.ConsumerStrategies.Subscribe

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

    val conf = new SparkConf()
      .setMaster("local[*]")
      .setAppName(this.getClass.getSimpleName)

    val ssc=  new StreamingContext(conf,Seconds(2))

    // kafka的參數配置
    val kafkaParams = Map[String, Object](
      "bootstrap.servers" -> "hadoop101:9092",
      "key.deserializer" -> classOf[StringDeserializer],
      "value.deserializer" -> classOf[StringDeserializer],
      "group.id" -> "hello_topic_group",
      // 從哪個位置開始讀取數據
      "auto.offset.reset" -> "earliest",
      // 是否可以自動提交偏移量   自定義
      "enable.auto.commit" -> (false: java.lang.Boolean)
    )

    // streaming 支持 讀取kafka的多個主題
    val topics = Array("second")
    // 指定泛型約定
    val stream: InputDStream[ConsumerRecord[String, String]] = KafkaUtils.createDirectStream[String, String](
      ssc,
      // 我們的brokerexecutor在同一臺機器上
      // LocationStrategies.PreferBrokers
      // 資源會平均分配
      LocationStrategies.PreferConsistent,
      // PreferConsistent,
      // 消費者 訂閱哪些主題
      Subscribe[String, String](topics, kafkaParams)
    )

    stream.foreachRDD(rdd=>{
      // rdd中的數據類型是 ConsumerRecord[String, String]   拿到這裏的value
      val result = rdd.flatMap(_.value().split(" ")).map((_,1)).reduceByKey(_+_)
      result.foreach(println)
    })

    ssc.start()
    ssc.awaitTermination()
  }
}

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