Spark Streaming快速狀態流處理

許多複雜流處理流水線程序必須將狀態保持一段時間,例如,如果你想實時瞭解網站用戶行爲,你需要將網站上各“用戶會話(user session)”信息保存爲持久狀態並根據用戶的行爲對這一狀態進行持續更新。這種有狀態的流計算可以在Spark Streaming中使用updateStateByKey 方法實現。

在Spark 1.6 中,我們通過使用新API mapWithState極大地增強對狀態流處理的支持。該新的API提供了通用模式的內置支持,而在以前使用updateStateByKey 方法來實現這一相同功能(如會話超時)需要進行手動編碼和優化。因此,mapWithState 方法較之於updateStateByKey方法,有十倍之多的性能提升。在本博文當中,我們將對mapWithState方法進行深入講解,同時提前感受後續新版本中將加入的特性。

使用mapWithState方法進行狀態流處理

Spark Streaming中最強大的特性之一是簡單的狀態流處理API及相關聯的本地化、可容錯的狀態管理能力。開發人員僅需要指定狀態的結構和更新邏輯,Spark Streaming便能夠接管集羣中狀態的分發、管理,在程序出錯時自動進行恢復並提供端到端的容錯保障。儘管現有DStream中updateStateByKey方法能夠允許用戶執行狀態計算,但使用mapWithState方法能夠讓用戶更容易地表達程序邏輯,同時讓性能提升10倍之多。讓我們通過一個例子對mapWithState方法的優勢進行闡述。 
假設我們要根據用戶歷史動作對某一網站的用戶行爲進行實時分析,對各個用戶,我們需要保持用戶動作的歷史信息,然後根據這些歷史信息得到用戶的行爲模型並輸出到下游的數據存儲當中。

在Spark Streaming中構建此應用程序時,我們首先需要獲取用戶動作流作爲輸入(例如通過Kafka或Kinesis),然後使用mapWithState 方法對輸入進行轉換操作以生成用戶模型流,最後將處理後的數據流保存到數據存儲當中。

圖片描述

在Spark Streaming中使用狀態流處理進行用戶會話維護

mapWithState方法可以通過下面的抽象方式進行理解,假設它是將用戶動作和當前用戶會話作爲輸入的一個算子(operator),基於某個輸入動作,該算子能夠有選擇地更新用戶會話,然後輸出更新後的用戶模型作爲下游操作的輸入。開發人員在定義mapWithState方法時可以指定該更新函數。

現在讓我們轉入到具體代碼來說明,首先我們定義狀態數據結構及狀態更新函數:

def stateUpdateFunction(
userId: UserId,
newData: UserAction, 
stateData: State[UserSession]): UserModel = {

    val currentSession = stateData.get()// 獲取當前會話數據
    val updatedSession = ...  // 使用newData計算更新後的會話
    stateData.update(updatedSession) // 更新會話數據     

    val userModel = ...  // 使用updatedSession計算模型
    return userModel   // 將模型發送給下游操作
}

然後,在用戶動作DStream上定義mapWithState 方法,通過創建StateSpec對象來實現,該對象中包含所有前述指定的操作。

// 用去動作構成的Stream,用戶ID作爲key
val userActions = ...  // key-value元組(UserId, UserAction)構成的stream

// 待提交的數據流
val userModels = userActions.mapWithState(StateSpec.function(stateUpdateFunction))

mapWithState的新特性和性能改進

通過前面的例子,我們已經明白其使用方式,現在讓我們再深入理解使用該新的API所帶來的特定優勢。

1. 原生支持會話超時

許多基於會話的應用程序要求具備超時機制,當某個會話在一定的時間內(如用戶沒有顯式地註銷而結束會話)沒有接收到新數據時就應該將其關閉,與使用updateStateByKey方法時需要手動進行編碼實現所不同的是,開發人員可以通過mapWithState方法直接指定其超時時間。

userActions.mapWithState(StateSpec.function(stateUpdateFunction).timeout(Minutes(10)))

除超時機制外,開發人員也可以設置程序啓動時的分區模式和初始狀態信息。

2. 任意數據都能夠發送到下游

與updateStateByKey方法不同,任意數據都可以通過狀態更新函數將數據發送到下游操作,這一點已經在前面的例子中有說明(例如通過用戶會話狀態返回用戶模型),此外,最新狀態的快照也能夠被訪問。

val userSessionSnapshots = userActions.mapWithState(statSpec).snapshotStream()

變量userSessionSnapshots 爲一個DStream,其中各個RDD爲各批(batch)數據處理後狀態更新會話的快照,該DStream與updateStateByKey方法返回的DStream是等同的。

3. 更高的性能

最後,與updateStateByKey方法相比,使用mapWithState方法能夠得到6倍的低延遲同時維護的key狀態數量要多10倍,這一性能提升和擴展性可從後面的基準測試結果得到驗證,所有的結果全部在時間間隔爲1秒的batch和相同大小的集羣中生成。

下圖比較的是mapWithState 方法和updateStateByKey 方法處理1秒的batch所消耗的平均時間,在本例中,我們爲同樣數量(從0.25~1百萬)的key保存其狀態,然後以同樣的速率(30k個更新/s)對其進行更新,如下圖所示,mapWithState方法比updateStateByKey方法的處理時間快8倍,從而允許更低的端到端延遲。

圖片描述

mapWithState方法比updateStateByKey方法的批處理時間(例如延遲)快8倍

此外,更快的處理速度使得mapWithState 方法能夠比updateStateByKey 方法管理多10倍的key(批處理間隔、集羣大小、更新頻率全部相同)。

圖片描述

mapWithState方法比updateStateByKey方法管理的key數量多10倍

Spark Streaming中其它的改進

除mapWithState方法外,Spark 1.6中的Spark Streaming組件還有其它幾項更進,部分如下:

  • Streaming UI的更進[SPARK-10885SPARK-11742]:Job失敗和其它一些詳細信息可以顯示在Streaming UI當中以便於程序調試。
  • Kinesis集成API改進[SPARK-11198SPARK-10891]:Kinesis流已經升級到可以使用KCL 1.4.0同時支持對KPL聚合記錄進行解聚合操作,另外,在確定什麼樣的數據要保存到內存中之前,任意的函數現在都可以作用於Kinesis接收器中的某個Kinesis記錄。
  • Python Streaming監聽器API[SPARK-6328]—獲取Streaming的統計信息(調度延遲、批處理時間等)
  • 支持S3寫時提前寫日誌(Write Ahead Logs ,WALs)[SPARK-11324SPARK-11141]:Spark Streaming使用提前寫日誌確保接收數據的容錯性。Spark 1.6中允許WAL應用到S3及其它不支持文件flush操作的存儲上,詳細信息請參見programming guide

如果你想試用這些新特性,你可以在Databricks官網上使用Spark 1.6,在使用時可以保留更老版本的Spark。

發佈了44 篇原創文章 · 獲贊 34 · 訪問量 104萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章