目錄
2.4 DStream中的transformation和action算子
2.5 Driver HA(Standalone或者Mesos)
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的區別
- Storm是純實時的流式處理框架,SparkStreaming是準實時的處理框架(微批處理)。因爲微批處理,SparkStreaming的吞吐量比Storm要高。
- Storm 的事務機制要比SparkStreaming的要完善。
- Storm支持動態資源調度。(spark1.2開始和之後也支持)
- 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
作用:
- 爲SparkStreaming中每一個Key維護一份state狀態,state類型可以是任意類型的,可以是一個自定義的對象,更新函數也可以是自定義的。
- 通過更新函數對該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打印一次窗口內聚合的結果)
- 窗口長度和滑動間隔必須是batchInterval的整數倍。如果不是整數倍會檢測報錯。
- 優化後的window操作要保存狀態所以要設置checkpoint路徑,沒有優化的window操作可以不設置checkpoint路徑。
- 優化後的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中元數據包括:
- 創建應用程序的配置信息。
- DStream的操作邏輯。
- job中沒有完成的批次數據,也就是job的執行進度。
3.kafka
3.1 簡介:
Apache Kafka是分佈式發佈-訂閱消息系統(分佈式消息隊列)。特點是生產者消費者模式,先進先出(FIFO)保證順序,自己不丟數據,默認每隔7天清理數據。消息列隊常見場景:系統之間解耦合、峯值壓力緩衝、異步通信。
MQ: message queue
傳統消息中間件服務RabbitMQ、Apache ActiveMQ(免費)等。IBMMQ (收費)
Apache Kafka與傳統消息系統相比,有以下不同:
- 它是分佈式系統,易於向外擴展;
- 它同時爲發佈和訂閱提供高吞吐量;
- 它支持多訂閱者,當失敗時能自動平衡消費者;
- 它將消息持久化到磁盤,因此可用於批量消費,例如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集羣的部署
- 上傳kafka_2.10-0.8.2.2.tgz包到三個不同節點上,解壓。
- 配置../ kafka_2.10-0.8.2.2/config/server.properties文件
-
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
-
- 分發安裝包,然後修改broker.id以及listeners
- 啓動zookeeper集羣。
- 在三個節點上啓動kafka
bin/kafka-server-start.sh config/server.properties
- 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,
// 我們的broker和executor在同一臺機器上
// 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()
}
}