一、基於 Spark 做 Spark Streaming 的思路
Spark Streaming 與 Spark Core 的關係可以用下面的經典部件圖來表述:
在本節,我們先探討一下基於 Spark Core 的 RDD API,如何對 streaming data 進行處理。理解下面描述的這個思路非常重要,因爲基於這個思路詳細展開後,就能夠充分理解整個 Spark Streaming 的模塊劃分和代碼邏輯。
第一步,假設我們有一小塊數據,那麼通過 RDD API,我們能夠構造出一個進行數據處理的 RDD DAG(如下圖所示)。
第二步,我們對連續的 streaming data 進行切片處理 —— 比如將最近 200ms 時間的 event 積攢一下 —— 每個切片就是一個 batch,然後使用第一步中的 RDD DAG 對這個 batch 的數據進行處理。
注意: 這裏我們使用的是 batch 的概念 —— 其實 200ms 在其它同類系統中通常叫做 mini-batch,不過既然 Spark Streaming 官方的叫法就是 batch,我們這裏就用 batch 表達 mini-batch 的意思了 :)
所以,針對連續不斷的 streaming data 進行多次切片,就會形成多個 batch,也就對應出來多個 RDD DAG(每個 RDD DAG 針對一個 batch 的數據)。如此一來,這多個 RDD DAG 之間相互同構,卻又是不同的實例。我們用下圖來表示這個關係:
所以,我們將需要:
- (1) 一個靜態的 RDD DAG 的模板,來表示處理邏輯;
- (2) 一個動態的工作控制器,將連續的 streaming data 切分數據片段,並按照模板複製出新的 RDD DAG 的實例,對數據片段進行處理。
第三步,我們回過頭來看 streaming data 本身的產生。Hadoop MapReduce, Spark RDD API 進行批處理時,一般默認數據已經在 HDFS, HBase 或其它存儲上。而 streaming data —— 比如 twitter 流 —— 又有可能是在系統外實時產生的,就需要能夠將這些數據導入到 Spark Streaming 系統裏,就像 Apache Storm 的 Spout,Apache S4 的 Adapter 能夠把數據導入系統裏的作用是一致的。所以,我們將需要:
- (3) 原始數據的產生和導入。
第四步,我們考慮,有了以上 (a)(b)(c) 3 部分,就可以順利用 RDD API 處理 streaming data 了嗎?其實相對於 batch job 通常幾個小時能夠跑完來講,streaming job 的運行時間是 +∞(正無窮大)的,所以我們還將需要:
- (4) 對長時運行任務的保障,包括輸入數據的失效後的重構,處理任務的失敗後的重調。
至此,streaming data 的特點決定了,如果我們想基於 Spark Core 進行 streaming data 的處理,還需要在 Spark Core 的框架上解決剛纔列出的 (1)(2)(3)(4) 這四點問題:
二、Spark Streaming 的整體模塊劃分
根據 Spark Streaming 解決這 4 個問題的不同 focus,可以將 Spark Streaming 劃分爲四個大的模塊:
- 模塊 1:DAG 靜態定義
- 模塊 2:Job 動態生成
- 模塊 3:數據產生與導入
- 模塊 4:長時容錯
其中每個模塊涉及到的主要的類,示意如下:
這裏先不用糾結每個類的具體用途,我們將在本文中簡述,並在本系列的後續文章裏對每個模塊逐一詳述。
2.1 模塊 1:DAG 靜態定義
通過前面的描述我們知道,應該首先對計算邏輯描述爲一個 RDD DAG 的“模板”,在後面 Job 動態生成的時候,針對每個 batch,Spark Streaming 都將根據這個“模板”生成一個 RDD DAG 的實例。
DStream 和 DStreamGraph
其實在 Spark Streaming 裏,這個 RDD “模板”對應的具體的類是 DStream
,RDD
DAG “模板”對應的具體類是DStreamGraph
。而 RDD
本身也有很多子類,幾乎每個子類都有一個對應的 DStream
,如 UnionRDD
的對應是UnionDStream
。RDD
通過 transformation
連接成
RDD DAG(但 RDD DAG 在 Spark Core 裏沒有對應的具體類),DStream
也通過 transformation
連接成 DStreamGraph
。
DStream 和 RDD 的關係
既然 DStream
是 RDD
的模板,而且 DStream
和 RDD
具有相同的 transformation 操作,比如
map(), filter(), reduce() ……等等(正是這些相同的 transformation 使得 DStreamGraph
能夠忠實記錄
RDD DAG 的計算邏輯),那 RDD
和 DStream
有什麼不一樣嗎?
還真不一樣。
比如,DStream
維護了對每個產出的 RDD
實例的指針。比如下圖裏,DStream
A
在 3 個 batch 裏分別實例化了 3 個 RDD
,分別是 a[1]
, a[2]
, a[3]
,那麼 DStream
A
就保留了一個 batch
→ 所產出的 RDD
的哈希表,即包含 batch
1 → a[1]
, batch
2 → a[2]
, batch
3 → a[3]
這 3 項。
另外,能夠進行流量控制的 DStream
子類,如 ReceiverInputDStream
,還會保存關於歷次
batch 的源頭數據條數、歷次 batch 計算花費的時間等數值,用來實時計算準確的流量控制信息,這些都是記在 DStream
裏的,而 RDD
a[1]
等則不會保存這些信息。
我們在考慮的時候,可以認爲,RDD
加上
batch 維度就是 DStream
,DStream
去掉
batch 維度就是 RDD
——
就像 RDD
= DStream at batch T
不過這裏需要特別說明的是,在DStreamGraph
的圖裏,DStream(即數據)是頂點,DStream
之間的
transformation(即計算)是邊,這與 Apache Storm 等是相反的。
在 Apache Storm 的 topology 裏,頂點是計算,邊是 stream(連續的 tuple),即數據。這一點也是比較熟悉 Storm 的同學剛開始一下子不太理解 DStream 的原因–我們再重複一遍,DStream 在有向圖裏是頂點,是數據本身,而不是邊。
2.2 模塊 2:Job 動態生成
現在有了 DStreamGraph
和 DStream
,也就是靜態定義了的計算邏輯,下面我們來看
Spark Streaming 是如何將其動態調度的。
在 Spark Streaming 程序的入口,我們都會定義一個 batchDuration,就是需要每隔多長時間就比照靜態的 DStreamGraph
來動態生成一個
RDD DAG 實例。在 Spark Streaming 裏,總體負責動態作業調度的具體類是 JobScheduler
,在
Spark Streaming 程序開始運行的時候,會生成一個 JobScheduler
的實例,並被
start() 運行起來。
JobScheduler
有兩個非常重要的成員:JobGenerator
和 ReceiverTracker
。JobScheduler
將每個
batch 的 RDD DAG 具體生成工作委託給 JobGenerator
,而將源頭輸入數據的記錄工作委託給 ReceiverTracker
。
JobGenerator
維護了一個定時器,週期就是我們剛剛提到的
batchDuration,定時爲每個 batch 生成 RDD DAG 的實例。具體的,每次 RDD DAG 實際生成包含 5 個步驟:
- (1) 要求
ReceiverTracker
將目前已收到的數據進行一次 allocate,即將上次 batch 切分後的數據切分到到本次新的 batch 裏 - (2) 要求
DStreamGraph
複製出一套新的 RDD DAG 的實例,具體過程是:DStreamGraph
將要求圖裏的尾DStream
節點生成具體的 RDD 實例,並遞歸的調用尾DStream
的上游DStream
節點……以此遍歷整個DStreamGraph
,遍歷結束也就正好生成了 RDD DAG 的實例 - (3) 獲取第 1 步
ReceiverTracker
分配到本 batch 的源頭數據的 meta 信息 - (4) 將第 2 步生成的本 batch 的 RDD DAG,和第 3 步獲取到的 meta 信息,一同提交給
JobScheduler
異步執行 - (5) 只要提交結束(不管是否已開始異步執行),就馬上對整個系統的當前運行狀態做一個 checkpoint
上述 5 個步驟的調用關係圖如下:
2.3 模塊 3:數據產生與導入
下面我們看 Spark Streaming 解決第三個問題的模塊分析,即數據的產生與導入。
DStream
有一個重要而特殊的子類 ReceiverInputDStream
:它除了需要像其它 DStream
那樣在某個
batch 裏實例化 RDD
以外,還需要額外的 Receiver
爲這個 RDD
生產數據!
具體的,Spark Streaming 在程序剛開始運行時:
- (1) 由
Receiver
的總指揮ReceiverTracker
分發多個 job(每個 job 有 1 個 task),到多個 executor 上分別啓動ReceiverSupervisor
實例; - (2) 每個
ReceiverSupervisor
啓動後將馬上生成一個用戶提供的Receiver
實現的實例 —— 該Receiver
實現可以持續產生或者持續接收系統外數據,比如TwitterReceiver
可以實時爬取 twitter 數據 —— 並在Receiver
實例生成後調用Receiver.onStart()
。
(1)(2) 的過程由上圖所示,這時 Receiver
啓動工作已運行完畢。
接下來 ReceiverSupervisor
將在
executor 端作爲的主要角色,並且:
- (3)
Receiver
在onStart()
啓動後,就將持續不斷地接收外界數據,並持續交給ReceiverSupervisor
進行數據轉儲; - (4)
ReceiverSupervisor
持續不斷地接收到Receiver
轉來的數據:- 如果數據很細小,就需要
BlockGenerator
攢多條數據成一塊(4a)、然後再成塊存儲(4b 或 4c) - 反之就不用攢,直接成塊存儲(4b 或 4c)
- 這裏 Spark Streaming 目前支持兩種成塊存儲方式,一種是由
blockManagerskManagerBasedBlockHandler
直接存到 executor 的內存或硬盤,另一種由WriteAheadLogBasedBlockHandler
是同時寫 WAL(4c) 和 executor 的內存或硬盤
- 如果數據很細小,就需要
- (5) 每次成塊在 executor 存儲完畢後,
ReceiverSupervisor
就會及時上報塊數據的 meta 信息給 driver 端的ReceiverTracker
;這裏的 meta 信息包括數據的標識 id,數據的位置,數據的條數,數據的大小等信息。 - (6)
ReceiverTracker
再將收到的塊數據 meta 信息直接轉給自己的成員ReceivedBlockTracker
,由ReceivedBlockTracker
專門管理收到的塊數據 meta 信息。
這裏 (3)(4)(5)(6) 的過程是一直持續不斷地發生的,我們也將其在上圖裏標識出來。
後續在 driver 端,就由 ReceiverInputDStream
在每個
batch 去檢查 ReceiverTracker
收到的塊數據
meta 信息,界定哪些新數據需要在本 batch 內處理,然後生成相應的 RDD
實例去處理這些塊數據,這個過程在模塊
1:DAG 靜態定義
模塊2:Job
動態生成
裏描述過了。
2.4 模塊 4:長時容錯
以上我們簡述完成 Spark Streamimg 基於 Spark Core 所新增功能的 3 個模塊,接下來我們看一看第 4 個模塊將如何保障 Spark Streaming 的長時運行 —— 也就是,如何與前 3 個模塊結合,保障前 3 個模塊的長時運行。
通過前 3 個模塊的關鍵類的分析,我們可以知道,保障模塊 1 和 2 需要在 driver 端完成,保障模塊 3 需要在 executor 端和 driver 端完成。
executor 端長時容錯
先看 executor 端。
在 executor 端,ReceiverSupervisor
和 Receiver
失效後直接重啓就
OK 了,關聯是保障收到的塊數據的安全。保障了源頭塊數據,就能夠保障 RDD DAG (Spark Core 的 lineage)重做。
Spark Streaming 對源頭塊數據的保障,分爲 4 個層次,全面、相互補充,又可根據不同場景靈活設置:
- (1) 熱備:熱備是指在存儲塊數據時,將其存儲到本 executor、並同時 replicate 到另外一個 executor 上去。這樣在一個 replica 失效後,可以立刻無感知切換到另一份 replica 進行計算。實現方式是,在實現自己的 Receiver 時,即指定一下
StorageLevel
爲MEMORY_ONLY_2
或MEMORY_AND_DISK_2
就可以了。
// 1.5.2 update 這已經是默認了。
- (2) 冷備:冷備是每次存儲塊數據前,先把塊數據作爲 log 寫出到
WriteAheadLog
裏,再存儲到本 executor。executor 失效時,就由另外的 executor 去讀 WAL,再重做 log 來恢復塊數據。WAL 通常寫到可靠存儲如 HDFS 上,所以恢復時可能需要一段 recover time。
- (3) 重放:如果上游支持重放,比如 Apache Kafka,那麼就可以選擇不用熱備或者冷備來另外存儲數據了,而是在失效時換一個 executor 進行數據重放即可。
- (4) 忽略:最後,如果應用的實時性需求大於準確性,那麼一塊數據丟失後我們也可以選擇忽略、不恢復失效的源頭數據。
我們用一個表格來總結一下:
圖示 | 優點 | 缺點 | |
(1) 熱備 | 無 recover time | 需要佔用雙倍資源 | |
(2) 冷備 | 十分可靠 | 存在 recover time | |
(3) 重放 | 不佔用額外資源 | 存在 recover time | |
(4) 忽略 | 無 recover time | 準確性有損失 |
driver 端長時容錯
前面我們講過,塊數據的 meta 信息上報到 ReceiverTracker,然後交給 ReceivedBlockTracker
做具體的管理。ReceivedBlockTracker
也採用
WAL 冷備方式進行備份,在 driver 失效後,由新的 ReceivedBlockTracker
讀取
WAL 並恢復 block 的 meta 信息。
另外,需要定時對 DStreamGraph
和 JobScheduler
做 Checkpoint
,來記錄整個 DStreamGraph
的變化、和每個
batch 的 job 的完成情況。
注意到這裏採用的是完整 checkpoint 的方式,和之前的 WAL 的方式都不一樣。Checkpoint
通常也是落地到可靠存儲如
HDFS。Checkpoint
發起的間隔默認的是和 batchDuration
一致
;即每次 batch 發起、提交了需要運行的 job 後就做Checkpoint
,另外在
job 完成了更新任務狀態的時候再次做一下 Checkpoint
。
這樣一來,在 driver 失效並恢復後,可以讀取最近一次的 Checkpoint 來恢復作業的 DStreamGraph
和
job 的運行及完成狀態。
總結一下本節內容爲上述表格,可以看到,Spark Streaming 的長時容錯特性,能夠提供不重、不丟,exactly-once 的處理語義。
三、入口:StreamingContext
上面我們花了很多篇幅來介紹 Spark Streaming 的四大模塊,我們在最後介紹一下 StreamingContext
。
下面我們用這段僅 11 行的完整 quick example,來說明用戶 code 是怎麼通過 StreamingContext
與前面幾個模塊進行交互的:
所以我們看到,StreamingContext
是
Spark Streaming 提供給用戶 code 的、與前述 4 個模塊交互的一個簡單和統一的入口。
四、總結與回顧
在最後我們再把 Sark Streaming 官方 Programming Guide 的部分內容放在這裏,作爲本文的一個回顧和總結。請大家看一看,如果看懂了本文的內容,是不是讀下面這些比較 high-level 的介紹會清晰化很多 :-)
Spark Streaming is an extension of the core Spark API that enables scalable, high-throughput, fault-tolerant stream processing of live data streams. Data can be ingested from many sources like Kafka, Flume, Twitter, ZeroMQ, Kinesis, or TCP sockets, and can be processed using complex algorithms expressed with high-level functions like map, reduce, join and window. Finally, processed data can be pushed out to filesystems, databases, and live dashboards. In fact, you can apply Spark’s machine learning and graph processing algorithms on data streams.
Internally, it works as follows. Spark Streaming receives live input data streams and divides the data into batches, which are then processed by the Spark engine to generate the final stream of results in batches.
Spark Streaming provides a high-level abstraction called discretized stream or DStream, which represents a continuous stream of data. DStreams can be created either from input data streams from sources such as Kafka, Flume, and Kinesis, or by applying high-level operations on other DStreams. Internally, a DStream is represented as a sequence of RDDs.
轉自:GitHub
https://github.com/proflin/CoolplaySpark/blob/master/Spark%20Streaming%20%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E7%B3%BB%E5%88%97/0.1%20Spark%20Streaming%20%E5%AE%9E%E7%8E%B0%E6%80%9D%E8%B7%AF%E4%B8%8E%E6%A8%A1%E5%9D%97%E6%A6%82%E8%BF%B0.md
轉載請註明:人人都是數據咖 » Spark Streaming 實現思路與模塊概述