MapReduce既是編程模型又是計算框架

在 Hadoop 問世之前,其實已經有了分布式計算,只是那個時候的分布式計算都是專用的系統,只能專門處理某一類計算,比如進行大規模數據的排序。很顯然,這樣的系統無法復用到其他的大數據計算場景,每一種應用都需要開發與維護專門的系統。而 Hadoop MapReduce 的出現,使得大數據計算通用編程成為可能。我們只要遵循 MapReduce 編程模型編寫業務處理邏輯代碼,就可以運行在 Hadoop 分布式集群上,無需關心分布式計算是如何完成的。也就是說,我們只需要關心業務邏輯,不用關心系統調用與運行環境,這和我們目前的主流開發方式是一致的。

大數據計算的核心思路是移動計算比移動數據更劃算。既然計算方法跟傳統計算方法不一樣,移動計算而不是移動數據,那么用傳統的編程模型進行大數據計算就會遇到很多困難,因此 Hadoop 大數據計算使用了一種叫作 MapReduce 的編程模型。

其實 MapReduce 編程模型并不是 Hadoop 原創,甚至也不是 Google 原創,但是 Google 和 Hadoop 創造性地將 MapReduce 編程模型用到大數據計算上,立刻產生了神奇的效果,看似復雜的各種各樣的機器學習、數據挖掘、SQL 處理等大數據計算變得簡單清晰起來。

今天我們就來聊聊Hadoop 解決大規模數據分布式計算的方案——MapReduce。

在我看來,MapReduce 既是一個編程模型,又是一個計算框架。也就是說,開發人員必須基于 MapReduce 編程模型進行編程開發,然后將程序通過 MapReduce 計算框架分發到 Hadoop 集群中運行。我們先看一下作為編程模型的 MapReduce。

為什么說 MapReduce 是一種非常簡單又非常強大的編程模型?

簡單在于其編程模型只包含 Map 和 Reduce 兩個過程,map 的主要輸入是一對 <Key, Value> 值,經過 map 計算后輸出一對 <Key, Value> 值;然后將相同 Key 合并,形成 <Key, Value 集合 >;再將這個 <Key, Value 集合 > 輸入 reduce,經過計算輸出零個或多個 <Key, Value> 對。

同時,MapReduce 又是非常強大的,不管是關系代數運算(SQL 計算),還是矩陣運算(圖計算),大數據領域幾乎所有的計算需求都可以通過 MapReduce 編程來實現。

下面,我以 WordCount 程序為例,一起來看下 MapReduce 的計算過程。

WordCount 主要解決的是文本處理中詞頻統計的問題,就是統計文本中每一個單詞出現的次數。如果只是統計一篇文章的詞頻,幾十 KB 到幾 MB 的數據,只需要寫一個程序,將數據讀入內存,建一個 Hash 表記錄每個詞出現的次數就可以了。這個統計過程你可以看下面這張圖。

image.png

如果用 Python 語言,單機處理 WordCount 的代碼是這樣的。

# 文本前期處理
strl_ist = str.replace('\n', '').lower().split(' ')
count_dict = {}
# 如果字典里有該單詞則加1,否則添加入字典
for str in strl_ist:
if str in count_dict.keys():
    count_dict[str] = count_dict[str] + 1
    else:
        count_dict[str] = 1

簡單說來,就是建一個 Hash 表,然后將字符串里的每個詞放到這個 Hash 表里。如果這個詞第一次放到 Hash 表,就新建一個 Key、Value 對,Key 是這個詞,Value 是 1。如果 Hash 表里已經有這個詞了,那么就給這個詞的 Value + 1。

小數據量用單機統計詞頻很簡單,但是如果想統計全世界互聯網所有網頁(數萬億計)的詞頻數(而這正是 Google 這樣的搜索引擎的典型需求),不可能寫一個程序把全世界的網頁都讀入內存,這時候就需要用 MapReduce 編程來解決。

WordCount 的 MapReduce 程序如下。


public class WordCount {

  public static class TokenizerMapper
       extends Mapper<Object, Text, Text, IntWritable>{

    private final static IntWritable one = new IntWritable(1);
    private Text word = new Text();

    public void map(Object key, Text value, Context context
                    ) throws IOException, InterruptedException {
      StringTokenizer itr = new StringTokenizer(value.toString());
      while (itr.hasMoreTokens()) {
        word.set(itr.nextToken());
        context.write(word, one);
      }
    }
  }

  public static class IntSumReducer
       extends Reducer<Text,IntWritable,Text,IntWritable> {
    private IntWritable result = new IntWritable();

    public void reduce(Text key, Iterable<IntWritable> values,
                       Context context
                       ) throws IOException, InterruptedException {
      int sum = 0;
      for (IntWritable val : values) {
        sum += val.get();
      }
      result.set(sum);
      context.write(key, result);
    }
  }
}

你可以從這段代碼中看到,MapReduce 版本 WordCount 程序的核心是一個 map 函數和一個 reduce 函數。

map 函數的輸入主要是一個 <Key, Value> 對,在這個例子里,Value 是要統計的所有文本中的一行數據,Key 在一般計算中都不會用到。


public void map(Object key, Text value, Context context
                    )

map 函數的計算過程是,將這行文本中的單詞提取出來,針對每個單詞輸出一個 <word, 1> 這樣的 <Key, Value> 對。

MapReduce 計算框架會將這些 <word , 1> 收集起來,將相同的 word 放在一起,形成 <word , <1,1,1,1,1,1,1…>> 這樣的 <Key, Value 集合 > 數據,然后將其輸入給 reduce 函數。

public void reduce(Text key, Iterable<IntWritable> values,
                       Context context
                       ) 

這里 reduce 的輸入參數 Values 就是由很多個 1 組成的集合,而 Key 就是具體的單詞 word。

reduce 函數的計算過程是,將這個集合里的 1 求和,再將單詞(word)和這個和(sum)組成一個 <Key, Value>,也就是 <word, sum> 輸出。每一個輸出就是一個單詞和它的詞頻統計總和。

一個 map 函數可以針對一部分數據進行運算,這樣就可以將一個大數據切分成很多塊(這也正是 HDFS 所做的),MapReduce 計算框架為每個數據塊分配一個 map 函數去計算,從而實現大數據的分布式計算。

假設有兩個數據塊的文本數據需要進行詞頻統計,MapReduce 計算過程如下圖所示。

image.png

以上就是 MapReduce 編程模型的主要計算過程和原理,但是這樣一個 MapReduce 程序要想在分布式環境中執行,并處理海量的大規模數據,還需要一個計算框架,能夠調度執行這個 MapReduce 程序,使它在分布式的集群中并行運行,而這個計算框架也叫 MapReduce。

所以,當我們說 MapReduce 的時候,可能指編程模型,也就是一個 MapReduce 程序;也可能是指計算框架,調度執行大數據的分布式計算。關于 MapReduce 計算框架.

小結

總結一下,今天我們學習了 MapReduce 編程模型。這個模型既簡單又強大,簡單是因為它只包含 Map 和 Reduce 兩個過程,強大之處又在于它可以實現大數據領域幾乎所有的計算需求。這也正是 MapReduce 這個模型令人著迷的地方。

思考題

對于這樣一張數據表


image.png

如果存儲在 HDFS 中,每一行記錄在 HDFS 對應一行文本,文本格式是

1,25
2,25
1,32
2,25

根據上面 WordCount 的示例,請你寫一個 MapReduce 程序,得到下面這條 SQL 的計算結果。

SELECT pageid, age, count(1) FROM pv_users GROUP BY pageid, age;

MapReduce如何讓數據完成一次旅行?

前面我們講到MapReduce 編程模型將大數據計算過程切分為 Map 和 Reduce 兩個階段,先復習一下,在 Map 階段為每個數據塊分配一個 Map 計算任務,然后將所有 map 輸出的 Key 進行合并,相同的 Key 及其對應的 Value 發送給同一個 Reduce 任務去處理。通過這兩個階段,工程師只需要遵循 MapReduce 編程模型就可以開發出復雜的大數據計算程序。

那么這個程序是如何在分布式集群中運行起來的呢?MapReduce 程序又是如何找到相應的數據并進行計算的呢?答案就是需要 MapReduce 計算框架來完成。上一期我講了 MapReduce 既是編程模型又是計算框架,我們聊完編程模型,接下來就來討論 MapReduce 如何讓數據完成一次旅行,也就是 MapReduce 計算框架是如何運作的。

首先我想告訴你,在實踐中,這個過程有兩個關鍵問題需要處理

  • 如何為每個數據塊分配一個 Map 計算任務,也就是代碼是如何發送到數據塊所在服務器的,發送后是如何啟動的,啟動以后如何知道自己需要計算的數據在文件什么位置(BlockID 是什么)。

  • 處于不同服務器的 map 輸出的 <Key, Value> ,如何把相同的 Key 聚合在一起發送給 Reduce 任務進行處理。

  • 處于不同服務器的 map 輸出的 <Key, Value> ,如何把相同的 Key 聚合在一起發送給 Reduce 任務進行處理。

那么這兩個關鍵問題對應在 MapReduce 計算過程的哪些步驟呢?根據我上一期所講的,我把 MapReduce 計算過程的圖又找出來,你可以看到圖中標紅的兩處,這兩個關鍵問題對應的就是圖中的兩處“MapReduce 框架處理”,具體來說,它們分別是 MapReduce 作業啟動和運行,以及 MapReduce 數據合并與連接。

image.png

MapReduce 作業啟動和運行機制

我們以 Hadoop 1 為例,MapReduce 運行過程涉及三類關鍵進程。

  1. 大數據應用進程。這類進程是啟動 MapReduce 程序的主入口,主要是指定 Map 和 Reduce 類、輸入輸出文件路徑等,并提交作業給 Hadoop 集群,也就是下面提到的 JobTracker 進程。這是由用戶啟動的 MapReduce 程序進程,比如我們上期提到的 WordCount 程序。

2.JobTracker 進程。這類進程根據要處理的輸入數據量,命令下面提到的 TaskTracker 進程啟動相應數量的 Map 和 Reduce 進程任務,并管理整個作業生命周期的任務調度和監控。這是 Hadoop 集群的常駐進程,需要注意的是,JobTracker 進程在整個 Hadoop 集群全局唯一。

3.TaskTracker 進程。這個進程負責啟動和管理 Map 進程以及 Reduce 進程。因為需要每個數據塊都有對應的 map 函數,TaskTracker 進程通常和 HDFS 的 DataNode 進程啟動在同一個服務器。也就是說,Hadoop 集群中絕大多數服務器同時運行 DataNode 進程和 TaskTracker 進程。

JobTracker 進程和 TaskTracker 進程是主從關系,主服務器通常只有一臺(或者另有一臺備機提供高可用服務,但運行時只有一臺服務器對外提供服務,真正起作用的只有一臺),從服務器可能有幾百上千臺,所有的從服務器聽從主服務器的控制和調度安排。主服務器負責為應用程序分配服務器資源以及作業執行的調度,而具體的計算操作則在從服務器上完成。

具體來看,MapReduce 的主服務器就是 JobTracker,從服務器就是 TaskTracker。還記得我們講 HDFS 也是主從架構嗎,HDFS 的主服務器是 NameNode,從服務器是 DataNode。后面會講到的 Yarn、Spark 等也都是這樣的架構,這種一主多從的服務器架構也是絕大多數大數據系統的架構方案。

可重復使用的架構方案叫作架構模式,一主多從可謂是大數據領域的最主要的架構模式。主服務器只有一臺,掌控全局;從服務器有很多臺,負責具體的事情。這樣很多臺服務器可以有效組織起來,對外表現出一個統一又強大的計算能力。

講到這里,我們對 MapReduce 的啟動和運行機制有了一個直觀的了解。那具體的作業啟動和計算過程到底是怎樣的呢?我根據上面所講的繪制成一張圖,你可以從圖中一步一步來看,感受一下整個流程。

image.png

如果我們把這個計算過程看作一次小小的旅行,這個旅程可以概括如下:

  1. 應用進程 JobClient 將用戶作業 JAR 包存儲在 HDFS 中,將來這些 JAR 包會分發給 Hadoop 集群中的服務器執行 MapReduce 計算。

  2. 應用程序提交 job 作業給 JobTracker。

3.JobTracker 根據作業調度策略創建 JobInProcess 樹,每個作業都會有一個自己的 JobInProcess 樹。

4.JobInProcess 根據輸入數據分片數目(通常情況就是數據塊的數目)和設置的 Reduce 數目創建相應數量的 TaskInProcess。

5.TaskTracker 進程和 JobTracker 進程進行定時通信。

  1. 如果 TaskTracker 有空閑的計算資源(有空閑 CPU 核心),JobTracker 就會給它分配任務。分配任務的時候會根據 TaskTracker 的服務器名字匹配在同一臺機器上的數據塊計算任務給它,使啟動的計算任務正好處理本機上的數據,以實現我們一開始就提到的“移動計算比移動數據更劃算”。

7.TaskTracker 收到任務后根據任務類型(是 Map 還是 Reduce)和任務參數(作業 JAR 包路徑、輸入數據文件路徑、要處理的數據在文件中的起始位置和偏移量、數據塊多個備份的 DataNode 主機名等),啟動相應的 Map 或者 Reduce 進程。

8.Map 或者 Reduce 進程啟動后,檢查本地是否有要執行任務的 JAR 包文件,如果沒有,就去 HDFS 上下載,然后加載 Map 或者 Reduce 代碼開始執行。

  1. 如果是 Map 進程,從 HDFS 讀取數據(通常要讀取的數據塊正好存儲在本機);如果是 Reduce 進程,將結果數據寫出到 HDFS。

通過這樣一個計算旅程,MapReduce 可以將大數據作業計算任務分布在整個 Hadoop 集群中運行,每個 Map 計算任務要處理的數據通常都能從本地磁盤上讀取到。現在你對這個過程的理解是不是更清楚了呢?你也許會覺得,這個過程好像也不算太簡單啊!

其實,你要做的僅僅是編寫一個 map 函數和一個 reduce 函數就可以了,根本不用關心這兩個函數是如何被分布啟動到集群上的,也不用關心數據塊又是如何分配給計算任務的。這一切都由 MapReduce 計算框架完成!是不是很激動,這也是我們反復講到的 MapReduce 的強大之處。

MapReduce 數據合并與連接機制

MapReduce 計算真正產生奇跡的地方是數據的合并與連接。

讓我先回到上一期 MapReduce 編程模型的 WordCount 例子中,我們想要統計相同單詞在所有輸入數據中出現的次數,而一個 Map 只能處理一部分數據,一個熱門單詞幾乎會出現在所有的 Map 中,這意味著同一個單詞必須要合并到一起進行統計才能得到正確的結果。

事實上,幾乎所有的大數據計算場景都需要處理數據關聯的問題,像 WordCount 這種比較簡單的只要對 Key 進行合并就可以了,對于像數據庫的 join 操作這種比較復雜的,需要對兩種類型(或者更多類型)的數據根據 Key 進行連接。

在 map 輸出與 reduce 輸入之間,MapReduce 計算框架處理數據合并與連接操作,這個操作有個專門的詞匯叫 shuffle。那到底什么是 shuffle?shuffle 的具體過程又是怎樣的呢?請看下圖。

image.png

每個 Map 任務的計算結果都會寫入到本地文件系統,等 Map 任務快要計算完成的時候,MapReduce 計算框架會啟動 shuffle 過程,在 Map 任務進程調用一個 Partitioner 接口,對 Map 產生的每個 <Key, Value> 進行 Reduce 分區選擇,然后通過 HTTP 通信發送給對應的 Reduce 進程。這樣不管 Map 位于哪個服務器節點,相同的 Key 一定會被發送給相同的 Reduce 進程。Reduce 任務進程對收到的 <Key, Value> 進行排序和合并,相同的 Key 放在一起,組成一個 <Key, Value 集合 > 傳遞給 Reduce 執行。

map 輸出的 <Key, Value>shuffle 到哪個 Reduce 進程是這里的關鍵,它是由 Partitioner 來實現,MapReduce 框架默認的 Partitioner 用 Key 的哈希值對 Reduce 任務數量取模,相同的 Key 一定會落在相同的 Reduce 任務 ID 上。從實現上來看的話,這樣的 Partitioner 代碼只需要一行。

 /** Use {@link Object#hashCode()} to partition. */ 
public int getPartition(K2 key, V2 value, int numReduceTasks) { 
    return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks; 
 }

講了這么多,對 shuffle 的理解,你只需要記住這一點:分布式計算需要將不同服務器上的相關數據合并到一起進行下一步計算,這就是 shuffle。

shuffle 是大數據計算過程中最神奇的地方,不管是 MapReduce 還是 Spark,只要是大數據批處理計算,一定都會有 shuffle 過程,只有讓數據關聯起來,數據的內在關系和價值才會呈現出來。如果你不理解 shuffle,肯定會在 map 和 reduce 編程中產生困惑,不知道該如何正確設計 map 的輸出和 reduce 的輸入。shuffle 也是整個 MapReduce 過程中最難、最消耗性能的地方,在 MapReduce 早期代碼中,一半代碼都是關于 shuffle 處理的。

小結

MapReduce 編程相對說來是簡單的,但是 MapReduce 框架要將一個相對簡單的程序,在分布式的大規模服務器集群上并行執行起來卻并不簡單。理解 MapReduce 作業的啟動和運行機制,理解 shuffle 過程的作用和實現原理,對你理解大數據的核心原理,做到真正意義上把握大數據、用好大數據作用巨大。

FAQ

您好,有個問題,當某個key聚集了大量數據,shuffle到同一個reduce來匯總,考慮數據量很大的情況,這個會不會把reduce所在機器節點撐爆?這樣任務是不是就失敗了?

會的,數據傾斜,會導致任務失敗。嚴重的數據傾斜可能是數據本身的問題,需要做好預處理
請問一下,map和reduce有絕對的先后關系嗎,還是說可以一邊map一邊reduce

絕對先后關系,一個reduce必須要他前置的所有map執行完才能執行。

MapReduce框架會在85%的map程序執行完成時,開始啟動reduce程序,reduce程序一邊shuffle已完成的map數據,一邊等待未完成的map繼續執行,直到全部map完成,reduce才開始執行計算。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,321評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,559評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,442評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,835評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,581評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,922評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,931評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,096評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,639評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,374評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,591評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,104評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,789評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,196評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,524評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,322評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,554評論 2 379