[轉載] Spark:大資料的“電光石火”

來源:互聯網
上載者:User

標籤:

轉載自http://www.csdn.net/article/2013-07-08/2816149

Spark已正式申請加入Apache孵化器,從靈機一閃的實驗室“電火花”成長為大資料技術平台中異軍突起的新銳。本文主要講述Spark的設計思想。Spark如其名,展現了大資料不常見的“電光石火”。具體特點概括為“輕、快、靈和巧”。

  • :Spark 0.6核心代碼有2萬行,Hadoop 1.0為9萬行,2.0為22萬行。一方面,感謝Scala語言的簡潔和豐富表達力;另一方面,Spark很好地利用了Hadoop和Mesos(伯克利 另一個進入孵化器的項目,主攻叢集的動態資源管理)的基礎設施。雖然很輕,但在容錯設計上不打折扣。主創人Matei聲稱:“不把錯誤當特例處理。”言下 之意,容錯是基礎設施的一部分。
  • :Spark對小資料集能達到亞秒級的延遲,這對於Hadoop MapReduce(以下簡稱MapReduce)是無法想象的(由於“心跳”間隔機制,僅任務啟動就有數秒的延遲)。就大資料集而言,對典型的迭代機器 學習、即席查詢(ad-hoc query)、圖計算等應用,Spark版本比基於MapReduce、Hive和Pregel的實現快上十倍到百倍。其中記憶體計算、資料本地性 (locality)和傳輸最佳化、調度最佳化等該居首功,也與設計伊始即秉持的輕量理念不無關係。
  • :Spark提供了不同層面的靈活性。在實現層,它完美演繹了Scala trait動態混入(mixin)策略(如可更換的叢集調度器、序列化庫);在原語(Primitive)層,它允許擴充新的資料運算元 (operator)、新的資料來源(如HDFS之外支援DynamoDB)、新的language bindings(Java和Python);在範式(Paradigm)層,Spark支援記憶體計算、多迭代批量處理、即席查詢、流處理和圖計算等多種 範式。
  • :巧在借勢和借力。Spark借Hadoop之勢,與Hadoop無縫結合;接著Shark(Spark上的資料倉儲實現)借了Hive的勢;圖計算借 用Pregel和PowerGraph的API以及PowerGraph的點分割思想。一切的一切,都藉助了Scala(被廣泛譽為Java的未來取代 者)之勢:Spark編程的Look‘n‘Feel就是原汁原味的Scala,無論是文法還是API。在實現上,又能靈巧借力。為支援互動式編 程,Spark只需對Scala的Shell小做修改(相比之下,微軟為支援JavaScript Console對MapReduce互動式編程,不僅要跨越Java和JavaScript的思維屏障,在實現上還要大動幹戈)。

說了一大堆好處,還是要指出Spark未臻完美。它有先天的限制,不能很好地支援細粒度、非同步資料處理;也有後天的原因,即使有很棒的基因,畢竟還剛剛起步,在效能、穩定性和範式的可擴充性上還有很大的空間。

計算範式和抽象

Spark首先是一種粗粒度資料並行(data parallel)的計算範式。

資料並行跟任務並行(task parallel)的區別體現在以下兩方面。

  • 計算的主體是資料集合,而非個別資料。集合的長度視實現而定,如SIMD(單指令多資料)向量指令一般是4到64,GPU的SIMT(單指令多線程)一般 是32,SPMD(單程式多資料)可以更寬。Spark處理的是大資料,因此採用了粒度很粗的集合,叫做Resilient Distributed Datasets(RDD)。
  • 集合內的所有資料都經過同樣的運算元序列。資料並行可程式化性好,易於獲得高並行性(與資料規模相關,而非與程式邏輯的並行性相關),也易於高效地映射到底層 的並行或分布式硬體上。傳統的array/vector程式設計語言、SSE/AVX intrinsics、CUDA/OpenCL、Ct(C++ for throughput),都屬於此類。不同點在於,Spark的視野是整個叢集,而非單個節點或平行處理器。

資料並行的範式決定了 Spark無法完美支援細粒度、非同步更新的操作。圖計算就有此類操作,所以此時Spark不如GraphLab(一個大規模圖計算架構);還有一些應用, 需要細粒度的日誌更新和資料檢查點,它也不如RAMCloud(斯坦福的記憶體儲存和計算研究項目)和Percolator(Google增量計算技術)。 反過來,這也使Spark能夠精心耕耘它擅長的應用領域,試圖粗細通吃的Dryad(微軟早期的大資料平台)反而不甚成功。

Spark的RDD,採用了Scala集合類型的編程風格。它同樣採用了函數式語義(functional semantics):一是閉包,二是RDD的不可修改性。邏輯上,每一個RDD運算元都產生新的RDD,沒有副作用,所以運算元又被稱為是確定性;由於所 有運算元都是等冪的,出現錯誤時只需把運算元序列重新執行即可。

Spark的計算抽象是資料流,而且是帶有工作集(working set)的資料流。流處理是一種資料流模型,MapReduce也是,區別在於MapReduce需要在多次迭代中維護工作集。工作集的抽象很普遍,如多 迭代機器學習、互動式資料採礦和圖計算。為保證容錯,MapReduce採用了穩定儲存(如HDFS)來承載工作集,代價是速度慢。HaLoop採用迴圈 敏感的調度器,保證前次迭代的Reduce輸出和本次迭代的Map輸入資料集在同一台物理機上,這樣可以減少網路開銷,但無法避免磁碟I/O的瓶頸。

Spark的突破在於,在保證容錯的前提下,用記憶體來承載工作集。記憶體的存取速度快於磁碟多個數量級,從而可以極大提升效能。關鍵是實現容錯,傳統上有兩種方法:日 志和檢查點。考慮到檢查點有資料冗餘和網路通訊的開銷,Spark採用日誌資料更新。細粒度的日誌更新並不便宜,而且前面講過,Spark也不擅長。 Spark記錄的是粗粒度的RDD更新,這樣開銷可以忽略不計。鑒於Spark的函數式語義和等冪特性,通過重放日誌更新來容錯,也不會有副作用。

編程模型

來看一段代碼:textFile運算元從HDFS讀取記錄檔,返回“file”(RDD);filter運算元篩出帶“ERROR”的行,賦給 “errors”(新RDD);cache運算元把它緩衝下來以備未來使用;count運算元返回“errors”的行數。RDD看起來與Scala集合類型 沒有太大差別,但它們的資料和運行模型大相迥異。

圖1給出了RDD資料模型,並將上例中用到的四個運算元映射到四種運算元類型。Spark程式工作在兩個空間中:Spark RDD空間和Scala原生資料空間。在原生資料空間裡,資料表現為標量(scalar,即Scala基本類型,用橘色小方塊表示)、集合類型(藍色虛線 框)和持久儲存(紅色圓柱)。

 

圖1 兩個空間的切換,四類不同的RDD運算元

輸入運算元(橘色箭頭)將Scala集合類型或儲存中的資料吸入RDD空間,轉為RDD(藍色實線框)。輸入運算元的輸入大致有兩類:一類針對Scala集合類型,如parallelize;另一類針對儲存資料,如上例中的textFile。輸入運算元的輸出就是Spark空間的RDD。

因為函數語義,RDD經過變換(transformation)運算元(藍色箭頭)產生新的RDD。變換運算元的輸入和輸出都是RDD。RDD會被劃分成很多的分區 (partition)分布到叢集的多個節點中,圖1用藍色小方塊代表分區。注意,分區是個邏輯概念,變換前後的新舊分區在物理上可能是同一塊記憶體或存 儲。這是很重要的最佳化,以防止函數式不變性導致的記憶體需求無限擴張。有些RDD是計算的中間結果,其分區並不一定有相應的記憶體或儲存與之對應,如果需要 (如以備未來使用),可以調用緩衝運算元(例子中的cache運算元,灰色箭頭表示)將分區物化(materialize)存下來(灰色方塊)。

一部分變換運算元視RDD的元素為簡單元素,分為如下幾類:

  • 輸入輸出一對一(element-wise)的運算元,且結果RDD的分區結構不變,主要是map、flatMap(map後展平為一維RDD);
  • 輸入輸出一對一,但結果RDD的分區結構發生了變化,如union(兩個RDD合為一個)、coalesce(分區減少);
  • 從輸入中選擇部分元素的運算元,如filter、distinct(去除冗餘元素)、subtract(本RDD有、它RDD無的元素留下來)和sample(採樣)。

另一部分變換運算元針對Key-Value集合,又分為:

  • 對單個RDD做element-wise運算,如mapValues(保持源RDD的分區方式,這與map不同);
  • 對單個RDD重排,如sort、partitionBy(實現一致性的分區劃分,這個對資料本地性最佳化很重要,後面會講);
  • 對單個RDD基於key進行重組和reduce,如groupByKey、reduceByKey;
  • 對兩個RDD基於key進行join和重組,如join、cogroup。

後三類操作都涉及重排,稱為shuffle類操作。

從RDD到RDD的變換運算元序列,一直在RDD空間發生。這裡很重要的設計是lazy evaluation:計算並不實際發生,只是不斷地記錄到中繼資料。中繼資料的結構是DAG(有向非循環圖),其中每一個“頂點”是RDD(包括生產該RDD 的運算元),從父RDD到子RDD有“邊”,表示RDD間的依賴性。Spark給中繼資料DAG取了個很酷的名字,Lineage(世系)。這個 Lineage也是前面容錯設計中所說的日誌更新。

Lineage一直增長,直到遇上行動(action)運算元(圖1中的綠色箭頭),這時 就要evaluate了,把剛才累積的所有運算元一次性執行。行動運算元的輸入是RDD(以及該RDD在Lineage上依賴的所有RDD),輸出是執行後生 成的原生資料,可能是Scala標量、集合類型的資料或儲存。當一個運算元的輸出是上述類型時,該運算元必然是行動運算元,其效果則是從RDD空間返回原生資料 空間。

行動運算元有如下幾類:產生標量,如count(返回RDD中元素的個數)、reduce、fold/aggregate(見 Scala同名運算元文檔);返回幾個標量,如take(返回前幾個元素);產生Scala集合類型,如collect(把RDD中的所有元素倒入 Scala集合類型)、lookup(尋找對應key的所有值);寫入儲存,如與前文textFile對應的saveAsText-File。還有一個檢 查點運算元checkpoint。當Lineage特別長時(這在圖計算中時常發生),出錯時重新執行整個序列要很長時間,可以主動調用 checkpoint把當前資料寫入穩定儲存,作為檢查點。

這裡有兩個設計要點。首先是lazy evaluation。熟悉編譯的都知道,編譯器能看到的scope越大,最佳化的機會就越多。Spark雖然沒有編譯,但調度器實際上對DAG做了線性複 雜度的最佳化。尤其是當Spark上面有多種計算範式混合時,調度器可以打破不同範式代碼的邊界進行全域調度和最佳化。下面的例子中把Shark的SQL代碼 和Spark的機器學習代碼混在了一起。各部分代碼翻譯到底層RDD後,融合成一個大的DAG,這樣可以獲得更多的全域最佳化機會。

另一個要點是一旦行動運算元產生原生資料,就必須退出RDD空間。因為目前Spark只能夠跟蹤RDD的計算,原生資料的計算對它來說是不可見的(除非以後 Spark會提供原生資料類型操作的重載、wrapper或implicit conversion)。這部分不可見的代碼可能引入前後RDD之間的依賴,如下面的代碼:

第三行filter對errors.count()的依賴是由(cnt-1)這個原生資料運算產生的,但調度器看不到這個運算,那就會出問題了。

由於Spark並不提供控制流程,在計算邏輯需要條件分支時,也必須回退到Scala的空間。由於Scala語言對自訂控制流程的支援很強,不排除未來Spark也會支援。

Spark 還有兩個很實用的功能。一個是廣播(broadcast)變數。有些資料,如lookup表,可能會在多個作業間反覆用到;這些資料比RDD要小得多,不 宜像RDD那樣在節點之間劃分。解決之道是提供一個新的語言結構——廣播變數,來修飾此類資料。Spark運行時把廣播變數修飾的內容發到各個節點,並保 存下來,未來再用時無需再送。相比Hadoop的distributed cache,廣播內容可以跨作業共用。Spark提交者Mosharaf師從P2P的老法師Ion Stoica,採用了BitTorrent(沒錯,就是下載電影的那個BT)的簡化實現。有興趣的讀者可以參考SIGCOMM‘11的論文 Orchestra。另一個功能是Accumulator(源於MapReduce的counter):允許Spark代碼中加入一些全域變數做 bookkeeping,如記錄當前的運行指標。

運行和調度

圖2顯示了Spark程式的運行情境。它由用戶端啟動,分兩個階段:第一階段記錄變換運算元序列、增量構建DAG圖;第二階段由行動運算元觸 發,DAGScheduler把DAG圖轉化為作業及其任務集。Spark支援本地單節點運行(開發調試有用)或叢集運行。對於後者,用戶端運行於 master節點上,通過Cluster manager把劃分好分區的任務集發送到叢集的worker/slave節點上執行。

 

圖2 Spark程式運行過程

Spark 傳統上與Mesos“焦不離孟”,也可支援Amazon EC2和YARN。底層任務調度器的基類是個trait,它的不同實現可以混入實際的執行。例如,在Mesos上有兩種調度器實現,一種把每個節點的所有 資源分給Spark,另一種允許Spark作業與其他作業一起調度、共用叢集資源。worker節點上有任務線程(task thread)真正運行DAGScheduler產生的任務;還有塊管理器(block manager)負責與master上的block manager master通訊(完美使用了Scala的Actor模式),為任務線程提供資料區塊。

最有趣的部分是DAGScheduler。下面詳解它的工作過程。RDD的資料結構裡很重要的一個域是對父RDD的依賴。3所示,有兩類依賴:窄(Narrow)依賴和寬(Wide)依賴。

  

圖3 窄依賴和寬依賴

窄依賴指父RDD的每一個分區最多被一個子RDD的分區所用,表現為一個父RDD的分區對應於一個子RDD的分區,和兩個父RDD的分區對應於一個子RDD 的分區。圖3中,map/filter和union屬於第一類,對輸入進行協同劃分(co-partitioned)的join屬於第二類。

寬依賴指子RDD的分區依賴於父RDD的所有分區,這是因為shuffle類操作,3中的groupByKey和未經協同劃分的join。

窄依賴對最佳化很有利。邏輯上,每個RDD的運算元都是一個fork/join(此join非上文的join運算元,而是指同步多個並行任務的barrier): 把計算fork到每個分區,算完後join,然後fork/join下一個RDD的運算元。如果直接翻譯到物理實現,是很不經濟的:一是每一個RDD(即使 是中間結果)都需要物化到記憶體或儲存中,費時費空間;二是join作為全域的barrier,是很昂貴的,會被最慢的那個節點拖死。如果子RDD的分區到 父RDD的分區是窄依賴,就可以實施經典的fusion最佳化,把兩個fork/join合為一個;如果連續的變換運算元序列都是窄依賴,就可以把很多個 fork/join並為一個,不但減少了大量的全域barrier,而且無需物化很多中間結果RDD,這將極大地提升效能。Spark把這個叫做流水線 (pipeline)最佳化。

變換運算元序列一碰上shuffle類操作,寬依賴就發生了,流水線最佳化終止。在具體實現 中,DAGScheduler從當前運算元往前回溯依賴圖,一碰到寬依賴,就產生一個stage來容納已遍曆的運算元序列。在這個stage裡,可以安全地實 施流水線最佳化。然後,又從那個寬依賴開始繼續回溯,產生下一個stage。

要深究兩個問題:一,分區如何劃分;二,分區該放到叢集內哪個節點。這正好對應於RDD結構中另外兩個域:分區劃分器(partitioner)和首選位置(preferred locations)。

分區劃分對於shuffle類操作很關鍵,它決定了該操作的父RDD和子RDD之間的依賴類型。上文提到,同一個join運算元,如果協同劃分的話,兩個父 RDD之間、父RDD與子RDD之間能形成一致的分區安排,即同一個key保證被映射到同一個分區,這樣就能形成窄依賴。反之,如果沒有協同劃分,導致寬 依賴。

所謂協同劃分,就是指定分區劃分器以產生前後一致的分區安排。Pregel和HaLoop把這個作為系統內建的一部分;而Spark 預設提供兩種劃分器:HashPartitioner和RangePartitioner,允許程式通過partitionBy運算元指定。注意,HashPartitioner能夠發揮作用,要求key的hashCode是有效,即同樣內容的key產生同樣的hashCode。這對 String是成立的,但對數組就不成立(因為數組的hashCode是由它的標識,而非內容,產生)。這種情況下,Spark允許使用者自訂 ArrayHashPartitioner。

第二個問題是分區放置的節點,這關乎資料本地性:本地性好,網路通訊就少。有些RDD產生時就 有首選位置,如HadoopRDD分區的首選位置就是HDFS塊所在的節點。有些RDD或分區被緩衝了,那計算就應該送到緩衝分區所在的節點進行。再不 然,就回溯RDD的lineage一直找到具有首選位置屬性的父RDD,並據此決定子RDD的放置。

寬/窄依賴的概念不止用在調度中,對容錯也很有用。如果一個節點宕機了,而且運算是窄依賴,那隻要把丟失的父RDD分區重算即可,跟其他節點沒有依賴。而寬依賴需要父RDD的所有分區都存在, 重算就很昂貴了。所以如果使用checkpoint運算元來做檢查點,不僅要考慮lineage是否足夠長,也要考慮是否有寬依賴,對寬依賴加檢查點是最物 有所值的。

結語

因為篇幅所限,本文只能介紹Spark的基本概念和設計思想,內容來自Spark的多篇論文(以NSDI‘12 “Resilient Distributed Datasets: A Fault-Tolerant Abstraction for In-Memory Cluster Computing”為主),也有我和同事研究Spark的心得,以及多年來從事並行/分布式系統研究的感悟。Spark核心成員/Shark主創者辛湜 對本文作了審閱和修改,特此致謝!

Spark站在一個很高的起點上,有著高尚的目標,但它的征程還剛剛開始。Spark致力於構建開放的生態系統( http://spark-project.org/ https://wiki.apache.org/incubator/SparkProposal),願與大家一起為之努力!

[轉載] Spark:大資料的“電光石火”

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.