We should forget about small efficiencies — Donald Knuth
2 Jul

What’s New
Firefox 3.5 community edition en_US (20090630)
Firefox 3.5 community edition zh_TW (20090630)
Firefox 3.5 community edition zh_CN (20090630)
Firefox 3.5 community edition ja (20090701)
Firefox 3.5 community edition de (20090701)
Firefox 3.5 community edition fr (20090701)
Popularity: 1% [?]
24 Jun

What’s New
Firefox 3.0.11 community edition en_US (20090615)
Firefox 3.0.11 community edition zh_TW (20090615)
Firefox 3.0.11 community edition zh_CN (20090615)
Firefox 3.0.11 community edition ja (20090616)
Firefox 3.0.11 community edition fr (20090616)
Popularity: 1% [?]
9 Jun

What’s New
Firefox 3.5pre community edition en_US (20090608)
Popularity: 7% [?]
7 Jun
4 Jun

What’s New
Firefox 3.5pre community edition en_US (20090603)
Popularity: 9% [?]
2 Jun

What’s New
Firefox 3.5pre community edition en_US (20090601)
Popularity: 10% [?]
25 May
子曰:「性相近也,習相遠也。」Hadoop 和 Google 的 MapReduce 的實作就有這種感覺。
前一篇有提到,MapReduce 是 Google 所提出來的一個 software framework,只要把握它的原則,實作它並不會很困難。我們來看看 Google 和 Hadoop 三大碁石的對應表:
| Hadoop | |
| GFS (和 RedHat 的 GFS 是不一樣的) | HDFS |
| MapReduce | MapReduce |
| BigTable | HBase |
雖然目前會先以 open-source 的 Hadoop MapReduce 為主,但是我們可以看的到這兩個都是用 Distributed file system 來作資料的交換,也有自己的 MapReduce 的方法,所以概念上相去不遠。
在開始講之前,有件事應該要先提一下,因為 Hadoop 是以 Java 開發的,所以不熟的人可能會很排斥,老實說我也如此。但是 Hadoop 有提供一些方法讓開發者可以用其它的語言來寫 MapReduce。
我想 Java 的使用者不管是 Hadoop 的說明文件,還是範例程式應該都不是甚麼問題,因此我主要會以 C 以及 Python 為主,然後盡量也會帶點 Ruby 和 Perl。
我們先來看看 Hadoop MapReduce 的 work flow:

這張 work flow 我們往後也會一直看到。如同前一篇提到的,MapReduce 有四個很重要的步驟。我們來看看 Hadoop 在這四個階段作了甚麼事情。
1) Input
將要處理的資料上傳到分散式檔案系統中。預設的格式是文字檔 (我們也不會用其它的檔案格式),而分散式檔案系統以 HDFS 當範例。
完成上傳到分散式檔案系統之後,所有的資料都可以被每一個 node 去存取。而每一個檔案,會以行當單位,自動被分割成固定大小的 Block,分散在各個 node 之中,然而這件事對我們而言是 transparent 的。(不過這裡特別提的原因是這有可能會影響到 Map 的數目,所以有個印象即可。)
以 Block 大小是 64MB 當例子,假設原始檔案是 200MB,那第一個 Block 是從頭算起的第 64 MB,再往後找到第一個換行 (\n) 為止。這樣作是為了方便 Map 不用處理這種 boundary case。同樣的,第二個 Block 就是該換行的下一個 byte,取 64MB 再找換行,如此不斷的反覆直到檔案結尾。換句話說,最後一個 Block 的大小會隨機變動,也和下一個檔案無關。
2) Map
雖然 Map 的數目是可以設定的,但是預設每個 Block 理論上都會被一個 Map 處理。那 Map 的邏輯就是讀 stdin,以行為單位處理之後,印成 key-value pair 到 stdout。就像是 unix 上的 pipeline一樣。key 跟 value 預設是以 tab (\t) 當分割。(精確的說,是以第一個 tab 當中介,往前是 key,往後是 value,這也是可以設定的。)
因為 Map 的程式是我們可以動手的,所以我們可以記得 Map 就是把一行的文字,轉換成一個 key-value pair 的邏輯。
3) Shuffle/Sort
為甚麼是 key-value pair 呢? 那是因為相同的 key 會送到同一個 reduce,而每個 reduce 會產生自己獨立的 output file。如果 map 的結果是隨機送往 reduce,那這個的 work flow 便會有其限制了。這就是第三個階段 Shuffle/Sort 的工作。
舉個例子,假設我們是要將 input file 的每個單字出現的次數。其中一行是 “one for all; all for one”,我們的 map 程式就是把每一個單字,都作成一個 key-value pair 往後送。Key 當然是單字本身,那 value 就是 1,代表 count 是 1。因此,上例會產生六個 key-value pair: (one,1), (for,1), (all,1), (all,1), (for,1), (one,1)。如果是隨機送往 reduce,那有可能 reduce1 收到前三個,reduce2 收到後三個,加上彼此的 reduce 不會互相參照,所以這結果並不是我們要的。(因為 for 這個字有些在 reduce1 的 output file,有些在 reduce2 的 output file,那還要特別寫程式作加總才是我們要的結果。當然如果 reduce 只有一個就沒有這樣的問題。)
那相同的 key 會送到同一個 reduce 的 case 會發生甚麼事? 我們可以保證同一個單字不會送到不同的 reduce,所以某個 reduce 的 output file 的任一單字的數目,一定代表原始所有檔案中,該單字的出現的次數。
那 sort 在做甚麼? 在上例我們看不出來關聯性,但是如果我們要的不單是單字出現的次數,還要排序,那就會有差別了。
一樣用 “one for all; all for one” 當例子。如果碰巧二個 reduce 收到的是:
1) Reduce1: (one, 1), (for, 1), (for, 1), (one, 1)
2) Reduce2: (all, 1), (all, 1)
PS: 斜體代表是 all for one。
那每個 reduce 程式還要先讀完全部的 stdin 才能加以 sort,這樣就沒有效率了。反過來說,如果先把資料 sort 好再給 reduce 程式,reduce 就可以 streaming 的方式運作,寫法也比較簡單。
4) Reduce
Reduce 的邏輯和 Map 相似。它是把 key-value pair,轉換成我們要的結果。也是拿上面的例子,經過 Shuffle/Sort 之後,Reduce 會收到:
1) Reduce1: (one, 1), (one, 1), (for, 1), (for, 1)
2) Reduce2: (all, 1), (all, 1)
PS: 斜體代表是 all for one。注意,第二個 one 跑到 for 之前了。
所以我們 reduce 的作的事情是: “在遇到下一個單字前,累加 value,如果遇到新的單字,就印出這個單字和總量到 stdout。” 所以我們的結果是
1) Output1: (one, 2) 和 (for, 2) 共兩行
2) Output2: (all, 2) 一行
這就是我們的結果。
這篇文章雖然長,但是瞭解之後可以大概的知道 Hadoop 整個 work flow。下一篇我們就要動手試我們的程式了。
Popularity: 6% [?]