SparkSQL-MR、RDD、DataFrame三個程式設計模型演進
最近看了一些很好的部落格文章,轉載過來記錄一下學習過程。
SparkSQL 歷史回顧
對SparkSQL瞭解的童鞋或多或少聽說過Shark,不錯,Shark就是SparkSQL的前身。2011的時候,Hive可以說是SQL On Hadoop的唯一選擇,負責將SQL解析成MR任務執行在大資料上,實現互動式查詢、報表等功能。就在那個時候,Spark社群的小夥伴就意識到可以使用Spark作為執行引擎替換Hive中的MR,這樣可以使Hive的執行效率得到極大提升。這個思想的產物就是Shark,所以從實現功能上來看,Shark更像一個Hive On Spark實現版本。
改造完成剛開始,Shark確實比Hive的執行效率有了極大提升。然而,隨著改造的深入,發現因為Shark繼承了大量Hive程式碼導致新增優化規則等變得異常困難,優化的前景不再那麼樂觀。在意識到這個問題之後,Spark社群經過一段時間激烈的思想鬥爭之後,還是毅然決然的在2014年徹底放棄了Shark,轉向SparkSQL。
因此可以理解為SparkSQL是一個全新的專案,接下來將會帶大家一起走近SparkSQL的世界,從SparkSQL體系的最頂端走向最底層,尋根問底,深入理解SparkSQL是如何工作的。
SparkSQL 體系結構
SparkSQL體系結構如下圖所示,整體由上到下分為三層:程式設計模型層、執行任務優化層以及任務執行引擎層,其中SparkSQL程式設計模型可以分為SQL和DataFrame兩種;執行計劃優化又稱為Catalyst,該模組負責將SQL語句解析成AST(邏輯執行計劃),並對原始邏輯執行計劃進行優化,優化規則分為基於規則的優化策略和基於代價的優化策略兩種,最終輸出優化後的物理執行計劃;任務執行引擎就是Spark核心,負責根據物理執行計劃生成DAG,在任務排程系統的管理下分解為任務集並分發到叢集節點上載入資料執行,Tungsten基於對記憶體和CPU的效能優化,使得Spark能夠更好地利用當前硬體條件提升效能,詳情可以閱讀
SparkSQL系列文章會按照體系結構由上至下詳細地進行說明,本篇下面會重點講解程式設計介面DataFrame,後面會利用M篇文章分析Catalyst的工作原理,再後面會利用N篇文章分析Spark核心工作原理。
SparkSQL 程式設計模型 - DataFrame
說到計算模型,批處理計算從最初提出一直到現在,一共經歷了兩次大的變革,第一次變革是從MR程式設計模式到RDD程式設計模型,第二次則是從RDD程式設計模式進化到DataFrame模式。
第一次變革:MR程式設計模型 -> DAG程式設計模型
和MR計算模型相比,DAG計算模型有很多改進:
1. 可以支援更多的運算元,比如filter運算元、sum運算元等,不再像MR只支援map和reduce兩種
2. 更加靈活的儲存機制,RDD可以支援本地硬碟儲存、快取儲存以及混合儲存三種模式,使用者可以進行選擇。而MR目前只支援HDFS儲存一種模式。很顯然,HDFS儲存需要將中間資料儲存三份,而RDD則不需要,這是DAG程式設計模型效率高的一個重要原因之一。
3. DAG模型帶來了更細粒度的任務併發,不再像MR那樣每次起個任務就要起個JVM程序,重死了;另外,DAG模型帶來了另一個利好是很好的容錯性,一個任務即使中間斷掉了,也不需要從頭再來一次。
4. 延遲計算機制一方面可以使得同一個stage內的操作可以合併到一起落在一塊資料上,而不再是所有資料先執行a操作、再掃描一遍執行b操作,太浪費時間。另一方面給執行路徑優化留下了可能性,隨便你怎麼優化…
所有這些改進使得DAG程式設計模型相比MR程式設計模型,效能可以有10~100倍的提升!然而,DAG計算模型就很完美嗎?要知道,使用者手寫的RDD程式基本或多或少都會有些問題,效能也肯定不會是最優的。如果沒有一個高手指點或者優化,效能依然有很大的優化潛力。這就是促成了第二次變革,從DAG程式設計模型進化到DataFrame程式設計模型。
第二次變革:DAG程式設計模型 -> DataFrame程式設計模型
相比RDD,DataFrame增加了scheme概念,從這個角度看,DataFrame有點類似於關係型資料庫中表的概念。可以根據下圖對比RDD與DataFrame資料結構的差別:
直觀上看,DataFrame相比RDD多了一個表頭,這個小小的變化帶來了很多優化的空間:
1. RDD中每一行紀錄都是一個整體,因此你不知道內部資料組織形式,這就使得你對資料項的操作能力很弱。表現出來就是支援很少的而且是比較粗粒度的運算元,比如map、filter運算元等。而DataFrame將一行切分了多個列,每個列都有一定的資料格式,這與資料庫表模式就很相似了,資料粒度相比更細,因此就能支援更多更細粒度的運算元,比如select運算元、groupby運算元、where運算元等。更重要的,後者的表達能力要遠遠強於前者,比如同一個功能用RDD和DataFrame實現:
2. DataFrame的Schema的存在,資料項的轉換也都將是型別安全的,這對於較為複雜的資料計算程式的除錯是十分有利的,很多資料型別不匹配的問題都可以在編譯階段就被檢查出來,而對於不合法的資料檔案,DataFrame也具備一定分辨能力。
3. DataFrame schema的存在,開闢了另一種資料儲存形式:列式資料儲存。列式儲存是相對於傳統的行式儲存而言的,簡單來講,就是將同一列的所有資料物理上儲存在一起。對於列式儲存和行式儲存可以參考下圖:
列式儲存有兩個重要的作用,首先,同一種類型的資料儲存在一起可以很好的提升資料壓縮效率,因為越“相似”的資料,越容易壓縮。資料壓縮可以減少儲存空間需求,還可以減少資料傳輸過程中的頻寬需求,這對於類似於Spark之類的大記憶體計算引擎來講,會帶來極大的益處;另外,列式儲存還可以有效減少查詢過程中的實際IO,大資料領域很多OLAP查詢業務通常只會檢索部分列值,而不是粗暴的select * ,這樣列式儲存可以有效執行’列值裁剪’,將不需要查詢的列直接跳過。
4. DAG程式設計模式都是使用者自己寫RDD scala程式,自己寫嘛,必然或多或少會有效能提升的空間!而DataFrame程式設計模式集成了一個優化神奇-Catalyst,這玩意類似於MySQL的SQL優化器,負責將使用者寫的DataFrame程式進行優化得到最優的執行計劃(下文會講),比如最常見的謂詞下推優化。很顯然,優化後的執行計劃相比於手寫的執行計劃性能當然會來的好一些。下圖是官方給出來的測試對比資料(測試過程是在10billion資料規模下進行過濾聚合):
個人覺得,RDD和DataFrame的關係類似於組合語言和Java語言的關係,同一個功能,如果你用匯編實現的話,一方面會寫的很長,另一方面寫的程式碼可能還不是最優的,可謂是又臭又長。而Java語言有很多高階語意,可以很方便的實現相關功能,另一方面經過JVM優化後會更加高效。
DataFrame 使用
參考文獻:
1. A Tale of Three Apache Spark APIs: RDDs, DataFrames, and Datasets