MapReduce中的map與reduce

本文主要介紹MapReduce的map與reduce所包含的各各階段

        MapReduce中的每個map任務可以細分4個階段:record reader、mapper、combiner和partitioner。map任務的輸出被稱

爲中間鍵和中間值,會被髮送到reducer做後續處理。reduce任務可以分爲4個階段:混排(shuffle)、排序(sort)、reducer

和輸出格式(output format)。map任務運行的節點會優先選擇在數據所在的節點,因此,一般可以通過在本地機器上進行計算

來減少數據的網絡傳輸。

        Mapreduce作業的輸入是一系列存儲在Hadoop分佈式文件系統(HDFS)上的文件。在MapReduce中,這些文件通過輸入格

(input format)被分成了一系列的輸入split(input split)。輸入split可以看做是文件在字節層面的分塊表示,每個split由一

map任務負責處理。


record reader

        record reader通過輸入格式將輸入split解析成記錄。record reader的目的只是將輸入數據解析成記錄,但不負責解析記錄

本身。它將數據轉化爲鍵/值(key/value)對的形式,並傳遞給mapper處理。通常鍵是數據在文件中的位置,值是組成記錄的

數據塊。

map

        在mapper中,用戶定義的map代碼通過處理record reader解析的每個鍵/值對來產生0個或多個新的鍵/值對結果。鍵/值的

選擇對MapReduce作業的完成效率來說非常重要。鍵是數據在reducer中處理時被分組的依據,值是reducer需要分析的數

據。

combiner

        combiner是一個可選的本地reducer,可以在map階段聚合數據。combiner通過執行用戶指定的來自mapper的中間鍵對

map的中間結果做單個map範圍內的聚合。

        例如,一個聚合的計數是每個部分計數的總和,用戶可以先將每個中間結果取和,再將中間結果的和相加,從而得到最終

結果。

        在很多情況下,這樣可以明顯地減少通過網絡傳輸的數據量。在網絡上發送一次(hello,3)要比三次(hello,1)節省更

多的字節量。通過combiner可以產生特別大的性能提升,並且沒有副作用,因此combiner的應用非常廣泛。

partitioner

        partitioner的作用是將mapper(如果使用了combiner的話就是combiner)輸出的鍵/值對拆分爲分片(shard),每reducer

對應一個分片。默認情況下,partitioner先計算目標的散列值(通常爲md5值)。然後,通過reducer個數執行取模運算

key.hashCode()%(reducer的個數)。這種方式不僅能夠隨機地將整個鍵空間平均分發給每個reducer,同時也能確保不同mapper

產生的相同鍵能被分發至同一個reducer。用戶可以定製partitioner的默認行爲,並可以使用更高級的模式,如排序。當然,一

般情況下是不需要改寫partitioner的。對於每個map任務,其分好區的數據最終會寫入本地文件系統,等待其各自的reducer拉

取。


混排和排序

        reduce任務開始於混排和排序這一步驟。該步驟主要是將所有partitioner寫入的輸出文件拉取到運行reducer的本地機器

上,然後將這些數據按照鍵排序並寫到一個較大的數據列表中。排序的目的是將相同鍵的記錄聚合在一起,這樣其所對應的值

就可以很方便地在reduce任務中進行迭代處理。這個過程完全不可定製,而且是由框架自動處理的。開發人員只能通過自定義

Comparator對象來確定鍵如何排序和分組。

reduce

        reducer將已經分好組的數據作爲輸入,並依次爲每個鍵對應分組執行reduce函數。reduce函數的輸入是鍵以及包含與該鍵

對應的所有值的迭代器。在後文介紹的模式中,我們將看到在這個函數中有很多種處理方法。這些數據可以被聚合、過濾或以

多種方式合併。當reduce函數執行完畢後,會將0個或多個鍵/值對發送到最後的處理步驟——輸出格式。和map函數一樣,因

爲reduce函數是業務處理邏輯的核心部分,所以不同作業的reduce函數也是不相同。

輸出格式

        輸出格式獲取reduce函數輸出的最終鍵/值對,並通過record write將它寫入到輸出文件中。每條記錄的鍵和值默認通過tab

分隔,不同記錄通過換行符分隔。雖然一般情況下可以通過自定義實現非常多的輸出格式,但是,不管什麼格式,最終的結果

都將寫到HDFS上。


附:

Mapper、Reducer類的源碼,通過源碼我們可以瞭解到在map、reduce階段也可以做一些自定義處理。

Mapper類中有4個主要的函數分別是:setup、map、cleanup、run,源碼如下:

public class Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT> {

  /**
	* The <code>Context</code> passed on to the {@link Mapper} implementations.
	*/
  public abstract class Context
    implements MapContext<KEYIN,VALUEIN,KEYOUT,VALUEOUT> {
  }
  
 /**
   * Called once at the beginning of the task.
   */
  protected void setup(Context context
                       ) throws IOException, InterruptedException {
    // NOTHING
  }

  /**
   * Called once for each key/value pair in the input split. Most applications
   * should override this, but the default is the identity function.
   */
  @SuppressWarnings("unchecked")
  protected void map(KEYIN key, VALUEIN value, 
                     Context context) throws IOException, InterruptedException {
    context.write((KEYOUT) key, (VALUEOUT) value);
  }

  /**
   * Called once at the end of the task.
   */
  protected void cleanup(Context context
                         ) throws IOException, InterruptedException {
    // NOTHING
  }
  
  /**
   * Expert users can override this method for more complete control over the
   * execution of the Mapper.
   * @param context
   * @throws IOException
   */
  public void run(Context context) throws IOException, InterruptedException {
    setup(context);
    try {
      while (context.nextKeyValue()) {
        map(context.getCurrentKey(), context.getCurrentValue(), context);
      }
    } finally {
      cleanup(context);
    }
  }
}
        由源碼可知,當調用到map時,通常會先執行一個setup函數,最後會執行一個cleanup函數。而默認情況下,這兩個函數的內容都是nothing。因此,

當map方法不符合應用要求時,可以試着通過增加setup和cleanup的內容來滿足應用的需求。

Recuder類中也有4個主要的函數分別是:setup、reduce、cleanup、run,源碼如下:

public class Reducer<KEYIN,VALUEIN,KEYOUT,VALUEOUT> {

	public abstract class Context
			implements ReduceContext<KEYIN,VALUEIN,KEYOUT,VALUEOUT> {
	}

	protected void setup(Context context
	                    ) throws IOException, InterruptedException {
		// NOTHING
	}

	@SuppressWarnings("unchecked")
	protected void reduce(KEYIN key, Iterable<VALUEIN> values, Context context
	                     ) throws IOException, InterruptedException {
		for(VALUEIN value: values) {
			context.write((KEYOUT) key, (VALUEOUT) value);
		}
	}
	
	protected void cleanup(Context context
	                      ) throws IOException, InterruptedException {
		// NOTHING
	}

	public void run(Context context) throws IOException, InterruptedException {
		setup(context);
		try {
			while (context.nextKey()) {
				reduce(context.getCurrentKey(), context.getValues(), context);
				// If a back up store is used, reset it
				Iterator<VALUEIN> iter = context.getValues().iterator();
				if(iter instanceof ReduceContext.ValueIterator) {
					((ReduceContext.ValueIterator<VALUEIN>)iter).resetBackupStore();
				}
			}
		} finally {
			cleanup(context);
		}
	}
}
       由源碼可知,當調用到reduce時,通常會先執行一個setup函數,最後會執行一個cleanup函數。而默認情況下,這兩個函數的內容都是nothing。因此

一般情況下reduce是中的setup和cleanup是沒有內容的,當處理結果不符合或需要更多處理時我們要編寫setup和cleanup的內容。

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