Spark1.1.0中變化較大是sparkSQL和MLlib,sparkSQL1.1.0主要的變動有:
- 增加了JDBC/ODBC Server(ThriftServer),用戶可以在應用程序中連接到SparkSQL并使用其中的表和緩存表。
- 增加了對JSON文件的支持
- 增加了對parquet文件的本地優(yōu)化
- 增加了支持將python、scala、java的lambda函數(shù)注冊成UDF,并能在SQL中直接引用
- 引入了動態(tài)字節(jié)碼生成技術(bytecode generation,即CG),明顯地提升了復雜表達式求值查詢的速率。
- 統(tǒng)一API接口,如sql()、SchemaRDD生成等。
- ......
下面分十個小節(jié)來介紹sparkSQL1.1.0的架構(gòu)和使用,希望各位讀者joy it!
第一節(jié):為什么sparkSQL 為本篇,介紹sparkSQL的發(fā)展歷程和性能
第二節(jié):sparkSQL架構(gòu) 介紹catalyst,然后介紹sqlContext、hiveContext的運行架構(gòu)及區(qū)別
第三節(jié):sparkSQL組件之解析 介紹sparkSQL運行架構(gòu)中的各個組件的功能和實現(xiàn)
第四節(jié):深入了解sparkSQL之運行 使用hive/console更深入了解各種計劃是如何生成的
第五節(jié):測試環(huán)境之搭建 介紹后面章節(jié)將使用的環(huán)境搭建和測試數(shù)據(jù)
第六節(jié):sparkSQL之基礎應用 介紹sqlContext的RDD、Json、parquet使用以及hiveContext使用
第七節(jié):ThriftServer和CLI 介紹TriftServer和CLI的使用,以及如何使用JDBC訪問sparkSQL數(shù)據(jù)
第八節(jié):sparkSQL之綜合應用 介紹sparkSQL和MLlib、sparkSQL和GraphX結(jié)合使用
第九節(jié):sparkSQL之調(diào)優(yōu) 介紹CG、壓縮、序化器、緩存之使用
第十節(jié):總結(jié)
至于與hive的兼容性、具體的SQL語法以后有機會再介紹。
本篇為第一節(jié),為什么sparkSQL?
1:sparkSQL的發(fā)展歷程。
A:hive and shark
sparkSQL的前身是shark。在hadoop發(fā)展過程中,為了給熟悉RDBMS但又不理解MapReduce的技術人員提供快速上手的工具,hive應運而生,是當時唯一運行在hadoop上的SQL-on-Hadoop工具。但是,MapReduce計算過程中大量的中間磁盤落地過程消耗了大量的I/O,降低的運行效率,為了提高SQL-on-Hadoop的效率,大量的SQL-on-Hadoop工具開始產(chǎn)生,其中表現(xiàn)較為突出的是:
- MapR的Drill
- Cloudera的Impala
- Shark
其中Shark是伯克利實驗室spark生態(tài)環(huán)境的組件之一,它修改了下圖所示的右下角的內(nèi)存管理、物理計劃、執(zhí)行三個模塊,并使之能運行在spark引擎上,從而使得SQL查詢的速度得到10-100倍的提升。
但是,隨著Spark的發(fā)展,對于野心勃勃的Spark團隊來說,Shark對于hive的太多依賴(如采用hive的語法解析器、查詢優(yōu)化器等等),制約了Spark的One Stack rule them all的既定方針,制約了spark各個組件的相互集成,所以提出了sparkSQL項目。SparkSQL拋棄原有Shark的代碼,汲取了Shark的一些優(yōu)點,如內(nèi)存列存儲(In-Memory Columnar Storage)、Hive兼容性等,重新開發(fā)了SparkSQL代碼;由于擺脫了對hive的依賴性,SparkSQL無論在數(shù)據(jù)兼容、性能優(yōu)化、組件擴展方面都得到了極大的方便,真可謂“退一步, 海闊天空”。
- 數(shù)據(jù)兼容方面 不但兼容hive,還可以從RDD、parquet文件、JSON文件中獲取數(shù)據(jù),未來版本甚至支持獲取RDBMS數(shù)據(jù)以及cassandra等NOSQL數(shù)據(jù)
- 性能優(yōu)化方面 除了采取In-Memory Columnar Storage、byte-code generation等優(yōu)化技術外、將會引進Cost Model對查詢進行動態(tài)評估、獲取最佳物理計劃等等
- 組件擴展方面 無論是SQL的語法解析器、分析器還是優(yōu)化器都可以重新定義,進行擴展
2014年6月1日,Shark項目和SparkSQL項目的主持人Reynold Xin宣布:停止對Shark的開發(fā),團隊將所有資源放sparkSQL項目上,至此,Shark的發(fā)展畫上了句話,但也因此發(fā)展出兩個直線:SparkSQL和hive on spark。
其中sparkSQL作為Spark生態(tài)的一員繼續(xù)發(fā)展,而不再受限于hive,只是兼容hive;而hive on spark是一個hive的發(fā)展計劃,該計劃將spark作為hive的底層引擎之一,也就是說,hive將不再受限于一個引擎,可以采用map-reduce、Tez、spark等引擎。
2:sparkSQL的性能
shark的出現(xiàn),使得SQL-on-Hadoop的性能比hive有了10-100倍的提高:
那么,擺脫了hive的限制,sparkSQL的性能又有怎么樣的表現(xiàn)呢?雖然沒有shark相對于hive那樣矚目地性能提升,但也表現(xiàn)得非常優(yōu)異:
為什么sparkSQL的性能會得到怎么大的提升呢?主要sparkSQL在下面幾點做了優(yōu)化:
A:內(nèi)存列存儲(In-Memory Columnar Storage)
sparkSQL的表數(shù)據(jù)在內(nèi)存中存儲不是采用原生態(tài)的JVM對象存儲方式,而是采用內(nèi)存列存儲,如下圖所示。
該存儲方式無論在空間占用量和讀取吞吐率上都占有很大優(yōu)勢。
對于原生態(tài)的JVM對象存儲方式,每個對象通常要增加12-16字節(jié)的額外開銷,對于一個270MB的TPC-H lineitem table數(shù)據(jù),使用這種方式讀入內(nèi)存,要使用970MB左右的內(nèi)存空間(通常是2~5倍于原生數(shù)據(jù)空間);另外,使用這種方式,每個數(shù)據(jù)記錄產(chǎn)生一個JVM對象,如果是大小為200B的數(shù)據(jù)記錄,32G的堆棧將產(chǎn)生1.6億個對象,這么多的對象,對于GC來說,可能要消耗幾分鐘的時間來處理(JVM的垃圾收集時間與堆棧中的對象數(shù)量呈線性相關)。顯然這種內(nèi)存存儲方式對于基于內(nèi)存計算的spark來說,很昂貴也負擔不起。
對于內(nèi)存列存儲來說,將所有原生數(shù)據(jù)類型的列采用原生數(shù)組來存儲,將Hive支持的復雜數(shù)據(jù)類型(如array、map等)先序化后并接成一個字節(jié)數(shù)組來存儲。這樣,每個列創(chuàng)建一個JVM對象,從而導致可以快速的GC和緊湊的數(shù)據(jù)存儲;額外的,還可以使用低廉CPU開銷的高效壓縮方法(如字典編碼、行長度編碼等壓縮方法)降低內(nèi)存開銷;更有趣的是,對于分析查詢中頻繁使用的聚合特定列,性能會得到很大的提高,原因就是這些列的數(shù)據(jù)放在一起,更容易讀入內(nèi)存進行計算。
B:字節(jié)碼生成技術(bytecode generation,即CG)
在數(shù)據(jù)庫查詢中有一個昂貴的操作是查詢語句中的表達式,主要是由于JVM的內(nèi)存模型引起的。比如如下一個查詢:
在這個查詢里,如果采用通用的SQL語法途徑去處理,會先生成一個表達式樹(有兩個節(jié)點的Add樹,參考后面章節(jié)),在物理處理這個表達式樹的時候,將會如圖所示的7個步驟:
- 調(diào)用虛函數(shù)Add.eval(),需要確認Add兩邊的數(shù)據(jù)類型
- 調(diào)用虛函數(shù)a.eval(),需要確認a的數(shù)據(jù)類型
- 確定a的數(shù)據(jù)類型是Int,裝箱
- 調(diào)用虛函數(shù)b.eval(),需要確認b的數(shù)據(jù)類型
- 確定b的數(shù)據(jù)類型是Int,裝箱
- 調(diào)用Int類型的Add
- 返回裝箱后的計算結(jié)果
其中多次涉及到虛函數(shù)的調(diào)用,虛函數(shù)的調(diào)用會打斷CPU的正常流水線處理,減緩執(zhí)行。
Spark1.1.0在catalyst模塊的expressions增加了codegen模塊,如果使用動態(tài)字節(jié)碼生成技術(配置spark.sql.codegen參數(shù)),sparkSQL在執(zhí)行物理計劃的時候,對匹配的表達式采用特定的代碼,動態(tài)編譯,然后運行。如上例子,匹配到Add方法:
然后,通過調(diào)用,最終調(diào)用:
最終實現(xiàn)效果類似如下偽代碼:
- val a: Int = inputRow.getInt(0)
- val b: Int = inputRow.getInt(1)
- val result: Int = a + b
- resultRow.setInt(0, result)
對于Spark1.1.0,對SQL表達式都作了CG優(yōu)化,具體可以參看codegen模塊。CG優(yōu)化的實現(xiàn)主要還是依靠scala2.10的運行時放射機制(runtime reflection)。對于SQL查詢的CG優(yōu)化,可以簡單地用下圖來表示:
C:scala代碼優(yōu)化
另外,sparkSQL在使用Scala編寫代碼的時候,盡量避免低效的、容易GC的代碼;盡管增加了編寫代碼的難度,但對于用戶來說,還是使用統(tǒng)一的接口,沒受到使用上的困難。下圖是一個scala代碼優(yōu)化的示意圖: