03.08 「大數據」(八十一)Spark之SparkSQL運行架構

【導讀:數據是二十一世紀的石油,蘊含巨大價值,這是·情報通·大數據技術系列第[81]篇文章,歡迎閱讀和收藏】

1 SparkSQL 的發展歷程

1.1 Hive and Shark

SparkSQL 的前身是 Shark ,給熟悉 RDBMS 但又不理解 MapReduce 的技術人員提供快速上手的工具, Hive 應運而生,它是當時唯一運行在 Hadoop 上的 SQL-on-Hadoop 工具。但是 MapReduce 計算過程中大量的中間磁盤落地過程消耗了大量的 I/O ,降低的運行效率,為了提高 SQL-on-Hadoop 的效率,大量的 SQL-on-Hadoop 工具開始產生,其中表現較為突出的是:

l MapR 的 Drill

l Cloudera 的 Impala

l Shark

其中 Shark 是伯克利實驗室 Spark 生態環境的組件之一,它修改了內存管理、物理計劃、執行三個模塊,並使之能運行在 Spark 引擎上,從而使得 SQL 查詢的速度得到 10-100 倍的提升。

「大數據」(八十一)Spark之SparkSQL運行架構

1.2 Shark 和 SparkSQL

隨著 Spark 的發展,對於野心勃勃的 Spark 團隊來說, Shark 對於 Hive 的太多依賴(如採用 Hive 的語法解析器、查詢優化器等等),制約了 Spark 的 One Stack Rule Them All 的既定方針,制約了 Spark 各個組件的相互集成,所以提出了 SparkSQL 項目。 SparkSQL 拋棄原有 Shark 的代碼,汲取了 Shark 的一些優點,如內存列存儲( In-Memory Columnar Storage )、 Hive 兼容性等,重新開發了 SparkSQL 代碼;由於擺脫了對 Hive 的依賴性, SparkSQL 無論在數據兼容、性能優化、組件擴展方面都得到了極大的方便,真可謂 “ 退一步,海闊天空 ” 。

l 數據兼容方面 不但兼容 Hive ,還可以從 RDD 、 parquet 文件、 JSON 文件中獲取數據,未來版本甚至支持獲取 RDBMS 數據以及 cassandra 等 NOSQL 數據;

l 性能優化方面 除了採取 In-Memory Columnar Storage 、 byte-code generation 等優化技術外、將會引進 Cost Model 對查詢進行動態評估、獲取最佳物理計劃等等;

l 組件擴展方面 無論是 SQL 的語法解析器、分析器還是優化器都可以重新定義,進行擴展。

2014 年 6 月 1 日 Shark 項目和 SparkSQL 項目的主持人 Reynold Xin 宣佈:停止對 Shark 的開發,團隊將所有資源放 SparkSQL 項目上,至此, Shark 的發展畫上了句話,但也因此發展出兩個直線: SparkSQL 和 Hive on Spark 。

其中 SparkSQL 作為 Spark 生態的一員繼續發展,而不再受限於 Hive ,只是兼容 Hive ;而 Hive on Spark 是一個 Hive 的發展計劃,該計劃將 Spark 作為 Hive 的底層引擎之一,也就是說, Hive 將不再受限於一個引擎,可以採用 Map-Reduce 、 Tez 、 Spark 等引擎。


1.3 SparkSQL 的性能

SparkSQL 在下面幾點做了優化:

1.3.1 內存列存儲( In-Memory Columnar Storage )

SparkSQL 的表數據在內存中存儲不是採用原生態的 JVM 對象存儲方式,而是採用內存列存儲。

該存儲方式無論在空間佔用量和讀取吞吐率上都佔有很大優勢。

對於原生態的 JVM 對象存儲方式,每個對象通常要增加 12-16 字節的額外開銷,對於一個 270MB 的 TPC-H lineitem table 數據,使用這種方式讀入內存,要使用 970MB 左右的內存空間(通常是 2 ~ 5 倍於原生數據空間);另外,使用這種方式,每個數據記錄產生一個 JVM 對象,如果是大小為 200B 的數據記錄, 32G 的堆棧將產生 1.6 億個對象,這麼多的對象,對於 GC 來說,可能要消耗幾分鐘的時間來處理( JVM 的垃圾收集時間與堆棧中的對象數量呈線性相關)。顯然這種內存存儲方式對於基於內存計算的 Spark 來說,很昂貴也負擔不起。

對於內存列存儲來說,將所有原生數據類型的列採用原生數組來存儲,將 Hive 支持的複雜數據類型(如 array 、 map 等)先序化後並接成一個字節數組來存儲。這樣,每個列創建一個 JVM 對象,從而導致可以快速的 GC 和緊湊的數據存儲;額外的,還可以使用低廉 CPU 開銷的高效壓縮方法(如字典編碼、行長度編碼等壓縮方法)降低內存開銷;更有趣的是,對於分析查詢中頻繁使用的聚合特定列,性能會得到很大的提高,原因就是這些列的數據放在一起,更容易讀入內存進行計算。

1.3.2 字節碼生成技術( bytecode generation ,即 CG )

在數據庫查詢中有一個昂貴的操作是查詢語句中的表達式,主要是由於 JVM 的內存模型引起的。比如如下一個查詢:

SELECT a + b FROM table

在這個查詢裡,如果採用通用的 SQL 語法途徑去處理,會先生成一個表達式樹(有兩個節點的 Add 樹,參考後面章節),在物理處理這個表達式樹的時候,將會如圖所示的 7 個步驟:

1. 調用虛函數 Add.eval() ,需要確認 Add 兩邊的數據類型

2. 調用虛函數 a.eval() ,需要確認 a 的數據類型

3. 確定 a 的數據類型是 Int ,裝箱

4. 調用虛函數 b.eval() ,需要確認 b 的數據類型

5. 確定 b 的數據類型是 Int ,裝箱

6. 調用 Int 類型的 Add

7. 返回裝箱後的計算結果

其中多次涉及到虛函數的調用,虛函數的調用會打斷 CPU 的正常流水線處理,減緩執行。

Spark1.1.0 在 catalyst 模塊的 expressions 增加了 codegen 模塊,如果使用動態字節碼生成技術(配置 spark.sql.codegen 參數), SparkSQL 在執行物理計劃的時候,對匹配的表達式採用特定的代碼,動態編譯,然後運行。

2 SparkSQL 運行架構

類似於關係型數據庫, SparkSQL 也是語句也是由 Projection ( a1 , a2 , a3 )、 Data Source ( tableA )、 Filter ( condition )組成,分別對應 sql 查詢過程中的 Result 、 Data Source 、 Operation ,也就是說 SQL 語句按 Result-->Data Source-->Operation 的次序來描述的。

當執行 SparkSQL 語句的順序為:

1 、對讀入的 SQL 語句進行解析( Parse ),分辨出 SQL 語句中哪些詞是關鍵詞(如 SELECT 、 FROM 、 WHERE ),哪些是表達式、哪些是 Projection 、哪些是 Data Source 等,從而判斷 SQL 語句是否規範;

2 、將 SQL 語句和數據庫的數據字典(列、表、視圖等等)進行綁定( Bind ),如果相關的 Projection 、 Data Source 等都是存在的話,就表示這個 SQL 語句是可以執行的;

3 、一般的數據庫會提供幾個執行計劃,這些計劃一般都有運行統計數據,數據庫會在這些計劃中選擇一個最優計劃( Optimize );

4 、計劃執行( Execute ),按 Operation-->Data Source-->Result 的次序來進行的,在執行過程有時候甚至不需要讀取物理表就可以返回結果,比如重新運行剛運行過的 SQL 語句,可能直接從數據庫的緩衝池中獲取返回結果。


分享到:


相關文章: