傳統的MapReduce框架慢在那里


為什么之前的MapReduce系統比較慢

常理上有幾個理由使得MapReduce框架慢於MPP數據庫:

  1. 容錯所引入的昂貴數據實體化(data materialization)開銷。
  2. 孱弱的數據布局(data layout),比如缺少索引。
  3. 執行策略的開銷[1 2]。

而我們對於Hive的實驗也進一步證明了上述的理由,但是通過對Hive“工程上”的改進,如改變存儲引擎(內存存儲引擎)、改善執行架構(partial DAG execution)能夠縮小此種差距。同時我們也發現一些MapReduce實現的細節會對性能有巨大的影響,如任務調度的開銷,如果減小調度開銷將極大地提高負載的均衡性。

中間結果輸出:類似於Hive這樣的基於MapReduce的查詢引擎,往往會將中間結果實體化(materialize)到磁盤上:

  • 在MapReduce任務內部,為了防止Reduce任務的失敗,Map通常會把結果存儲在磁盤上。
  • 通常一些查詢在翻譯到MapReduce任務的時候,往往會產生多個stage,而這些串聯的stage則又依賴於底層文件系統(如HDFS)來存儲每一個stage的輸出結果。

對於第一種情況,Map的輸出結果存儲在磁盤上是為了確保能夠有足夠的空間來存儲這些大數據批量任務的輸出。而Map的輸出並不會復制到不同的節點上去,因此如果執行Map任務的節點失效的話仍會造成數據丟失[3]。由此可以推出,如果將這部分輸出數據緩存在內存中,而不是全部輸出到磁盤上面也是合理的。Shark Shuffle的實現正是應用了此推論,將Map的輸出結果存儲在內存中,極大地提高Shuffle的吞吐量。通常對於聚合(aggregation)和過濾之類的查詢,它們的輸出結果往往遠小於輸入,這種設計是非常合理的。而SSD的流行,也會極大地提高隨機讀取的性能,對於大數據量的Shuffle,能夠獲得較大的吞吐量,同時也擁有比內存更大的空間。

對於第二種情況,一些執行引擎擴展了MapReduce的執行模型,將MapReduce的執行模型泛化成更為通用的執行計划圖(task DAG),可以將多stage的任務串聯執行而無需將stage中間結果輸出到HDFS中去,這些引擎包括Dryad[4], Tenzing[5]和Spark[6]。

數據格式和布局(layout) 由於MapReduce單純的Schema-on-read的處理方式會引起較大的處理開銷,許多系統在MapReduce模型內部設計和使用了更高效的存儲結構來加速查詢。Hive本身支持“分區表(table partitions)”(一種基本的類索引系統,它將特定的鍵段存儲在特定的文件中,可以避免對於整個表的掃描),類似於磁盤數據的列式存儲結構[7]。在Shark中我們更進一步地采用了基於內存的列式存儲結構,Shark在實現此結構時並沒有修改Spark的代碼,而是簡單地將一組列式元組存儲為Spark內的一條記錄,而對於列式元組內的結構則有Shark負責解析。

另一個Spark獨有的特性是能夠控制數據在不同節點上的分區,這為Shark帶來了一種新的功能:對表進行聯合分區(co-partition)

最后,對於RDD我們還未挖掘其隨機讀取的能力,雖然對於寫入操作,RDD只能支持粗粒度的操作,但對於讀取操作,RDD可以精確到每一條記錄[6],這使得RDD可以用來作為索引, Tenzing 可以用此來作為join操作的遠程查詢表(remote-lookup)

執行策略: Hive在數據Shuffle之前花費了大量的時間用來排序,同時將MapReduce結果輸出到HDFS上面也占用了大量的時間,這些都是由於Hadoop自身基本的,單次迭代的MapReduce模型所限制的。對於Spark這樣的更通用的執行引擎,則可減輕上述問題帶來的開銷。舉例來說,Spark支持基於Hash的分布式聚合和更為通用任務執行計划圖(DAG)

事實上,為了能夠真正優化關系型查詢的執行,我們發現在基於數據統計的基礎上來選擇執行計划是非常有必要的。但是由於UDF和復雜分析函數的存在,而Shark又將其視為一等公民(first-class citizens),這種統計將變得十分困難。為了能夠解決這個問題,我們提出了partial DAG execution (PDE),這使得Spark能夠在基於數據統計的基礎上改變后續執行計划圖,PDE與其他系統(DryadLINQ)的運行時執行計划圖重寫的不同在於:它能夠收集鍵值范圍內的細粒度統計數據;能夠完全重新選擇join的執行策略,如broadcast join,而不僅僅是選擇Reduce任務的個數。

任務調度的開銷: 大概在諸多影響Shark的部分中,最令人感到意外的卻只是一個純粹工程上的問題:運行任務帶來的開銷。傳統的MapReduce系統,就比如Hadoop,是為了運行長達數小時的批量作業而設計的,而組成作業的每個任務其運行時間則有數分鍾之久,他們會在獨立的系統進程中執行任務,在某些極端情況下提交一個任務的延遲非常之高。拿Hadoop打比方,它使用周期性的“心跳”消息來向工作節點分配任務,而這個周期是3秒鍾,因此總共的任務啟動延時就會高達5-10秒。這對於批處理的系統顯然是可以忍受的,但是對於實時查詢這顯然是不夠的。

為了避免上述問題,Spark采用了事件驅動的RPC類庫來啟動任務,通過復用工作進程來避免系統進程開銷。它能夠在一秒鍾內啟動上千個任務,任務之間的延時小於5毫秒,從而使得50-100毫秒的任務,500毫秒的作業變得可能。而這種改進對於查詢性能的提升,甚至對於較長執行時間的查詢性能的提升也令我們感到吃驚不已。

亞秒級的任務使得引擎能夠更好地在工作節點之間平衡任務的分配,甚至在某些節點遇到了不可預知的延遲(網絡延遲或是JVM垃圾回收)的情況下面也能較好地平衡。同時對於數據傾斜也有巨大的幫助,考慮到在100個核上做哈希聚合(hash aggregation),對於每一個任務所處理的鍵范圍需要精心選定,任何的數據傾斜的部分都會拖慢整個作業。但是如果將作業分發到1000個核上面,那么最慢的任務只會比平均任務慢10倍,這就大大提高了可接受程度。而當我們在PDE中應用傾斜感知的選擇策略后,令我們感到失望的是相比於增大Reduce任務個數帶來的提升,這種策略所帶來的提升卻比較小。但不可否認的是,引擎對於異常數據傾斜有了更高的穩定性。

在Hadoop/Hive中,錯誤的選擇任務數量往往會比優化好的執行策略慢上10倍,因此有大量的工作集中在如何自動的選擇Reduce任務的數量[8 9],下圖可以看到Hadoop/Hive和Spark Reduce任務數量對於作業執行時間的影響。因為Spark作業能夠以較小的開銷運行數千個Reduce任務,數據傾斜的影響可以通過運行較多任務來減小。

hadoop hive task compare

事實上,對於更大規模集群(數萬個節點)上亞秒級任務的可行性我們還未探究。但是對於Dremel[10]這樣的周期性地在數千個節點上運行亞秒級作業的系統,實際情況下當單個主節點無法滿足任務調度的速度時,調度策略可以將任務委派給子集群的“副”主節點。同時細粒度的任務執行策略相比於粗粒度的設計不僅僅帶來了負載均衡的好處,而且還包括快速恢復(fast recovery)(通過將失敗任務分發到更多的節點上去)、查詢的彈性(query elasticity)

細粒度任務模型(Fine-Grained Task Modle)帶來的其他好處

雖然這篇文章主要關注的是細粒度任務模型帶來的容錯性優勢,這個模型同樣也提供了許多誘人的特性,接下將會介紹在MapReduce系統中已被證明的兩個特性。

伸縮性(Elasticity): 在傳統的MPP數據庫中,一旦分布式執行計划被選中,系統就必須以此並行度執行整一個的查詢。但是在細粒度任務系統中,在執行查詢的過程中節點可以增刪節點,系統會自動地把阻塞的作業分發到其他節點上去,這使得整個系統變得非常具有伸縮性。如果數據庫管理者需要在這個系統中移除某些節點,系統可以簡單地將這些節點視為失效節點,或者更好的處理方法是將這些節點上的數據復制到其他節點上去。與刪除節點相對應的是,當執行查詢變得更慢時,數據庫系統可以動態地申請更多的資源來提升計算能力。亞馬遜的Elastic MapReduce[11]已經支持運行時調整集群規模。

多租戶架構(Multitenancy): 多租戶架構如同上面提到伸縮性一樣,目的是為了在不同用戶之間動態地共享資源。在傳統的MPP數據庫中,當一個重要的查詢提交的時候已經有一個較大的查詢占據了大多數的集群資源,這時能做的選擇不外乎就是取消先前的查詢等有限的操作。而在基於細粒度任務模型的系統中,查詢作業可以等待幾秒到當前作業完成,然后提交新的查詢作業。Facebook和Microsoft已經為Hadoop和Dryad開發了公平調度器,使得大型的、計算密集型的歷史記錄查詢與實時的小型查詢可以共享集群資源而不會產生飢餓現象[12 13]。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM