Hadoop-2.4.1學習之如何確定Mapper數量

       MapReduce框架的優勢是可以在集羣中並行運行mapper和reducer任務,那如何確定mapper和reducer的數量呢,或者說如何以編程的方式控製作業啓動的mapper和reducer數量呢?在《Hadoop-2.4.1學習之Mapper和Reducer》中曾經提及建議reducer的數量爲(0.95~1.75 ) * 節點數量 * 每個節點上最大的容器數,並可使用方法Job.setNumReduceTasks(int),mapper的數量由輸入文件的大小確定,且沒有相應的setNumMapTasks方法,但可以通過Configuration.set(JobContext.NUM_MAPS, int)設置,其中JobContext.NUM_MAPS的值爲mapreduce.job.maps,而在Hadoop的官方網站上對該參數的描述爲與MapReduce框架和作業配置巧妙地交互,並且設置起來更加複雜。從這樣一句含糊不清的話無法得知究竟如何確定mapper的數量,顯然只能求助於源代碼了。

      在hadoop中MapReduce作業通過JobSubmitter類的submitJobInternal(Jobjob, Cluster cluster)方法向系統提交作業(該方法不僅設置mapper數量,還執行了一些其它操作如檢查輸出格式等,感興趣的可以參考源代碼),在該方法中與設置mapper有關的代碼如下:

  1. int maps = writeSplits(job, submitJobDir);  
  2. conf.setInt(MRJobConfig.NUM_MAPS, maps);  
  3. LOG.info(”number of splits:” + maps);  

      方法writeSplits返回mapper的數量,該方法的源代碼如下:

  1. private int writeSplits(org.apache.hadoop.mapreduce.JobContext job,Path jobSubmitDir)   
  2. throws IOException,InterruptedException, ClassNotFoundException {  
  3.     JobConf jConf = (JobConf)job.getConfiguration();  
  4.     int maps;  
  5.     if (jConf.getUseNewMapper()) {  
  6.       maps = writeNewSplits(job, jobSubmitDir);  
  7.     } else {  
  8.       maps = writeOldSplits(jConf, jobSubmitDir);  
  9.     }  
  10.     return maps;  
  11.   }  

      在該方法中,根據是否使用了新版本的JobContext而使用不同的方法計算mapper數量,實際情況是jConf.getUseNewMapper()將返回true,因此將執行writeNewSplits(job,jobSubmitDir)語句,該方法的源代碼如下:

  1. Configuration conf = job.getConfiguration();  
  2. InputFormat<?, ?> input = ReflectionUtils.newInstance(job.getInputFormatClass(), conf);  
  3. List<InputSplit> splits = input.getSplits(job);  
  4. T[] array = (T[]) splits.toArray(new InputSplit[splits.size()]);  
  5.   
  6. // sort the splits into order based on size, so that the biggest  
  7. // go first  
  8. Arrays.sort(array, new SplitComparator());  
  9. JobSplitWriter.createSplitFiles(jobSubmitDir, conf, jobSubmitDir.getFileSystem(conf), array);  
  10. return array.length;  

      通過上面的代碼可以得知,實際的mapper數量爲輸入分片的數量,而分片的數量又由使用的輸入格式決定,默認爲TextInputFormat,該類爲FileInputFormat的子類。確定分片數量的任務交由FileInputFormat的getSplits(job)完成,在此補充一下FileInputFormat繼承自抽象類InputFormat,該類定義了MapReduce作業的輸入規範,其中的抽象方法List<InputSplit> getSplits(JobContext context)定義瞭如何將輸入分割爲InputSplit,不同的輸入有不同的分隔邏輯,而分隔得到的每個InputSplit交由不同的mapper處理,因此該方法的返回值確定了mapper的數量。下面將分爲兩部分學習該方法是如何在FileInputFormat中實現的,爲了將注意力集中在最重要的部分,對日誌輸出等信息將不做介紹,完整的實現可以參考源代碼。

      首先是第一部分,該部分代碼計算了最大InputSplit和最小InputSplit的值,如下:

  1. long minSize = Math.max(getFormatMinSplitSize(), getMinSplitSize(job));  
  2. long maxSize = getMaxSplitSize(job);  

      其中的getMinSplitSize和getMaxSplitSize方法分別用於獲取最小InputSplit和最大InputSplit的值,對應的配置參數分別爲mapreduce.input.fileinputformat.split.minsize,默認值爲1L和mapreduce.input.fileinputformat.split.maxsize,默認值爲Long.MAX_VALUE,十六進制數值爲 0x7fffffffffffffffL,對應的十進制爲9223372036854775807,getFormatMinSplitSize方法返回該輸入格式下InputSplit的下限。以上數字的單位都是byte。由此得出minSize的大小爲1L,maxSize的大小爲Long.MAX_VALUE。

      其次是生成InputSplit的第二部分。在該部分將生成包含InputSplit的List,而List的大小爲InputSplit的數量,進而確定了mapper的數量。其中重要的代碼爲:

  1. if (isSplitable(job, path)) {  
  2.           long blockSize = file.getBlockSize();  
  3.           long splitSize = computeSplitSize(blockSize, minSize, maxSize);  
  4.           long bytesRemaining = length;  
  5.           while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {  
  6.             int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);  
  7.             splits.add(makeSplit(path, length-bytesRemaining, splitSize,  
  8.                                      blkLocations[blkIndex].getHosts()));  
  9.             bytesRemaining -= splitSize;  
  10.           }  
  11.           if (bytesRemaining != 0) {  
  12.             int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);  
  13.             splits.add(makeSplit(path, length-bytesRemaining, bytesRemaining,  
  14.                        blkLocations[blkIndex].getHosts()));  
  15.           }  
  16. }  

      blockSize的值爲參數dfs.blocksize的值,默認爲128M。方法computeSplitSize(blockSize, minSize, maxSize)根據blockSize,minSize,maxSize確定InputSplit的大小,源代碼如下:

  1. Math.max(minSize, Math.min(maxSize, blockSize))  

      從該代碼並結合第一部分的分析可以得知,InputSplit的大小取決於dfs.blocksiz、mapreduce.input.fileinputformat.split.minsize、mapreduce.input.fileinputformat.split.maxsize和所使用的輸入格式。在輸入格式爲TextInputFormat的情況下,且不修改InputSplit的最大值和最小值的情況,InputSplit的最終值爲dfs.blocksize的值。

變量SPLIT_SLOP的值爲1.1,決定了當剩餘文件大小多大時停止按照變量splitSize分割文件。根據代碼可知,當剩餘文件小於等於1.1倍splitSize時,將把剩餘的文件做爲一個InputSplit,即最後一個InputSplit的大小最大爲1.1倍splitSize。

總結

      本文分析了在輸入格式爲默認的TextInputFormat的情況,如何確定mapper的數量。在不修改源代碼的情況下(修改輸入格式的InputSplit下限),程序員可以通過設置dfs.blocksiz、mapreduce.input.fileinputformat.split.minsize、mapreduce.input.fileinputformat.split.maxsize參數的值設置InputSplit的大小來影響InputSplit的數量,進而決定mapper的數量。當輸入爲其它格式時,處理邏輯又不相同了,比如當輸入格式爲DBInputFormat時,會根據輸入表的行數(記錄數)決定mapper的數量,更多細節可以參考源代碼。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章