分布式基礎學習【二】 —— 分散式運算系統(Map/Reduce)

來源:互聯網
上載者:User
文章目錄
  • IV. Map任務詳請
  • V. Reduce任務詳情
  • VI. 分布式支援
  • VII. 總結
二. 分散式運算(Map/Reduce)
分布式式計算,同樣是一個寬泛的概念,在這裡,它狹義的指代,按Google
Map/Reduce架構所設計的分布式架構。在Hadoop中,Distributed File System,很大程度上,是為各種分散式運算需求所服務的。我們說Distributed File System就是加了分布式的檔案系統,類似的定義推廣到分散式運算上,我們可以將其視為 增加了分布式支援的計算函數。從計算的角度上看,Map/Reduce架構接受各種格式的索引值對檔案作為輸入,讀取計算後,最終產生自訂格式的輸出檔案。而從分布式的角度上看,分散式運算的輸入檔案往往規模巨大,且分布在多個機器上,單機計算完全不可支撐且效率低下,因此Map/Reduce架構需要提供一套機制,將此計算擴充到無限規模的機器叢集上進行。依照這樣的定義,我們對整個Map/Reduce的理解,也可以分別沿著這兩個流程去看。。。在Map/Reduce架構中,每一次計算請求,被稱為 作業。在分散式運算Map/Reduce架構中,為了完成這個作業,它進行兩步走的戰略,首先是將其拆分成若干個 Map任務,分配到不同的機器上去執行,每一個Map任務拿輸入檔案的一部分作為自己的輸入,經過一些計算,產生某種格式的中間檔案,這種格式,與最終所需的檔案格式完全一致,但是僅僅包含一部分資料。因此,等到所有Map任務完成後,它會進入下一個步驟,用以合并這些中間檔案獲得最後的輸出檔案。此時,系統會產生若干個 Reduce任務,同樣也是分配到不同的機器去執行,它的目標,就是將若干個Map任務產生的中間檔案為匯總到最後的輸出檔案中去。當然,這個匯總不總會像1
+ 1 =
2那麼直接了當,這也就是Reduce任務的價值所在。經過如上步驟,最終,作業完成,所需的目標檔案產生。整個演算法的關鍵,就在於增加了一個中間檔案產生的流程,大大提高了靈活性,使其分布式擴充性得到了保證。。。I. 術語對照
和Distributed File System一樣,Google、Hadoop和....我,各執一種方式表述統一概念,為了保證其統一性,特有下表。。。
文中翻譯 Hadoop術語 Google術語 相關解釋
作業 Job Job 使用者的每一個計算請求,就稱為一個作業。
作業伺服器 JobTracker Master 使用者提交作業的伺服器,同時,它還負責各個作業任務的分配,管理所有的任務伺服器。
任務伺服器 TaskTracker Worker 任勞任怨的工蜂,負責執行具體的任務。
任務 Task Task 每一個作業,都需要拆分開了,交由多個伺服器來完成,拆分出來的執行單位,就稱為任務。
備份任務 Speculative Task Buckup Task 每一個任務,都有可能執行失敗或者緩慢,為了降低為此付出的代價,系統會未雨綢繆的實現在另外的任務伺服器上執行同樣一個任務,這就是備份任務。
II. 基本架構
與Distributed File System類似,Map/Reduce的叢集,也由三類伺服器構成。其中 作業伺服器,在Hadoop中稱為 Job
Tracker
,在Google論文中稱為 Master。前者告訴我們,作業伺服器是負責管理運行在此架構下所有作業的,後者告訴我們,它也是為各個作業分配任務的核心。與HDFS的主控伺服器類似,它也是作為單點存在的,簡化了負責的同步流程。具體的 負責執行使用者定義操作的,是 任務伺服器,每一個作業被拆分成很多的 任務,包括 Map任務Reduce任務等,任務是具體執行的基本單元,它們都需要分配到合適任務伺服器上去執行,任務伺服器一邊執行一邊向作業伺服器彙報各個任務的狀態,以此來協助作業伺服器瞭解作業執行的整體情況,分配新的任務等等。。。除了作業的管理者執行者,還需要有一個 任務的提交者,這就是用戶端。與Distributed File System一樣,用戶端也不是一個單獨的進程,而是一組API,使用者需要自訂好自己需要的內容,經由用戶端相關的代碼,將作業及其相關內容和配置,提交到作業伺服器去,並時刻監控執行的狀況。。。同作為Hadoop的實現,與HDFS的通訊機制相同,Hadoop
Map/Reduce也是用了協議介面來進行伺服器間的交流。實現者作為RPC伺服器,調用者經由RPC的代理進行調用,如此,完成大部分的通訊,具體伺服器的架構,和其中啟動並執行各個協議狀況,參見。可以看到,與HDFS相比,相關的協議少了幾個,用戶端與任務伺服器,任務伺服器之間,都不再有直接通訊關係。這並不意味著用戶端就不需要瞭解具體任務的執行狀況,也不意味著,任務伺服器之間不需要瞭解別家任務執行的情形,只不過,由於整個叢集各機器的聯絡比HDFS複雜的多,直接通訊過於的難以維繫,所以,都統一由作業伺服器整理轉寄。另外,從這幅圖可以看到,任務伺服器不是一個人在戰鬥,它會像孫悟空一樣招出一群寶寶協助其具體執行任務。這樣做的好處,個人覺得,應該有安全性方面的考慮,畢竟,任務的代碼是使用者提交的,資料也是使用者指定的,這品質自然良莠不齊,萬一碰上個搞破壞的,把整個任務伺服器處理序搞死了,就因小失大了。因此,放在單獨的地盤進行,愛咋咋地,也算是權責明確了。。。與Distributed File System相比,Map/Reduce架構的還有一個特點,就是 可定製性強。檔案系統中很多的演算法,都是很固定和直觀的,不會由於所儲存的內容不同而有太多的變化。而作為通用的計算架構,需要面對的問題則要複雜很多,在各種不同的問題、不同的輸入、不同的需求之間,很難有一種包治百病的藥能夠一招鮮吃遍天。作為Map/Reduce架構而言,一方面要儘可能的抽取出公用的一些需求,實現出來。更重要的,是需要提供良好的可擴充機制,滿足使用者自訂各種演算法的需求。Hadoop是由Java來實現的,因此通過反射來實現自訂的擴充,顯得比較小菜一碟了。在 JobConf類中,定義了大量的介面,這基本上是Hadoop
Map/Reduce架構所有可定製內容的一次集中展示。在JobConf中,有大量set介面接受一個 Class<? extends
xxx>
的參數,通常它都有一個預設實現的類,使用者如果不滿意,則可自訂實現。。。III. 計算流程
如果一切都按部就班的進行,那麼整個作業的計算流程,應該是作業的提交 -> Map任務的分配和執行 -> Reduce任務的分配和執行
-> 作業的完成。而在每個任務的執行中,又包含輸入的準備 -> 演算法的執行 ->
輸出的產生,三個子步驟。沿著這個流程,我們可以很快的整理清晰整個Map/Reduce架構下作業的執行。。。1、作業的提交
一個作業,在提交之前,需要把所有應該配置的東西都配置好,因為一旦提交到了作業伺服器上,就陷入了完全自動化的流程,使用者除了觀望,最多也就能起一個監督作用,懲治一些不好好工作的任務。。。基本上,使用者在提交代碼階段,需要做的工作主要是這樣的:首先,書寫好所有自定的代碼,最起碼,需要有Map和Reduce的執行代碼。在Hadoop中,Map需要派生自 Mapper<K1, V1,
K2, V2>
介面,Reduce需要派生自 Reducer<K2, V2, K3,
V3>
介面。這裡都是用的泛型,用以支援不同的索引值類型。這兩個介面都僅有一個方法,一個是map,一個是reduce,這兩個方法都直接受四個參數,前兩個是輸入的 相關的資料結構,第三個是作為 輸出相關的資料結構,最後一個,是一個 Reporter類的執行個體,實現的時候可以利用它來統計一些計數。除了這兩個介面,還有大量可以派生的介面,比如分割的 Partitioner<K2,
V2>
介面。。。然後,需要書寫好主函數的代碼,其中最主要的內容就是執行個體化一個 JobConf類的對象,然後調用其豐富的setXXX介面,設定好所需的內容,包括輸入輸出的檔案路徑,Map和Reduce的類,甚至包括讀取寫入檔案所需的格式支援類,等等。。。最後,調用 JobClientrunJob方法,提交此JobConf對象。runJob方法會先行調用到 JobSubmissionProtocol介面所定義的 submitJob方法,將此作業,提交給作業伺服器。接著,runJob開始迴圈,不停的調用JobSubmissionProtocol的 getTaskCompletionEvents方法,獲得 TaskCompletionEvent類的對象執行個體,瞭解此作業各任務的執行狀況。。。2、Map任務的分配當一個作業提交到了作業伺服器上,作業伺服器會產生若干個Map任務,每一個Map任務,負責將一部分的輸入轉換成格式與最終格式相同的中間檔案。通常一個 作業的輸入都是基於Distributed File System的檔案(當然在單機環境下,檔案系統單機的也可以...),因為,它可以很天然的和分布式的計算產生聯絡。而對於一個Map任務而言,它的輸入往往是輸入檔案的一個資料區塊,或者是資料區塊的一部分,但通常, 不跨資料區塊。因為,一旦跨了資料區塊,就可能涉及到多個伺服器,帶來了不必要的複雜性。。。當一個作業,從用戶端提交到了作業伺服器上,作業伺服器會產生一個 JobInProgress對象,作為與之對應的標識,用於管理。作業被拆分成若干個Map任務後,會預先掛在作業伺服器上的任務伺服器拓撲樹。這是依照分布式檔案資料區塊的位置來劃分的,比如一個Map任務需要用某個資料區塊,這個資料區塊有三份備份,那麼,在這三台伺服器上都會掛上此任務,可以視為是一個預分配。。。關於任務管理和分配的大部分的真實功能和邏輯的實現,JobInProgress則依託 JobInProgressListenerTaskScheduler的子類。TaskScheduler,顧名思義是用於任務分配的策略類(為了簡化描述,用它代指所有TaskScheduler的子類...)。它會掌握好所有作業的任務資訊,其 assignTasks函數,接受一個 TaskTrackerStatus作為參數,依照此任務伺服器的狀態和現有的任務狀況,為其分配新的任務。而為了掌握所有作業相關任務的狀況,TaskScheduler會將若干個JobInProgressListener註冊到 JobTracker中去,當有新的作業到達、移除或更新的時候,JobTracker會告知給所有的JobInProgressListener,以便它們做出相應的處理。。。任務分配是一個重要的環節,所謂任務分配,就是 將合適作業的合適任務分配到合適的伺服器上。不難看出,裡面蘊含了兩個步驟,先是選擇作業,然後是在此作業中選擇任務。和所有分配工作一樣,任務分配也是一個複雜的活。不良好的任務分配,可能會導致網路流量增加、某些任務伺服器負載過重效率下降,等等。不僅如此,任務分配還是一個無一致模式的問題,不同的業務背景,可能需要不同的演算法才能滿足需求。因此,在Hadoop中,有很多TaskScheduler的子類,像Facebook,Yahoo,都為其貢獻出了自家用的演算法。在Hadoop中,預設的任務分配器,是 JobQueueTaskScheduler類。它選擇作業的基本次序是:Map
Clean Up Task(Map任務伺服器的清理任務,用於清理相關的到期的檔案和環境...) -> Map Setup
Task(Map任務伺服器的安裝任務,負責配置好相關的環境...) -> Map Tasks -> Reduce Clean Up Task
-> Reduce Setup Task -> Reduce
Tasks。在這個前提下,具體到Map任務的分配上來。當一個任務伺服器工作的遊刃有餘,期待獲得新的任務的時候,JobQueueTaskScheduler會按照各個作業的優先順序,從 最高優先順序的作業開始分配。每分配一個,還會為其留出餘量,已被不時之需。舉一個例子:系統目前有優先順序3、2、1的三個作業,每個作業都有一個可分配的Map任務,一個任務伺服器來申請新的任務,它還有能力承載3個任務的執行,JobQueueTaskScheduler會先從優先順序3的作業上取一個任務分配給它,然後再留出一個1任務的餘量。此時,系統只能在將優先順序2作業的任務分配給此伺服器,而不能分配優先順序1的任務。這樣的策略,基本思路就是 一切為高優先順序的作業服務,優先分配不說,分配了好保留有餘力以備不時之需,如此優待,足以讓高優先順序的作業喜極而泣,讓低優先順序的作業感慨既生瑜何生亮,甚至是活活餓死。。。確定了從哪個作業提取任務後,具體的分配演算法,經過一系列的調用,最後實際是由 JobInProgressfindNewMapTask函數完成的。它的演算法很簡單,就是 盡全力為此伺服器非配且儘可能好的分配任務,也就是說,只要還有可分配的任務,就一定會分給它,而不考慮後來者。作業伺服器會從離它最近的伺服器開始,看上面是否還掛著未分配的任務(預分配上的),從近到遠,如果所有的任務都分配了,那麼看有沒有開啟多次執行,如果開啟,考慮把未完成的任務再分配一次(後面有地方詳述...)。。。對於作業伺服器來說,把一個任務分配出去了,並不意味著它就徹底解放,可以對此任務可以不管不顧了。因為任務可以在任務伺服器上執行失敗,可能執行緩慢,這都需要作業伺服器協助它們再來一次。因此在Task中,記錄有一個 TaskAttemptID,對於任務伺服器而言,它們每次跑的,其實都只是一個Attempt而已,Reduce任務只需要採信一個的輸出,其他都算白忙乎了。。。3、Map任務的執行與HDFS類似,任務伺服器是通過心跳訊息,向作業伺服器彙報此時此刻其上各個任務執行的狀況,並向作業伺服器申請新的任務的。具體實現,是 TaskTracker調用 InterTrackerProtocol協議的 heartbeat方法來做的。這個方法接受一個 TaskTrackerStatus對象作為參數,它描述了此時此任務伺服器的狀態。當其有餘力接受新的任務的時候,它還會傳入 acceptNewTasks為true的參數,表示希望作業伺服器委以重任。 JobTracker接收到相關的參數後,經過處理,會返回一個 HeartbeatResponse對象。這個對象中,定義了一組TaskTrackerAction,用於指導任務伺服器進行下一步的工作。系統中已定義的了一堆其TaskTrackerAction的子類,有的對攜帶的參數進行了擴充,有的只是標明了下ID,具體不詳寫了,一看便知。。。當TaskTracker收到的TaskTrackerAction中,包含了 LaunchTaskAction,它會開始執行所分配的新的任務。在TaskTracker中,有一個 TaskTracker.TaskLauncher線程(確切的說是兩個,一個等Map任務,一個等Reduce任務),它們在癡癡的守候著新任務的來到。一旦等到了,會最終調用到Task的 createRunner方法,構造出一個 TaskRunner對象,建立一個線程來執行。對於一個Map任務,它對應的Runner是TaskRunner的子類 MapTaskRunner,不過,核心部分都在TaskRunner的實現內。TaskRunner會先將所需的檔案全部下載並拆包好,並記錄到一個全域緩衝中,這是一個全域的目錄,可以供所有此作業的所有任務使用。它會用一些軟連結,將一些檔案名稱連結到這個緩衝中來。然後,根據不同的參數,配置出一個JVM執行的環境,這個環境與 JvmEnv類的對象對應。接著,TaskRunner會調用 JvmManagerlaunchJvm方法,提交給JvmManager處理。JvmManager用於管理該TaskTracker上所有啟動並執行Task子進程。在目前的實現中,嘗試的是池化的方式。有若干個固定的槽,如果槽沒有滿,那麼就啟動新的子進程,否則,就尋找idle的進程,如果是同Job的直接放進去,否則殺死這個進程,用一個新的進程代替。每一個進程都是由JvmRunner來管理的,它也是位於單獨線程中的。但是從實現上看,這個機制好像沒有部署開,子進程是死迴圈等待,而不會阻塞在父進程的相關線程上,父線程的變數一直都沒有個調整,一旦分配,始終都處在繁忙的狀況了。真實的執行載體,是Child,它包含一個main函數,進程執行,會將相關參數傳進來,它會拆解這些參數,並且構造出相關的Task執行個體,調用其run函數進行執行。每一個子進程,可以執行指定個數量的Task,這就是上面所說的池化的配置。但是,這套機制在我看來,並沒有運行起來,每個進程其實都沒有機會不死而執行新的任務,只是傻傻的等待進程池滿,而被一刀斃命。也許是我老眼昏花,沒看出其中實現的端倪。。。4、Reduce任務的分配與執行比之Map任務,Reduce的分配及其簡單,基本上是所有Map任務完成了,有閒置任務伺服器,來了就給分配一個Job任務。因為Map任務的結果星羅棋布,且變化多端,真要搞一個全域最佳化的演算法,絕對是得不償失。而Reduce任務的執行進程的構造和分配流程,與Map基本完全的一致,沒有啥可說的了。。。但其實,Reduce任務與Map任務的最大不同,是Map任務的檔案都在本地隔著,而Reduce任務需要到處採集。這個流程是作業伺服器經由此Reduce任務所處的任務伺服器,告訴Reduce任務正在執行的進程,它需要的Map任務執行過的伺服器位址,此Reduce任務伺服器會於原Map任務伺服器聯絡(當然本地就免了...),通過FTP服務,下載過來。這個隱含的直接資料聯絡,就是執行Reduce任務與執行Map任務最大的不同了。。。5、作業的完成當所有Reduce任務都完成了,所需資料都寫到了Distributed File System上,整個作業才正式完成了。此中,涉及到很多的類,很多的檔案,很多的伺服器,所以說起來很費勁,話說,一圖解千語,說了那麼多,我還是畫兩幅圖,徹底表達一下吧。。。首先,是一個時序圖。它類比了一個由3個Map任務和1個Reduce任務構成的作業執行流程。我們可以看到,在執行的過程中,只要有人太慢,或者失敗,就會增加一次嘗試,以此換取最快的執行總時間。一旦所有Map任務完成,Reduce開始運作(其實,不一定要這樣的...),對於每一個Map任務來說,只有執行到Reduce任務把它上面的資料下載完成,才算成功,否則,都是失敗,需要重新進行嘗試。。。而第二副圖,不是我畫的,就不轉載了,參見這裡,它描述了整個Map/Reduce的伺服器狀況圖,包括整體流程、所處伺服器處理序、輸入輸出等,看清楚這幅圖,對Map/Reduce的基本流程應該能完全跑通了。有這幾點,可能圖中描述的不夠清晰需要提及一下,一個是在HDFS中,其實還有記錄檔,圖中沒有標明;另一個是步驟5,其實是由TaskTracker主動去拉取而不是JobTracker推送過來的;還有步驟8和步驟11,建立出來的MapTask和ReduceTask,在Hadoop中都是運行在獨立的進程上的。。。IV. Map任務詳請從上面,可以瞭解到整個Map和Reduce任務的整體流程,而後面要囉嗦的,是具體執行中的細節。Map任務的輸入,是Distributed File System上的,包含索引值對資訊的檔案。為了給每一個Map任務指定輸入,我們需要掌握檔案格式把它分切成塊,並從每一塊中分離出索引值資訊。在HDFS中,輸入的檔案格式,是由 InputFormat<K,
V>
類來表示的,在JobConf中,它的預設值是 TextInputFormat類(見 getInputFormat),此類是特化的 FileInputFormat<LongWritable,
Text>
子類,而 FileInputFormat<K, V>正是InputFormat<K,
V>的子類。通過這樣的關係我們可以很容易的理解,預設的檔案格式是 文字檔,且鍵是 LongWritable類型(整形數),值是 Text類型(字串)。僅僅知道檔案類型是不夠的,我們還需要將檔案中的每一條資料,分離成索引值對,這個工作,是 RecordReader<K,
V>
來做的。在TextInputFormat的 getRecordReader方法中我們可以看到,與TextInputFormat預設配套使用的,是 LineRecordReader類,是特化的 RecordReader<LongWritable,
Text>
的子類,它將每 一行作為一個記錄,起始的位置作為鍵,整行的字串作為值。有了格式,分出了索引值,還需要切開分給每一個Map任務。每一個Map任務的輸入用 InputSplit介面表示,對於一個檔案輸入而言,其實現是 FileSplit,它包含著 檔案名稱、起始位置、長度和儲存它的一組伺服器位址。。。當Map任務拿到所屬的InputSplit後,就開始一條條讀取記錄,並調用用於定義的Mapper,進行計算(參見MapRunner<K1,
V1, K2, V2>和MapTask的run方法),然後,輸出。MapTask會傳遞給Mapper一個OutputCollector<K,
V>對象,作為輸出的資料結構。它定義了一個collect的函數,接受一個索引值對。在MapTask中,定義了兩個OutputCollector的子類,一個是MapTask.DirectMapOutputCollector<K,
V>,人如其名,它的實現確實很Direct,直截了當。它會利用一個RecordWriter<K,
V>對象,collect一調用,就直接調用RecordWriter<K,
V>的write方法,寫入本地的檔案中去。如果覺著RecordWriter<K,
V>出現的很突兀,那麼看看上一段提到的RecordReader<K,
V>,基本上,資料結構都是對應著的,一個是輸入一個是輸出。輸出很對稱也是由RecordWriter<K,
V>和OutputFormat<K, V>來協同完成的,其預設實現是LineRecordWriter<K,
V>和TextOutputFormat<K, V>,多麼的眼熟啊。。。除了這個非常直接的實現之外,MapTask中還有一個複雜的多的實現,是MapTask.MapOutputBuffer<K extends
Object, V extends
Object>。有道是簡單壓倒一切,那為什麼有很簡單的實現,要琢磨一個複雜的呢。原因在於,看上去很美的往往帶著刺,簡單的輸出實現,每調用一次collect就寫一次檔案,頻繁的硬碟操作很有可能導致此方案的低效。為瞭解決這個問題,這就有了這個複雜版本,它先開好一段記憶體做 緩衝,然後制定一個比例做 閾值開一個線程監控此緩衝。collect來的內容,先寫到緩衝中,當監控線程發現緩衝的內容比例超過閾值,掛起所有寫入操作,建一個 新的檔案,把緩衝的內容批量 刷到此檔案中去,清空緩衝,重新開放,接受繼續collect。。。為什麼說是刷到檔案中去呢。因為這不是一個簡單的照本宣科簡單複製的過程,在寫入之前,會先將緩衝中的記憶體,經過排序、合并器(Combiner)統計之後,才會寫入。如果你覺得Combiner這個名詞聽著太陌生,那麼考慮一下Reducer,Combiner也就是一個Reducer類,通過JobConf的setCombinerClass進行設定,在常用的配置中,Combiner往往就是用使用者為Reduce任務定義的那個Reducer子類。只不過,Combiner只是服務的範圍更小一些而已,它在Map任務執行的伺服器本地,依照Map處理過的那一小部分資料,先做一次Reduce操作,這樣,可以壓縮需要傳輸內容的大小,提高速度。每一次刷緩衝,都會開一個新的檔案,等此任務所有的輸入都處理完成後,就有了若干個有序的、經過合并的輸出檔案。系統會將這些檔案搞在一起,再做一個多路的歸併外排,同時使用合并器進行合并,最終,得到了唯一的、有序的、經過合并的中間檔案(註:檔案數量等同於分類數量,在不考慮分類的時候,簡單的視為一個...)。它,就是Reduce任務夢寐以求的輸入檔案。。。除了做合并,複雜版本的OutputCollector,還具有 分類的功能。分類,是通過 Partitioner<K2,
V2>
來定義的,預設實現是 HashPartitioner<K2,
V2>,
作業提交者可以通過JobConf的 setPartitionerClass來自訂。分類的含義是什麼呢,簡單的說,就是將Map任務的輸出,劃分到若干個檔案中(通常與Reduce任務數目相等),使得每一個Reduce任務,可以處理某一類檔案。這樣的好處是大大的,舉一個例子說明一下。比如有一個作業是進行 單詞統計的,其Map任務的中間結果應該是 以單詞為鍵,以單詞數量為值的檔案。如果這時候只有一個Reduce任務,那還好說,從 全部的Map任務那裡收集檔案過來,分別統計得到最後的輸出檔案就好。但是,如果單Reduce任務無法承載此負載或效率太低,就需要多個Reduce任務並存執行。此時,再沿用之前的模式就有了問題。每個Reduce任務從 一部分Map任務那裡獲得輸入檔案,但最終的輸出結果並不正確,因為同一個單詞可能在不同的Reduce任務那裡都有統計,需要想方法把它們統計在一起才能獲得最後結果,這樣就沒有將Map/Reduce的作用完全發揮出來。這時候,就需要用到分類。如果此時有兩個Reduce任務,那麼將輸出分成兩類,一類存放字母表排序較高的單詞,一類存放字母表排序低的單詞,每一個Reduce任務從 所有的Map任務那裡擷取一類的中間檔案,得到自己的輸出結果。最終的結果,只需要把各個Reduce任務輸出的,拼接在一起就可以了。本質上,這就是將Reduce任務的輸入, 由垂直分割,變成了水平分割。Partitioner的作用,正是接受一個索引值,返回一個分類的序號。它會在從緩衝刷到檔案之前做這個工作,其實只是多了一個檔案名稱的選擇而已,別的邏輯都不需要變化。。。除了緩衝、合并、分類等附加工作之外,複雜版本的OutputCollector還支援錯誤資料的跳過功能,在後面分布式將排錯的時候,還會提及,標記一下,按下不表。。。V. Reduce任務詳情理論上看,Reduce任務的整個執行流程要比Map任務更為的羅嗦一些,因為,它需要收集輸入檔案,然後才能進行處理。Reduce任務,主要有這麼三個步驟: CopySortReduce(參見ReduceTask的run方法)。所謂Copy,就是從執行各個Map任務的伺服器那裡,收羅到本地來。拷貝的任務,是由 ReduceTask.ReduceCopier類來負責,它有一個內嵌類,叫 MapOutputCopier,它會在一個單獨的線程內,負責某個Map任務伺服器上檔案的拷貝工作。遠程拷貝過來的內容(當然也可以是本地了...),作為MapOutput對象存在,它可以在記憶體中也可以序列化在磁碟上,這個根據記憶體使用量狀況來自動調節。整個拷貝過程是一個動態過程,也就是說它不是一次給好所有輸入資訊就不再變化了。它會不停的調用 TaskUmbilicalProtocol協議的 getMapCompletionEvents方法,向其父TaskTracker詢問此作業個Map任務的完成狀況(TaskTracker要向JobTracker詢問後再轉告給它...)。當擷取到相關Map任務執行伺服器的資訊後,都會有一個線程開啟,做具體的拷貝工作。同時,還有一個記憶體Merger線程和一個檔案Merger線程在同步工作,它們將新鮮下載過來的檔案(可能在記憶體中,簡單的統稱為檔案...),做著歸併排序,以此,節約時間,降低輸入檔案的數量,為後續的排序工作減負。。。Sort,排序工作,就相當於上述排序工作的一個延續。它會在所有的檔案都拷貝完畢後進行,因為雖然同步有做著歸併的工作,但可能留著尾巴,沒做徹底。經過這一個流程,該徹底的都徹底了,一個嶄新的、合并了所有所需Map任務輸出檔案的新檔案,誕生了。而那些千行萬苦從其他各個伺服器網羅過來的Map任務輸出檔案,很快的結束了它們的曆史使命,被掃地出門一掃而光,全部刪除了。。。
所謂好戲在後頭,Reduce任務的最後一個階段,正是Reduce本身。它也會準備一個 OutputCollector收集輸出,與MapTask不同,這個OutputCollector更為簡單,僅僅是開啟一個 RecordWriter,collect一次,write一次。最大的不同在於,這次傳入RecordWriter的檔案系統,基本都是 Distributed File System,或者說是HDFS。而在輸入方面,ReduceTask會從JobConf那裡調用一堆getMapOutputKeyClass、getMapOutputValueClass、getOutputKeyComparator等等之類的自訂類,構造出Reducer所需的鍵類型,和值的迭代類型Iterator(一個鍵到了這裡一般是對應一組值)。具體實現頗為拐彎抹角,建議看一下 Merger.MergeQueueRawKeyValueIteratorReduceTask.ReduceValuesIterator等等之類的實現。有了輸入,有了輸出,不斷迴圈調用自訂的Reducer,最終,Reduce階段完成。。。VI. 分布式支援1、伺服器正確性保證Hadoop
Map/Reduce伺服器狀況和HDFS很類似,由此可知,救死扶傷的方法也是大同小異。廢話不多說了,直接切正題。同作為用戶端,Map/Reduce的用戶端只是將作業提交,就開始搬個板凳看戲,沒有占茅坑的行動。因此,一旦它掛了,也就掛了,不傷大雅。而任務伺服器,也需要隨時與作業伺服器保持心跳聯絡,一旦有了問題,作業伺服器可以將其上啟動並執行任務,移交給它人完成。作業伺服器,作為一個單點,非常類似的是利用還原點(等同於HDFS的鏡像)和記錄(等同於HDFS的日誌),來進行恢複。其上,需要持久化用於恢複的內容,包含作業狀況、任務狀況、各個任務嘗試的工作狀況等。有了這些內容,再加上任務伺服器的動態註冊,就算挪了個窩,還是很容易恢複的。 JobHistory是記錄相關的一個靜態類,本來,它也就是一個幹寫日誌活的,只是在Hadoop的實現中,對日誌的寫入做了物件導向的封裝,同時又大量用到觀察者模式做了些嵌入,使得看起來不是那麼直觀。本質上,它就是開啟若干個記錄檔,利用各類介面來往裡面寫內容。只不過,這些日誌,會放在Distributed File System中,就不需要像HDFS那樣,來一個SecondXXX隨時候命,由此可見,有巨人在腳下踩著,真好。JobTracker.RecoveryManager類是作業伺服器中用於進行恢複相關的事情,當作業伺服器啟動的時候,會調用其recover方法,恢複記錄檔中的內容。其中步驟,注釋中寫的很清楚,請自行查看。。。2、任務執行的正確和速度整個作業流程的執行,秉承著木桶原理。執行的最慢的Map任務和Reduce任務,決定了系統整體執行時間(當然,如果執行時間在整個流程中佔比例很小的話,也許就微不足道了...)。因此,盡量加快最慢的任務執行速度,成為提高整體速度關鍵。所使用的策略,簡約而不簡單,就是 一個任務多次執行。當所有未執行的任務都分配出去了,並且先富起來的那部分任務已經完成了,並還有任務伺服器孜孜不倦的索取任務的時候,作業伺服器會開始炒剩飯,把那些正在吭哧吭哧在某個伺服器上慢慢執行的任務,再把此任務分配到一個新的任務伺服器上,同時執行。兩個伺服器各盡其力,成王敗寇,先結束者的結果將被採納。這樣的策略,隱含著一個假設,就是我們相信,輸入檔案的分割演算法是公平的,某個任務執行慢,並不是由於這個任務本身負擔太重,而是由於伺服器不爭氣負擔太重能力有限或者是即將撒手西去,給它換個新環境,人挪死樹挪活事半功倍。。。

當然,肯定有哽咽的任務,不論是在哪個伺服器上,都無法順利完成。這就說明,此問題不在於伺服器上,而是任務本身天資有缺憾。缺憾在何處?每個作業,功能代碼都是一樣的,別的任務成功了,就是這個任務不成功,很顯然,問題出在輸入那裡。輸入中有非法的輸入條目,導致程式無法辨識,只能揮淚惜別。說到這裡,解決方案策略也浮出水面了,三十六計走位上,惹不起,還是躲得起的。在MapTask中的MapTask.SkippingRecordReader<K,
V>和ReduceTask裡的ReduceTask.SkippingReduceValuesIterator<KEY,VALUE>,都是用於幹這個事情的。它們的原理很簡單,就是在讀一條記錄前,把當前的位置資訊,封裝成SortedRanges.Range對象,經由Task的reportNextRecordRange方法提交到TaskTracker上去。TaskTracker會把這些內容,擱在TaskStatus對象中,隨著心跳訊息,彙報到JobTracker上面。這樣,作業伺服器就可以隨時隨刻瞭解清楚,每個任務正讀取在那個位置,一旦出錯,再次執行的時候,就在分配的任務資訊裡面添加一組SortedRanges資訊。MapTask或ReduceTask讀取的時候,會看一下這些地區,如果目前範圍正好處於上述雷區,跳過不讀。如此反覆,正可謂,道路曲折,前途光明啊。。。

VII. 總結對於Map/Reduce而言,真正的困難,在於提高其適應能力,打造一款能夠包治百病的執行架構。Hadoop已經做得很好了,但只有真正搞清楚了整個流程,你才能協助它做的更好。。。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.