原址:http://kissmett.iteye.com/blog/1887946
- /*
- 符注:
- ()內爲數據;[]內爲處理;
- {}內爲框架模塊;
- ()數據若無說明則爲在內存;
- ->本機數據流;=>網絡數據流;~>分佈式-本地讀寫數據流;
- /**/爲標註;
- */
- (分佈式源文件)~>{JobTracker分配到各TaskTracker本機上}=>
- -------------------------------- @TaskTracker(Map) machine
- (split)->[InputFormat]->
- [RecordReader]迭代begin
- (k1,v1)->[map]->
- (k2,v2)->[partition]->
- [RecordReader]迭代end->
- (k2,v2內存集合1)->[sort,merge]->
- (k2,v2內存排序集合)->
- [combine]迭代begin/*若有partition則按其規則*/
- (k2,iter(v2))->[combine]->
- (k3,v3)->
- [combine]迭代end->
- (k3,iter(v3)本地文件)->
- -------------------------------- @TaskTracker(Map) machine
- [shuffle]=>
- -------------------------------- @TaskTracker(Reduce) machine
- (k3,iter(v3)來自各mapper的子集)->[sort,merge]->
- (k3,iter(v3)來自各mapper的合集)->[reduce]->
- (k4,v4)->[OutputFormat]~>
- -------------------------------- @TaskTracker(Reduce) machine
- (分佈式結果文件)
======================================
總結mapreduce數據處理流程:
所謂分佈式計算在hadoop的實現裏可表達爲:
1.基於hdfs分佈式存儲的各存儲節點的map運算過程;
2.之後的在少量(甚至唯一)節點上的reduce運算過程;
3.以及連接map運算輸出和reduce輸入的shuffle過程;
下圖表達在某datanode機器節點進行map和shuffle的過程:
InputFomat:從block到split到(k1,v1)
hdfs中文件是按照配置大小(默認64M)分block存儲n份(一般爲)到n個獨立datanode節點的;
當要解析某分佈式文件,要執行map任務的TaskTracker將讀取存儲於本機的相關block進行本地文件解析;
個人理解是一個block可以被拆分爲多個split,每個split作爲一個map tasktracker的輸入(但如何切分還沒搞清楚),我所涉及的所有mr測試中都是一個block對應一個map任務.
這個split可以通過FSDataInputStream輸入流讀取,這是一個本地文件讀取操作.
此過程中可以編寫自己的InputFormat進行自定義的讀取,此類功能的核心是返回一個RecordReader,RR是具體解析文件邏輯實現類:
對於hadoop0.20及以前版本,對應hadoop core jar裏的 org.apache.hadoop.mapred包,RR我理解是一種被動模式,從其接口函數命名看,主要實現如下函數:
- createKey()
- createValue()
- getPos()
- next(Text key, Text value)
這個RecordReader接口具體實現類的調用者,應是先調用createKey和createValue來實例化key,value對象(這裏是爲初始化自定義對象考慮的),然後再調用next(key,value)來填充這兩個對象而得到解析的結果.
對於hadoop1.0及之後的版本,對應hadoop core jar裏的 org.apache.hadoop.mapreduce包,RR是一種主動模式,主要實現如下函數:
- getCurrentKey()
- getCurrentValue()
- nextKeyValue()
- getPos()
- isSplitable(JobContext context, Path file)
此接口調用者,應是直接調用nextKeyValue()來獲取key value實例,然後通過兩個getCurrent方法由調用者獲取其對應實例;
無論是next(Text key, Text value),還是nextKeyValue(),split被RR解析爲一條的(k1,v1)
MapClass.map():從(k1,v1)到(k2,v2)
不多說.
Partition.partition():給(k2,v2)蓋章(指定reduce)
經過此函數數據流中的k,v是不變的,函數相當於給此k,v對蓋個章,指定其要去的reduce.
值得注意的是,經過RR出來的(k1,v1)是先順序經過map和partition,而非全過完map之後再全過partition.
所有(k2,v2)寫入membuffer.
經過InputFormat中RR的next/nextKeyValue迭代,形成的系列(k2,v2)會被寫入到內存的buffer中,此buffer大小通過io.sort.mb參數指定.
另爲了控制資源,會有另一個進程來監控此buffer容量,當實際容量達到/超過此buffer某百分比時,將發生spill操作,此百分比通過io.sort.spill.percent參數設定.
此監控進程與寫入獨立,所以不會影響寫入速度.
spill:mem buffer中的(k2,v2)落地爲local disk的文件
當buffer實際容量超過門檻限制,或者split所有數據經RR next/nextKeyValue迭代完成時,均觸發spill操作,所以每個map任務流中至少會有一次spill.
spill作用是將buffer裏的kv對sort到本地磁盤文件.
sort:針對spill kv數據的排序
在spill時,sort會參考partition時給數據所蓋的章,即reduce號,另在reduce號相同的情況下再按照k2排序.這個排序至少有兩個目的:
1.使得在有可能的combine中進行同key的迭代變的很容易;
2.使得shuffle後的reduce大流程中基於同key合併變得相對容易;
combine:從(k2,iter(v2))到(k3,v3)
combine類建立的初衷是儘量減少spill發生的磁盤寫操作的量(拿網上通用的說法就是本地的reduce,也確實是實現了Reduce的接口/父類)
combine基於業務規則處理key及同key的所有value.這(k2,iter(v2))經處理後,僅返回一個(k3,v3),減少了spill磁盤寫入量.
combine很有意思,特別是當它跟partition聯合來使用時,在此我在理解上了也頗費了翻功夫:
partition給(k2,v2)蓋章,決定了這個(k2,v2)具體要到哪個reduce去.而其後的combine處理,我完全隨便寫邏輯來處理同key的(k2,iter(v2)),大家是否也有跟我一樣的疑問:假設我在partition裏是根據value的某種規則來決定reduce號,但到了combine中,某個(k2,v2)卻要跟其他很多(k2,v2')進行處理,而其他(k2,v2')並不一定會在partition根據規則歸到同一個reduce號上,那combine卻籠統的返回了一個(k3,v3),那麼這個(k3,v3)到底是會給哪個reduce呢?
我理解hadoop在這裏的機制是,在spill時,首先按照partition的reduce號來將分到同reduce的(k2,v2)放到一起,在此前提下,再進行同key的sort,最終寫入到一個與reduce號相關的spill file中(位於local disk).我還沒有去看具體代碼,暫時把這個理解寫這裏.
另對於這個問題,網上有文章(http://langyu.iteye.com/blog/992916)提及:"Combiner只應該用於那種Reduce的輸入key/value與輸出key/value類型完全一致,且不影響最終結果的場景。比如累加,最大值等。Combiner的使用一定得慎重,如果用好,它對job執行效率有幫助,反之會影響reduce的最終結果。"
這位作者文章很精彩,我很贊同以上的說法,但,實際上我認爲運用spill的這種特性,可以完成二級(或者多級)統計的功能(見後面一個測試的例子).
merge and sort:將多個map的spill文件按找partition分配的reduce號歸併
首先是按照reduce號進行分組,分組內將按照key(即k3)排序.
shuffle:洗牌,不如稱之爲摸牌
當一個TaskTracker執行完了map任務,他將完成狀態彙報給JobTracker.這時(我認爲)JobTracker會讓分配reduce的TaskTracker來獲取這個map生成的文件,每個TaskTracker獲取它所執行reduce對應的那份.
merge and sort:reduce端的
當TaskTracker從各map機上取得屬於自己的文件後,要執行merge和sort過程,即按照k3進行排序,這樣有利於通過一次遍歷就可以達到輸入(k3,iter(v3))的效果.
reduce:從(k3,iter(v3))到(k4,v4)並寫入至分佈式文件系統
每個reduce會生成一個分佈式文件,放置於任務目錄下.
附:一個測試的例子:
...經過map的處理到partition,
partition的輸入(k2,v2)其中k2的含義是書籍出版的年份m個(比如,1999/2010/2012/...),v2的含義是書的分類n個(比如,技術/文學/藝術/...)
partition的處理是根據value來映射到reduce,即每中書籍分類讓一個reduce處理.
combine的輸入(k2,iter(v2)),輸出(k3,v3)其中k3=k2;v3=count(iter(v2));
reduce的輸入是(k3,iter(v3)),輸出(k4,v4),其中k4=k3;v4=sum(iter(v2));
整個mr執行完之後,按書籍分類輸出n個文件,每個文件里根據年份輸出該年份(該分類)的書籍總數;
達到做二級分類統計的目的