hadoop中的Shuffle

缺省情況下,MapReduce Framework的Shuffle & Sort過程將所有和某一個鍵相關聯的值“組合”(group)在一起,傳送到一個唯一確定的Reducer,而且傳送到每個Reducer的鍵是“排序”的(sort)。這對應到三個操作:1)“組合”; 2)“排序”; 和 3)partition(確定哪個鍵及其值的組合送到哪個Reducer)。

這三個操作涉及到最基本的MapRedcue工作原理,好理解。但是初學者容易忽略的地方是他們很容易記住這三個操作的缺省行爲,卻不清楚其實其中每一個的行爲都是可以在代碼裏進行定製的。所以,下面首先介紹如何控制這三個操作行爲:

- 定義partitioner來告訴MapReduce framework如何將見到的鍵和值送到哪個Reducer

- 定義key comparator來告訴MapReduce framework如何排序鍵

- 定義grouping comparator來告訴MapReduce framework如何控制“組合”鍵值在一起

這些很重要,它意味着MapReduce framework其實非常靈活,允許你根據應用來定製系統不同於缺省情況下的行爲。這三個可以定製的行爲在Secondary Sort裏得到了應用和體現。下面我們介紹Secondary Sort。

Hadoop做排序是一個常見的應用。其中,Secondary Sort則是一類特殊的排序問題,即:我們不僅要求key是排序的,而且要求對應相同鍵的值也是排序的。

譬如我們有一組原始數據:

a 1

z 3

b 2

a 100

a 3

b 1


如果只對鍵進行升序排序,編寫並運行相應排序MapReduce,可能某次執行結果我們得到:

a 1

a 100

a 3

b 2

b 1

z 3


而另一次我們則得到不同的結果,可能爲:

a 3

a 100

a 1

b 1

b 2

z 3


也就是說,結果是按鍵升序排序,但是對應相同的鍵,值不是排序的,並且不同運行得到的值得次序是不定的。如果我們需要對應相同的鍵,值按照數字升序排序,那麼就是做一個secondary sort。對應上面的數據,預期的結果爲:

a 1

a 3

a 100

b 1

b 2

z 3


實現Secondary Sort有些額外操作,其中某些步驟比較費解,因爲要求不同於系統的缺省行爲。下面結合輸入輸出的變化對如何實現Secondary Sort進行一些講解。

首先,在Map端,我們需要產生一個複合鍵,其中一部分來自原始的“自然鍵”(譬如上面的a, b, z等),另一部分來自“自然鍵”對應的值。而Map的輸出則是對應原始鍵-值對,產生相應的複合鍵-值對。

對應我們上面一開始給出的原始數據,中間產生的結果如下

a#1 1

z#3 3

b#2 2

a#100 100

a#3 3

b#1 1

注:這裏爲了講解方便,我用“#”來連接原始的鍵-值對從而產生相應的複合鍵。在實際應用中,應該爲其定義一個類。

接下來,我們在Map端還有三件事情需要做。

第一,需要保證我們期望的排序。這個在整個複合鍵上進行:首先按照複合建中的原始“自然鍵”部分進行排序;如果“自然鍵”相同,則按照複合鍵中的原始值部分進行排序。具體實現可以按照上述排序方法重載複合鍵類的compareTo方法;或者更好的辦法是定義一個Comparator類按照上述排序方法重載compare方法,並在作業配置中指明使用該類:


conf.setOutputKeyComparatorClass(MyOKCC.class);


第二,我們需要定一個partitioner來保證相同“自然鍵”對應的記錄都被送到同一個Reducer,並在作業配置中指明使用該類:


conf.setPartitionerClass(MyPartitioner.class);


第三,這個也是最費解的部分,有一些學員一開始都不是太理解,也是爲什麼我要寫這篇文章的主要動因。通過Mapper產生複合鍵,以及上面兩步,我們保證了相同自然鍵對應的記錄都能到達同一個Reducer,並且按照我們所需要的方式排序。不過,費解的是,如果我們的Mapper產生如下這樣的中間結果:

a#1   1

z#3   3

b#2   2

a#100 100

a#3   3

b#1   1


雖然 (a#1)   1, (a#3) 3,和(a#100)   100這三個記錄能被送到同一個Reducer,可是它們的鍵並不相同,所以對應這三個仍然是分開的記錄,而我們希望他們被“組合”在一起!

怎麼辦? MapReduce相當靈活,給我們提供了強大的機制來解決這個問題,這就涉及到我一開頭說的控制“組合”操作。這個操作實際涉及到兩方面:1)判定鍵相同;2)把相同鍵的值“組合”在一起。

正如我們一開始提到的,MapReduce的靈活性機制體現在這裏,允許應用指定如何判定鍵相同。如果我們告訴MapReduce Framework只按照複合鍵的自然鍵部分進行判定,那麼對於三個記錄(a#1)   1, (a#3) 3,和(a#100)   100,在MapReduce的“眼裏”,由於自然鍵部分都是“a”,那麼他們是相同的。因而對應的三個值1, 3 和100將被“組合”放在一個列表裏(1,3,100)作爲對應(a#1)的值。也就是說,首先(a#1,1)被處理,接着系統看到(a#3,3),由於我們告訴系統這個鍵"a#3"和(a#1,1)的鍵"a#1"相同,系統將3作爲和鍵"a#1"相關聯的值來看待和進行“組合”;類似地,100也被作爲和a#1相關聯的值,等等。結果就是,傳到Reducer的中間結果爲(a#1, [1, 3, 100])。

這個判定鍵大小的部分通過以下來實現:首先實現一個Comparator類(譬如叫“MyOVGC”),只按照複合鍵中的自然鍵部分進行比較來重載compare方法;然後,在作業配置中指明使用該類:


conf.setOutputValueGroupingComparator(MyOVGC.class);


這樣,Reducer收到的中間結果如下:

a#1,[1,3,100]

b#1,[1,2]

z#3,[3]


最後的事情就簡單了,Reducer輸出複合鍵裏的原始自然鍵部分以及每一個值作爲一個新的輸出記錄。由於鍵是排序的,而值也是排序的,最後的結果就是滿足需要的結果。

下面我們把整個數據流列在下面以方便理解整個過程。


原始數據

a 1

z 3

b 2

a 100

a 3

b 1


Mapper產生的中間結果

a#1   1

z#3   3

b#2   2

a#100 100

a#3   3

b#1   1


只使用一個Reducer,看到的輸入爲

a#1 [1,3,100]

b#1 [1,2]

z#3 [3]


最後的輸出爲

a 1

a 3

a 100

b 1

b 2

z 3

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章