hive on spark總體設計

http://www.csdn.net/article/2015-04-24/2824545

HIve on spark 總體設計思路,盡可能重用Hive邏輯層面的功能;從省城物理計劃開始,提供一整套針對spark的實現,比如SparkCompiler,SparkTask等,這樣HIve的查詢就可以作為Spark的任務來執行了。一下是幾點主要的設計原則。

1、盡可能減少對Hive原有代碼的修改,這是和之前的Shark設計思路最大的不同,Shark對HIve的修改動太大以至于無法被Hive社區接受,HIve on spark盡可能減少改動HIve的代碼,從而不影響Hive目前對MapReduce和Tez的支持,同時,HIve on spark 保證對現有的MapReduce和Tez模式在功能性能方面不會有任何影響。

2、對于選擇Spark的用戶,應使其能夠自動的獲取hive現有和未來新增的功能。

3、盡可能降低維護成本,保證對spark依賴的松耦合。

新的計算引擎

Hive的用戶可以通過hive.execution.engine來設置計算引擎,目前該參數可選的值為mr何tez,為了實現Hive on spark,我們將spark作為參數第三個選項。要開啟Hive on Spark模式,用戶僅需將這個參數設置為Spark即可。

以Hive的表作為RDD

Spark以分布式可靠數據集合作為其數據抽象,因此我們需要將Hive的表轉換為RDD以便spark處理。本質上,hive表和spark的HadoopRDD都是HDFS上的一組文件,通過inputFormat和RecordReader讀取其中的數據,因此這個轉化為自然而然的。

使用Hive原語

這里主要是指Hive的操作符對數據進行處理。Spark為RDD提供了一些列的轉換Transformation,其中有些轉換也是面向SQL的,比如groupByKey、join等如果使用這些轉換(就如Shark所做的那樣),那就意味著我們要重新實現一些Hive已有的功能;而且當Hive增加新的功能時,我們需要響應的Hive on spark模式。有鑒于此,我們選擇將Hive的操作包裝為Function,然后應用到RDD上,這樣,我們只需要依賴較少的集中RDD的轉換,而主要的計算邏輯任由Hive提供。

由于使用了Hive的原語,因此我們需要顯示的調用一些Transformation來實現Shuffle的功能。下表中列舉了Hive on Spark使用的所有轉換。

Transformation ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?功能 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 應用場景

mapPartitionsToPair ? ? ? ? ? ? ? ? ? 將function應用到RDD的每個partition上 ? ? ? ? ? 主要的計算邏輯

union ? ? ? ? ? ? ? ? ? ? 返回兩個RDD的聯合 ? ? ? ? ? ? ? ? ?多表查詢

groupByKey ? ? ? ?按照key對RDD進行group,group后的結果不保證有序,使用Hash Partitioner ? ? ?Shuffle 使用用不要求排序的情況

sortByKey ? ? ? ? ? 按照key對RDD進行全局排序,使用Range Partitioner ? ? Shuffle時使,適用于全局排序的查詢,且由于使用了Range Partitioner,因此可以用多個reducer來實現全局排序

repartitionAndSortWithinPartitions ? 對RDD進行重新分區,并對每個分區進行排序,使用hash Partition ?shuffle時使用,適用于需要排序的情況


repartitionAndSortWithinPartitions簡單說明,這個功能由SPARK-2978引入,目的是提供一種MapReduce風格的Shuffle.雖然sortBySort也提供了排序的功能,但某些情況下我們并不需要全局有序,另外其使用的Range Partitioner對于某些Hive查詢并不適用。

物理執行計劃:

通過SparkCompiler將Operator Tree轉換為Task Tree,其中需要提交給spark執行的任務即為sparkTask,不同于MapReduce中Map+Reduce的兩階段執行模式,spark采用DAG的執行模式,因此一個sparktask包含了一個表示RDD轉換的DAG,我們將這個DAG包裝為SparkWork。執行SparkTask時,就根據SparkWork所表示的DAG計算出最終的RDD,然后通過RDD的foreachAsync來出阿發原酸,使用foreachAsync是因為我們使用了Hive原語,因此不需要RDD返回結果;此外foreachAsync異步提交任務便于我們對任務進行監控。

SparkContext聲明周期

sparkContext是用戶與spark集群進行交互的接口,HIve on spark應該為每個用戶會話創建一個SparkContext。但是Spark目前的使用方式假設SparkContext的生命周期是spark應用級別的,而且目前同一個JVM中不能創建多個SparkContext,這明顯無法滿足HiveServer2的應用場景,因為多個客戶端需要通過一個HiveServer2來提供服務。鑒于此,我們需要在單獨JVM中啟動SparkContext,并通過RPC與遠程的SparkContext進行通信。


任務監控與統計信息收集

spark提供了SparkListener接口來監聽任務執行期間的各種事件,因此我們可以實現一個Listener來監控任務執行進度以及收集任務級別的統計的統計信息(目前任務級別的統計由sparkListener采集,任務進度則由spark提供專門的api來監控)另外Hive還提供了Operatior級別的統計數據信息,比如讀取的行數等。在MapReduce模式下,這些信息通過Hadoop Counter收集。我們可以使用Spark提供的Accumulator來實現該功能。


測試

除了一般的單元測試以外,hive還提供了Qfile Test,即圓形一些事先定義好的查詢,并根據結果判斷測試是否通過。HIve on spark的Qfile Test應該盡可能接近真實的spark部署環境。目前我們采用的是local-cluster方式(該不是)。。。。

實現細節:

sparkTask的生成執行



我們通過一個例子看下一個簡單的兩表JOIN查詢如何被轉換成為sparkTask并被執行。下圖左半部分展示了這個查詢的operation tree,以及該operation Tree如何被轉化成SparkTask;右半部分展示了該SparkTask執行時如何得到最終的RDD并通過foreachAsync提交spark任務

SparkCompiler遍歷Operator Tree,將其劃分為不同的MapWork和reducework,Mapwork為根節點,總是由TableScanOperator(hive中對表進行掃描操作符)開始;后續的work均為reducework。ReduceSinkOperator(Hive中進行Shuffle輸出的操作符)用來標記兩個Work之間的界限,出現ReduceSinkOperator表示當前work到下一個work之間的數據需要進行shuffle,因此當我們發現FileSinkOperator(hive中將結果輸出到文件的操作符)的work為葉子節點。與MapReduce最大的不同在于,我們并不要求ReduceWork一定是葉子節點,即ReduceWork之后可以連接更多的reducework,并在同一個SParkTask中執行。

這個查詢的OperatorTree 被轉化為兩個MapWork和一個ReduceWork。在執行SparkTask時,首先根據MapWork來生成最底層的HadoopRDD,然后將各個MapWork和ReduceWork包裝成Function應用到RDD上,在有依賴的Work之間,需要顯示的調用shuffle轉換,具體選用哪種shuffle則根據查詢的類型來確定。另外,由于這個例子涉及多表查詢,因此在shuffle之前還要對RDD進行Union,經過一系列的轉換后,得到最終的RDD,并通過foreachAsync提交到Spark集群上進行計算。



運行模式

RSC工作原理

Hive on Spark支持兩種運行模式;本地和遠程。當用戶吧Spark Master URL設置為local時,采用本地模式;其余情況則采用遠程模式。本地模式下,SparkContext與客戶端運行在同一個JVM中,遠程模式下,SparkContext運行在一個獨立的JVM中,本地模式提供了主要為了調試,一般用戶不應該選擇該模式。因此我們這里主要介紹遠程模式(Remote SparkContext。RSC)


用戶的每個Session都會創建一個SparkClient,SparkClient會啟動RemoteDriver進程,并由RemoteDriver創建SparkContext。SparkTask執行時,通過session提交任務,任務的主體就是對應的SparkWork。SparkClient將任務提交給RemoteDriver,并返回一個SparkJobRef,通過該SparkJobRef,客戶端可以監控任務的進度,進行錯誤處理,以及采集統計信息等。由于最終的RDD計算沒有返回結果,因此客戶端只需要監控執行進度而不需要處理返回值。RemoteDriver通過SparkListenner收集任務級別的統計數據,通過Accumulator收集Operator級別的統計數據(Accumulator被包裝為SparkCounter),并在任務結束時,返回給SparkClient。

SparkClient與RemoteDriver之間通過Netty的RPC進行通信。除了提交任務,SparkClient還提供了諸如添加jar包,獲取集群信息的接口。如果客戶端需要使用更一般的SparkContext的功能,可以自定義一個任務并通過SparkClient發送到Driver上執行。

理論上來說,HIve on Spark對Spark集群的部署方式沒有特別的要求,除了local以外,RemoteDriver可以連接到任務的Spark集群來執行任務。在我們的測試中,HIve on Spark在Standalone和spark on yarn的計算上都能正常工作(需要動態添加jar包的查詢在yarn-cluster模式下還不能運行)

優化

Map join

Map join是Hive中一個重要的優化,其原理是,如果參與join比較小的表可以存放如內存,因為這些小表在內存中生成Hash Table,這樣較大的表只需要通過一個MapWork唄掃描一次,然后與內存中的Hash Table進行join了,生氣了Shuffle和ReduceWork的開銷。在MapReduce模式下,通過一個在客戶端本地執行的任務來小表生成Hash table,并保存在文件系統上。后續的MapWork首先將Hash Table上傳至Distibuted Cache中,最后只要讀取大表和Distributed cache中的數據進行join就可以。

Hive on spark 對Map Join的實現與Map Reduce不同。當初我們考慮使用spark提供的廣播功能來把小表的Hash table分發到各個節點上。使用廣播的有點是spark采用高效的廣播算法,其性能應該優于使用distributed cache。而使用廣播的缺點會為Driver何計算及誒點帶來很大的內存開銷。為了使用廣播,Hash table的數據需要先被傳送到Driver端,然后由Driver進行廣播;而且即使在廣播之后,Driver仍需保留部分數據,以便應對計算節點的錯誤,雖然支持spill,但廣播數據仍會加劇Driver的內存壓力。此外廣播相對的開發成本比較高,不利于對已有的代碼的服用。

因此Hive on Spark選擇類似于Distributed cache的方式來實現Map join,而且為小表生成Hash table的任務可以分布式的執行,進一步減輕客戶端的壓力。

不同于MapReduce,對于Hive on spark而言,LocalWork只是為了提供一些優化時必要的信息,并不會真正被執行。對于小表的掃描以獨立的SparkTask分布式地執行,為此,我們也實現了能夠分布式運行HashTableSinkOperator(Hive中輸出小表Hash Table的操作符),其主要原理是通過提高HDFS Replication Factor的方式,是的生成的HashTable能夠被每個節點在本地訪問。

雖然目前采取了類似Distributed的這種實現方式,但如果再后期的測試中發現廣播的方式確實能帶來較大的性能提升,而且其引入的內存開銷可以被接受,我們也會考慮改用廣播來實現Map Join

Table cache

Spark的一個優勢是可以利用充分利用內存,允許用戶顯示地把一個RDD保存到內存或者磁盤上,以便于在多次訪問時候提高性能。另外在目前的RDD轉換模式弘,一個RDD的數據是無法同時被多個下游使用,當一個RDD需要通過不同的轉換得到不同的子節點時,就要被計算多次。這時我們應該使用cache來避免重復計算。

在Shark和SparkSQL中,都允許用戶顯式吧一張表cache來提高對該表的查詢性能。比如以下查詢,在





在這種情況下,對應的SparkWork中,一個MapWork/ReduceWork會有多個下游的Work,如果不進行cache,那么共享的數據就會被計算多次。為了避免這種情況,我們會將這些MapWork/ReduceWork復制成多個,每個對應一個下游的Work,并對其共享的數據進行cache(由于IOContext的同步問題,該工功能尚未完成)

更為一般的應用場景是一張表在查詢中被使用了多次的情況,Hive on spark 目前還不會針對這種查詢進行cache,


初步性能測試:

測試集群由10臺虛擬機組成,測試數據為320GB的TPC-DS數據。目前的測試用例為6條,包含了自定義的查詢以及TPC-DS數據集合。目前的測試用例為6條,包含了自定義的查詢以及TPC-DS中兩條查詢。由于Hive主要用于處理ETL查詢,因此我們在TPC-DS中選取用例時,選用較為接近ETL查詢用例(TPC-DS的用例主要針對交互式查詢,impala,sparkSQL等引擎更合適此類查詢),主要針對Hive on Spark 和Hive on Tez進行性能對比


Hive on Spark vs. Hive on Tez

圖中橫坐標為各個測試用例,縱坐標為所用時間,以秒為單位。

總結:

Hive on spark由多家公司協作開發,從項目開始以來,受到社區的廣泛關注。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容