转自:http://blog.csdn.net/androidlushangderen/article/details/41243505
 
 
 MapReduce五大过程已经分析过半了,上次分析完Map的过程,着实花费了我的很多时间,不过收获很大,值得了额,这次用同样的方法分析完了Reduce的过程,也算是彻底摸透了MapReduce思想的2个最最重要的思想了吧。好,废话不多,切入正题,在学习Reduce过程分析的之前,我特意查了书籍上或网络上相关的资料,我发现很大都是大同小异,缺乏对于源码的参照分析,所以我个人认为,我了可以在某些细节上讲得跟明白些,也许会比较好。因为Map和Reduce的过程的整体流程是非常相近的,如果你看过之前我写的Map Task的分析,相信你也能很快理解我的Reduce过程的分析的。Reduce过程的集中表现体现于Reduce Task中,Reduce Task与Map Reduce一样,分为Job-setup Task,  Job-cleanup Task, Task-cleanup Task和Reduce Task。我分析的主要是最后一个Reduce Task 。Reduce Task 主要分为5个阶段:
Shuffle------------------->Merge------------------->Sort------------------->Reduce------------------->Write
其中最重要的部分为前3部分,我也会花最多的时间描述前面3个阶段的任务。
       Shuffle阶段。我们知道,Reduce的任务在最最开始的时候,就是接收Map任务中输出的中间结果的数据,key-value根据特定的分区算法,给相应的Reduce任务做处理,所以这时需要Reduce任务去远程拷贝Map输出的中间数据了,这个过程就称作Shuffle阶段,所以这个阶段也称为Copy阶段。在Shuffle阶段中,有个GetMapEventsThread,会定期发送RPC请求,获取远程执行好的Map Task的列表,把他们的输出location映射到mapLocation中。
 
- ....  
-             
-             int numNewMaps = getMapCompletionEvents();  
-             if (LOG.isDebugEnabled()) {  
-               if (numNewMaps > 0) {  
-                 LOG.debug(reduceTask.getTaskID() + ": " +    
-                     "Got " + numNewMaps + " new map-outputs");   
-               }  
-             }  
-             Thread.sleep(SLEEP_TIME);  
-           }   
 
进入getMapCompletionEvents方法,继续看:
 
 
- ...  
-         for (TaskCompletionEvent event : events) {  
-           switch (event.getTaskStatus()) {  
-             case SUCCEEDED:  
-             {  
-               URI u = URI.create(event.getTaskTrackerHttp());  
-               String host = u.getHost();  
-               TaskAttemptID taskId = event.getTaskAttemptId();  
-               URL mapOutputLocation = new URL(event.getTaskTrackerHttp() +   
-                                       "/mapOutput?job=" + taskId.getJobID() +  
-                                       "&map=" + taskId +   
-                                       "&reduce=" + getPartition());  
-               List<MapOutputLocation> loc = mapLocations.get(host);  
-               if (loc == null) {  
-                 loc = Collections.synchronizedList  
-                   (new LinkedList<MapOutputLocation>());  
-                 mapLocations.put(host, loc);  
-                }  
-               
-               loc.add(new MapOutputLocation(taskId, host, mapOutputLocation));  
-               numNewMaps ++;  
-             }  
-             break;  
-             ....  
 
为了避免出现网络热点,Reduce Task对输出的位置进行了混洗的操作,然后保存到scheduleCopies中,后续的拷贝操作都是围绕着这个列表进行的。这个变量保存在了一个叫ReduceCopier的类里面。确认拷贝的目标位置,还只是Shuffle阶段的前半部分,这时看一下,执行的入口代码在哪里。回到Reduce Task的入口run()代码:
 
 
- public void run(JobConf job, final TaskUmbilicalProtocol umbilical)  
-     throws IOException, InterruptedException, ClassNotFoundException {  
-     this.umbilical = umbilical;  
-     job.setBoolean("mapred.skip.on", isSkipping());  
-   
-     if (isMapOrReduce()) {  
-       
-       copyPhase = getProgress().addPhase("copy");  
-       sortPhase  = getProgress().addPhase("sort");  
-       reducePhase = getProgress().addPhase("reduce");  
-     }  
-     
-     
-     TaskReporter reporter = new TaskReporter(getProgress(), umbilical,  
-         jvmContext);  
-     reporter.startCommunicationThread();  
-     
-     boolean useNewApi = job.getUseNewReducer();  
-     initialize(job, getJobID(), reporter, useNewApi);  
-   
-     
-     
-     if (jobCleanup) {  
-       
-       runJobCleanupTask(umbilical, reporter);  
-       return;  
-     }  
-     if (jobSetup) {  
-       
-       runJobSetupTask(umbilical, reporter);  
-       return;  
-     }  
-     if (taskCleanup) {  
-       
-       runTaskCleanupTask(umbilical, reporter);  
-       return;  
-     }  
-       
-     
-       
-     
-     codec = initCodec();  
-   
-     boolean isLocal = "local".equals(job.get("mapred.job.tracker", "local"));  
-     if (!isLocal) {  
-       reduceCopier = new ReduceCopier(umbilical, job, reporter);  
-       if (!reduceCopier.fetchOutputs()) {  
-           ......  
 
到了reduceCopier.fetchOutps()这里必须停一步了,因为后面的Shuffle阶段和Merge阶段都在这里实现:
 
 
-     public boolean fetchOutputs() throws IOException {  
-       int totalFailures = 0;  
-       int            numInFlight = 0, numCopied = 0;  
-       DecimalFormat  mbpsFormat = new DecimalFormat("0.00");  
-       final Progress copyPhase =   
-         reduceTask.getProgress().phase();  
-       
-       LocalFSMerger localFSMergerThread = null;  
-       
-       InMemFSMergeThread inMemFSMergeThread = null;  
-       GetMapEventsThread getMapEventsThread = null;  
-         
-       for (int i = 0; i < numMaps; i++) {  
-         copyPhase.addPhase();       
-       }  
-         
-       
-       copiers = new ArrayList<MapOutputCopier>(numCopiers);  
-         
-       
-       for (int i=0; i < numCopiers; i++) {  
-         
-         MapOutputCopier copier = new MapOutputCopier(conf, reporter,   
-             reduceTask.getJobTokenSecret());  
-         copiers.add(copier);  
-         
-         copier.start();  
-       }  
-         
-       
-       localFSMergerThread = new LocalFSMerger((LocalFileSystem)localFileSys);  
-       
-       inMemFSMergeThread = new InMemFSMergeThread();  
-       
-       localFSMergerThread.start();  
-       inMemFSMergeThread.start();  
-         
-       
-       getMapEventsThread = new GetMapEventsThread();  
-       getMapEventsThread.start();  
-       .....  
 
在上面的代码中出现很多陌生的Thread的定义,这个可以先不用管,我们发现getMapEventsThread就是在这里开启的,去获取了最新的位置,位置获取完成当然是要启动很多的拷贝线程了,这里叫做MapOutputCopier线程,作者是把他放入一个线程列表中,逐个开启。看看里面的具体实现,他是如何进行远程拷贝的呢。
 
 
- @Override  
-       public void run() {  
-         while (true) {          
-           try {  
-             MapOutputLocation loc = null;  
-             long size = -1;  
-               
-             synchronized (scheduledCopies) {  
-               
-               while (scheduledCopies.isEmpty()) {  
-                 
-                 scheduledCopies.wait();  
-               }  
-               
-               loc = scheduledCopies.remove(0);  
-             }  
-              
-             CopyOutputErrorType error = CopyOutputErrorType.OTHER_ERROR;  
-             readError = false;  
-             try {  
-               shuffleClientMetrics.threadBusy();  
-               
-               start(loc);  
-               
-               size = copyOutput(loc);  
-               shuffleClientMetrics.successFetch();  
-               
-               error = CopyOutputErrorType.NO_ERROR;  
-             } catch (IOException e) {  
-               
-               ....  
 
从location列表中去取出,然后进行拷贝操作,核心方法在copyOutput(),接着往里跟踪:
 
 
- .....  
-         
-         
-         MapOutput mapOutput = getMapOutput(loc, tmpMapOutput,  
-                                            reduceId.getTaskID().getId());  
 
继续往里:
 
 
- private MapOutput getMapOutput(MapOutputLocation mapOutputLoc,   
-                                      Path filename, int reduce)  
-       throws IOException, InterruptedException {  
-         
-         
-         URL url = mapOutputLoc.getOutputLocation();  
-         URLConnection connection = url.openConnection();  
-           
-         
-         InputStream input = setupSecureConnection(mapOutputLoc, connection);  
-    
-         ......  
-         
-         
-         
-         
-           
-         
-         
-         boolean shuffleInMemory = ramManager.canFitInMemory(decompressedLength);   
-   
-         
-         MapOutput mapOutput = null;  
-         if (shuffleInMemory) {  
-           if (LOG.isDebugEnabled()) {  
-             LOG.debug("Shuffling " + decompressedLength + " bytes (" +   
-                 compressedLength + " raw bytes) " +   
-                 "into RAM from " + mapOutputLoc.getTaskAttemptId());  
-           }  
-   
-           
-           mapOutput = shuffleInMemory(mapOutputLoc, connection, input,  
-                                       (int)decompressedLength,  
-                                       (int)compressedLength);  
-         } else {  
-           if (LOG.isDebugEnabled()) {  
-             LOG.debug("Shuffling " + decompressedLength + " bytes (" +   
-                 compressedLength + " raw bytes) " +   
-                 "into Local-FS from " + mapOutputLoc.getTaskAttemptId());  
-           }  
-             
-           
-           mapOutput = shuffleToDisk(mapOutputLoc, input, filename,   
-               compressedLength);  
-         }  
-               
-         return mapOutput;  
-       }  
 
在这里我们看到了,Hadoop通过URL资源定位符,获取远程输入流,进行操作的,在拷贝到本地的时候,还分了2种情况处理,当当前的内存能方得下当前数据的时候,放入内存中,放不下则写入到磁盘中。这里还出现了ShuffleRamManager的用法。至此,Shuffle阶段宣告完成。还是比较深的,一层,又一层的。
 
       Merger阶段。Merge阶段其实是和Shuffle阶段并行进行的,刚刚也看到了,在fetchOutputs中,这些相关进程都是同时开启的,
 
- public boolean fetchOutputs() throws IOException {  
-       int totalFailures = 0;  
-       int            numInFlight = 0, numCopied = 0;  
-       DecimalFormat  mbpsFormat = new DecimalFormat("0.00");  
-       final Progress copyPhase =   
-         reduceTask.getProgress().phase();  
-       
-       LocalFSMerger localFSMergerThread = null;  
-       
-       InMemFSMergeThread inMemFSMergeThread = null;  
-       ....  
 
Merge的主要工作就是合并数据,当内存中或者磁盘中的文件比较多的时候,将小文件进行合并变成大文件。挑出其中的一个run方法
- ....  
-       public void run() {  
-         LOG.info(reduceTask.getTaskID() + " Thread started: " + getName());  
-         try {  
-           boolean exit = false;  
-           do {  
-             exit = ramManager.waitForDataToMerge();  
-             if (!exit) {  
-               
-               doInMemMerge();  
 
目的非常明确,就是Merge操作,这是内存文件的合并线程的run方法,LocalFSMerger与此类似,不分析了。这个Mergr处理是并与Shuffle阶段的。在这里这2个阶段都完成了。还是有点复杂的。下面是相关的一些类关系图,主要要搞清4个线程是什么作用的。
 

4个线程的调用都是在ReduceCopier.fetchOutput()方法中进行的。在Shuffle,Merge阶段的后面就来到了,Sort阶段。
       Sort阶段,的任务和轻松,就是完成一次对内存和磁盘总的一次Merge合并操作,其中还会对其中进行一次sort排序操作。
 
- ....  
-     
-     copyPhase.complete();                         
-     setPhase(TaskStatus.Phase.SORT);  
-     statusUpdate(umbilical);  
-   
-     
-     final FileSystem rfs = FileSystem.getLocal(job).getRaw();  
-     RawKeyValueIterator rIter = isLocal  
-       ? Merger.merge(job, rfs, job.getMapOutputKeyClass(),  
-           job.getMapOutputValueClass(), codec, getMapFiles(rfs, true),  
-           !conf.getKeepFailedTaskFiles(), job.getInt("io.sort.factor", 100),  
-           new Path(getTaskID().toString()), job.getOutputKeyComparator(),  
-           reporter, spilledRecordsCounter, null)  
-       : reduceCopier.createKVIterator(job, rfs, reporter);  
 
那么Sort操作在哪里呢,就在最下面的createKVIterator中:
 
 
- private RawKeyValueIterator createKVIterator(  
-         JobConf job, FileSystem fs, Reporter reporter) throws IOException {  
-   
-       .....  
-       
-       Collections.sort(diskSegments, new Comparator<Segment<K,V>>() {  
-         public int compare(Segment<K, V> o1, Segment<K, V> o2) {  
-           if (o1.getLength() == o2.getLength()) {  
-             return 0;  
-           }  
-           return o1.getLength() < o2.getLength() ? -1 : 1;  
-         }  
-       });  
-   
-       
-       List<Segment<K,V>> finalSegments = new ArrayList<Segment<K,V>>();  
 
,Sort阶段的任务就是这么简单。下面看一下前3个阶段主要的执行流程,这3个阶段构成了Reduce Task的核心。
 

       Reduce阶段,跟随这个图的执行方向,接下来我们应该执行的是key-value的reduce()函数了,没错就是循环键值对,执行此函数
 
- ....  
-     
-     if (useNewApi) {  
-       runNewReducer(job, umbilical, reporter, rIter, comparator,   
-                     keyClass, valueClass);  
-     } else {  
-       runOldReducer(job, umbilical, reporter, rIter, comparator,   
-                     keyClass, valueClass);  
-     }  
 
在这里我们执行的就是runReducer方法了,我们往老的API跳:
 
 
- private <INKEY,INVALUE,OUTKEY,OUTVALUE>  
- void runOldReducer(JobConf job,  
-                    TaskUmbilicalProtocol umbilical,  
-                    final TaskReporter reporter,  
-                    RawKeyValueIterator rIter,  
-                    RawComparator<INKEY> comparator,  
-                    Class<INKEY> keyClass,  
-                    Class<INVALUE> valueClass) throws IOException {  
-   Reducer<INKEY,INVALUE,OUTKEY,OUTVALUE> reducer =   
-     ReflectionUtils.newInstance(job.getReducerClass(), job);  
-   
-   String finalName = getOutputName(getPartition());  
-   
-   
-   final RecordWriter<OUTKEY, OUTVALUE> out = new OldTrackingRecordWriter<OUTKEY, OUTVALUE>(  
-       reduceOutputCounter, job, reporter, finalName);  
-     
-   OutputCollector<OUTKEY,OUTVALUE> collector =   
-     new OutputCollector<OUTKEY,OUTVALUE>() {  
-       public void collect(OUTKEY key, OUTVALUE value)  
-         throws IOException {  
-         
-         out.write(key, value);  
-         
-         reporter.progress();  
-       }  
-     };  
-     
-   
-   try {  
-     
-     boolean incrProcCount = SkipBadRecords.getReducerMaxSkipGroups(job)>0 &&  
-       SkipBadRecords.getAutoIncrReducerProcCount(job);  
-       
-     
-     ReduceValuesIterator<INKEY,INVALUE> values = isSkipping() ?   
-         new SkippingReduceValuesIterator<INKEY,INVALUE>(rIter,   
-             comparator, keyClass, valueClass,   
-             job, reporter, umbilical) :  
-         new ReduceValuesIterator<INKEY,INVALUE>(rIter,   
-         job.getOutputValueGroupingComparator(), keyClass, valueClass,   
-         job, reporter);  
-     values.informReduceProgress();  
-     while (values.more()) {  
-       reduceInputKeyCounter.increment(1);  
-       
-       reducer.reduce(values.getKey(), values, collector, reporter);  
-       if(incrProcCount) {  
-         reporter.incrCounter(SkipBadRecords.COUNTER_GROUP,   
-             SkipBadRecords.COUNTER_REDUCE_PROCESSED_GROUPS, 1);  
-       }  
-       
-       values.nextKey();  
-       values.informReduceProgress();  
-     }  
-    
 
和Map Task的过程很类似,也正如我们预期的那样,循环迭代执行,这就是Reduce阶段。
 
        Write阶段。Write阶段是最后一个阶段,在用户自定义的reduce中,一般用户都会调用collect.collect方法,这时候就是写入的操作了。这时的写入就是将最后的结果写入HDFS作为最终结果了。这里先定义了OutputCollector的collect方法:
 
- OutputCollector<OUTKEY,OUTVALUE> collector =   
-       new OutputCollector<OUTKEY,OUTVALUE>() {  
-         public void collect(OUTKEY key, OUTVALUE value)  
-           throws IOException {  
-           
-           out.write(key, value);  
-           
-           reporter.progress();  
-         }  
-       };  
 
至此,完成了Reduce任务的所有阶段。下面是一张时序图,便于理解:
 

掌握了Map ,Reduce2个过程核心实现的过程将会帮助我们更加理解Hadoop作业运行的整个流程。整个分析的过程也许会有点枯燥,但是苦中作乐。
Reduce Task的学习笔记
原文:http://www.cnblogs.com/cxzdy/p/5043630.html