Hadoop1.2.1源码解析系列:JT与TT之间的心跳通信机制——TT篇

来源:互联网 发布:手机理财软件哪个安全 编辑:程序博客网 时间:2024/05/17 21:39

在Hadoop中JT(JobTracker)与TT(TaskTracker)之间的通信是通过心跳机制完成的。JT实现InterTrackerProtocol协议,该协议定义了JT与TT之间的通信机制——心跳。心跳机制实际上就是一个RPC请求,JT作为Server,而TT作为Client,TT通过RPC调用JT的heartbeat方法,将TT自身的一些状态信息发送给JT,同时JT通过返回值返回对TT的指令。

心跳有三个作用:

1)判断TT是否活着

2)报告TT的资源情况以及任务运行情况

3)为TT发送指令(如运行task,kill task等)

下面详细阅读下涉及到心跳调用的源码。

首先我们需要清楚,心跳机制是TT调用JT的方法,而非JT主动调用TT的方法。TT通过transmitHeartBeat方法调用JT的heartbeat方法。

1.TaskTracker.transmitHeartBeat:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. // Send Counters in the status once every COUNTER_UPDATE_INTERVAL  
  2.     boolean sendCounters;  
  3.     if (now > (previousUpdate + COUNTER_UPDATE_INTERVAL)) {  
  4.       sendCounters = true;  
  5.       previousUpdate = now;  
  6.     }  
  7.     else {  
  8.       sendCounters = false;  
  9.     }  
根据sendCounters的间隔判断此次心跳是否发送计算器信息。

2.TaskTracker.transmitHeartBeat:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. 1.TaskTracker.transmitHeartBeat:  
[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. // Check if the last heartbeat got through...   
  2.     // if so then build the heartbeat information for the JobTracker;  
  3.     // else resend the previous status information.  
  4.     //  
  5.     if (status == null) {  
  6.       synchronized (this) {  
  7.         status = new TaskTrackerStatus(taskTrackerName, localHostname,   
  8.                                        httpPort,   
  9.                                        cloneAndResetRunningTaskStatuses(  
  10.                                          sendCounters),   
  11.                                        taskFailures,  
  12.                                        localStorage.numFailures(),  
  13.                                        maxMapSlots,  
  14.                                        maxReduceSlots);   
  15.       }  
  16.     } else {  
  17.       LOG.info("Resending 'status' to '" + jobTrackAddr.getHostName() +  
  18.                "' with reponseId '" + heartbeatResponseId);  
  19.     }  
此处根据status变量是否为null,判断上次的心跳是否成功发送。tatus!=null,则表示上次的心跳尚未发送,所以直接将上次收集到的TT状态信息(封装在status中)发送给JT;相反,status==null,则表示上次心跳已完成,重新收集TT的状态信息,同样封装到status中。下面详细看下new TaskTrackerStatus()方法。注意此处有个cloneAndResetRunningTaskStatuses(sendCounters)方法:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. private synchronized List<TaskStatus> cloneAndResetRunningTaskStatuses(  
  2.                                           boolean sendCounters) {  
  3.     List<TaskStatus> result = new ArrayList<TaskStatus>(runningTasks.size());  
  4.     for(TaskInProgress tip: runningTasks.values()) {  
  5.       TaskStatus status = tip.getStatus();  
  6.       status.setIncludeCounters(sendCounters);  
  7.       // send counters for finished or failed tasks and commit pending tasks  
  8.       if (status.getRunState() != TaskStatus.State.RUNNING) {  
  9.         status.setIncludeCounters(true);  
  10.       }  
  11.       result.add((TaskStatus)status.clone());  
  12.       status.clearStatus();  
  13.     }  
  14.     return result;  
  15.   }  
该方法中涉及到runningTasks队列,该队列保存了该TT上接收的所有未完成的Task任务,通过runningTasks.values()可以获取TT当前所有未完成的Task,然后获取每个TaskInProgress的status信息,同时根据第一步判断出的sendCounters(true/false)决定是否发送counters信息(includeCounters),即是否将counters对象序列化到TaskStatus对象中,这里需要注意如果TaskInProgress不处于Running状态,则includeCounters设为true,即发送counters信息。

3.TaskTrackerStatus():

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public TaskTrackerStatus(String trackerName, String host,   
  2.                            int httpPort, List<TaskStatus> taskReports,   
  3.                            int taskFailures, int dirFailures,  
  4.                            int maxMapTasks, int maxReduceTasks) {  
  5.     this.trackerName = trackerName;  
  6.     this.host = host;  
  7.     this.httpPort = httpPort;  
  8.   
  9.     this.taskReports = new ArrayList<TaskStatus>(taskReports);  
  10.     this.taskFailures = taskFailures;  
  11.     this.dirFailures = dirFailures;  
  12.     this.maxMapTasks = maxMapTasks;  
  13.     this.maxReduceTasks = maxReduceTasks;  
  14.     this.resStatus = new ResourceStatus();  
  15.     this.healthStatus = new TaskTrackerHealthStatus();  
  16.   }  
这里只是进行简单的变量复制操作,分析下其中一些参数的含义:

1)taskReports:包含该TT上目前所有的Task状态信息,其中的counters信息会根据之前判断sendCounters值进行决定是否发送,上一步有提到。

2)taskFailures:该TT上失败的Task总数(重启会清空),该参数帮助JT决定是否向该TT提交Task,因为失败数越多表明该TT可能出现Task失败的概率越大。

3)dirFailures:这个值是mapred.local.dir参数设置的目录中有多少是不可用的(以后会详细提到)

4)maxMapSlots/maxReduceSlots:这个值是TT可使用的最大map和reduce slot数量

初始化完成,继续回到TaskTracker.transmitHeartBeat方法。

4.TaskTracker.transmitHeartBeat:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. // Check if we should ask for a new Task  
  2.    //  
  3.    boolean askForNewTask;  
  4.    long localMinSpaceStart;  
  5.    synchronized (this) {  
  6.      askForNewTask =   
  7.        ((status.countOccupiedMapSlots() < maxMapSlots ||   
  8.          status.countOccupiedReduceSlots() < maxReduceSlots) &&   
  9.         acceptNewTasks);   
  10.      localMinSpaceStart = minSpaceStart;  
  11.    }  
  12.    if (askForNewTask) {  
  13.      askForNewTask = enoughFreeSpace(localMinSpaceStart);  
  14.      long freeDiskSpace = getFreeSpace();  
  15.      long totVmem = getTotalVirtualMemoryOnTT();  
  16.      long totPmem = getTotalPhysicalMemoryOnTT();  
  17.      long availableVmem = getAvailableVirtualMemoryOnTT();  
  18.      long availablePmem = getAvailablePhysicalMemoryOnTT();  
  19.      long cumuCpuTime = getCumulativeCpuTimeOnTT();  
  20.      long cpuFreq = getCpuFrequencyOnTT();  
  21.      int numCpu = getNumProcessorsOnTT();  
  22.      float cpuUsage = getCpuUsageOnTT();  
  23.   
  24.      status.getResourceStatus().setAvailableSpace(freeDiskSpace);  
  25.      status.getResourceStatus().setTotalVirtualMemory(totVmem);  
  26.      status.getResourceStatus().setTotalPhysicalMemory(totPmem);  
  27.      status.getResourceStatus().setMapSlotMemorySizeOnTT(  
  28.          mapSlotMemorySizeOnTT);  
  29.      status.getResourceStatus().setReduceSlotMemorySizeOnTT(  
  30.          reduceSlotSizeMemoryOnTT);  
  31.      status.getResourceStatus().setAvailableVirtualMemory(availableVmem);   
  32.      status.getResourceStatus().setAvailablePhysicalMemory(availablePmem);  
  33.      status.getResourceStatus().setCumulativeCpuTime(cumuCpuTime);  
  34.      status.getResourceStatus().setCpuFrequency(cpuFreq);  
  35.      status.getResourceStatus().setNumProcessors(numCpu);  
  36.      status.getResourceStatus().setCpuUsage(cpuUsage);  
  37.    }  
从源码中的注释可以知道,此处是TT根据自身资源使用情况判断是否接收new task。

首先第一步status.countOccupiedMapSlots()获得该TT上已占用的map slot数量:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /** 
  2.    * Get the number of occupied map slots. 
  3.    * @return the number of occupied map slots 
  4.    */  
  5.   public int countOccupiedMapSlots() {  
  6.     int mapSlotsCount = 0;  
  7.     for (TaskStatus ts : taskReports) {  
  8.       if (ts.getIsMap() && isTaskRunning(ts)) {  
  9.         mapSlotsCount += ts.getNumSlots();  
  10.       }  
  11.     }  
  12.     return mapSlotsCount;  
  13.   }  
方法内部是根据taskReports中的TaskStatus进行判断,这里计算的是map slot,所以会判断ts.getIsMap(),如果该task是map任务,且isTaskRunning()返回true,则获取该task所需的slot数量。isTaskRunning()方法内部判断逻辑是:该task处于RUNNING或者UNASSIGNED状态,或者处于CleanerUp阶段(这里可能是Task处于FAILED_UNCLEAN或者KILLED_UNCLEAN阶段)。这个方法会计算出TT当前已占用的map slot数量。同样的通过countOccupiedReduceSlots()方法计算出TT当前已占用的reduce slot数量。获取到occupied map/reduce slots后将其同maxMapSlots/maxReduceSlots进行比较,这里是“||”而非“&&”,表示只要有map slot或者有reduce slot就可以接收新任务,当然还需要满足acceptNewTasks==true的条件。acceptNewTasks会在其他地方根据TT可使用的空间进行合适的赋值。以上可以判断出是否可以接收新任务,即askForNewTask值。

localMinSpaceStart = minSpaceStart,minSpaceStart由mapred.local.dir.minspacestart参数决定,默认是0,即无限制,该值的意思应该是可接收新任务的localDirs最小的可用空间大小。接下来可以看到该值能够影响acceptNewTasks值。

当acceptNewTasks==true时,即初步判断可以接收新任务,会再次根据localMinSpaceStart判断是否可接收新任务。

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /** 
  2.    * Check if any of the local directories has enough 
  3.    * free space  (more than minSpace) 
  4.    *  
  5.    * If not, do not try to get a new task assigned  
  6.    * @return 
  7.    * @throws IOException  
  8.    */  
  9.   private boolean enoughFreeSpace(long minSpace) throws IOException {  
  10.     if (minSpace == 0) {  
  11.       return true;  
  12.     }  
  13.     return minSpace < getFreeSpace();  
  14.   }  
[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. private long getFreeSpace() throws IOException {  
  2.     long biggestSeenSoFar = 0;  
  3.     String[] localDirs = localStorage.getDirs();  
  4.     for (int i = 0; i < localDirs.length; i++) {  
  5.       DF df = null;  
  6.       if (localDirsDf.containsKey(localDirs[i])) {  
  7.         df = localDirsDf.get(localDirs[i]);  
  8.       } else {  
  9.         df = new DF(new File(localDirs[i]), fConf);  
  10.         localDirsDf.put(localDirs[i], df);  
  11.       }  
  12.   
  13.       long availOnThisVol = df.getAvailable();  
  14.       if (availOnThisVol > biggestSeenSoFar) {  
  15.         biggestSeenSoFar = availOnThisVol;  
  16.       }  
  17.     }  
  18.       
  19.     //Should ultimately hold back the space we expect running tasks to use but   
  20.     //that estimate isn't currently being passed down to the TaskTrackers      
  21.     return biggestSeenSoFar;  
  22.   }  
判断方法是获取所有的lcoalDir,计算出这些目录中可用空间最大一个目录的可用大小,为什么使用最大值作为可用大小,而不是所有目录可用空间总和,是因为localDir存放task的一些本地信息,这些信息是不能夸目录存放的,所以必须确保有一个目录能够容纳下所有的信息。当计算出freeSpace后,根据比较localMinSpaceStart值与freeSpace的大小决定是否接收新任务。

接下来就是获取TT的一些资源信息,如总虚拟内存,总物理内存,可用的虚拟内存,可用的物理内存,CPU使用情况等。接着将这些值添加到status中去,发送给JT。

5.TaskTracker.transmitHeartBeat:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //add node health information  
  2.       
  3.     TaskTrackerHealthStatus healthStatus = status.getHealthStatus();  
  4.     synchronized (this) {  
  5.       if (healthChecker != null) {  
  6.         healthChecker.setHealthStatus(healthStatus);  
  7.       } else {  
  8.         healthStatus.setNodeHealthy(true);  
  9.         healthStatus.setLastReported(0L);  
  10.         healthStatus.setHealthReport("");  
  11.       }  
  12.     }  
此处是检查TT的健康状况。

6.TaskTracker.transmitHeartBeat:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //  
  2. // Xmit the heartbeat  
  3. //  
  4. HeartbeatResponse heartbeatResponse = jobClient.heartbeat(status,   
  5.                                                           justStarted,  
  6.                                                           justInited,  
  7.                                                           askForNewTask,   
  8.                                                           heartbeatResponseId);  
此处通过RPC调用JT的heartbeat()方法。传的参数包括:status——TT自身的状态信息;justStarted——表示TT是否刚启动;justInited——表示TT是否刚初始化;askForNewTask——表示是否接收新任务;heartbeatResponseId——上次心跳返回的responseId。方法的返回值是一个HeartbeatResponse对象,具体JT内的heartbeat()方法如何处理以及HeartbeatResponse内容会另外分析。继续往下走。

7.TaskTracker.transmitHeartBeat:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //  
  2.     // The heartbeat got through successfully!  
  3.     //  
  4.     heartbeatResponseId = heartbeatResponse.getResponseId();  
  5.         
  6.     synchronized (this) {  
  7.       for (TaskStatus taskStatus : status.getTaskReports()) {  
  8.         if (taskStatus.getRunState() != TaskStatus.State.RUNNING &&  
  9.             taskStatus.getRunState() != TaskStatus.State.UNASSIGNED &&  
  10.             taskStatus.getRunState() != TaskStatus.State.COMMIT_PENDING &&  
  11.             !taskStatus.inTaskCleanupPhase()) {  
  12.           if (taskStatus.getIsMap()) {  
  13.             mapTotal--;  
  14.           } else {  
  15.             reduceTotal--;  
  16.           }  
  17.           myInstrumentation.completeTask(taskStatus.getTaskID());  
  18.           runningTasks.remove(taskStatus.getTaskID());  
  19.         }  
  20.       }  
  21.         
  22.       // Clear transient status information which should only  
  23.       // be sent once to the JobTracker  
  24.       for (TaskInProgress tip: runningTasks.values()) {  
  25.         tip.getStatus().clearStatus();  
  26.       }  
  27.     }  
  28.   
  29.     // Force a rebuild of 'status' on the next iteration  
  30.     status = null;                                  
  31.   
  32.     return heartbeatResponse;  
首先从HeartbeatResponse返回值中获取heartbeatResponseId。接下来对TT中的每个TaskInProgress的status信息进行判断,如果一个task处于SUCCEEDED/FAILED/KILLED状态,则表示该task已完成(不论是失败还是成功,亦或是被kill掉),如果该task是一个map任务,则mapTotal减一,该task是一个reduce任务,则reduceTotal减一,mapTotal/reduceTotal记录当前TT所有处于运行状态(非SUCCEEDED/FAILED/KILLED状态)的task数量。

myInstrumentation.completeTask(taskStatus.getTaskID())此处将该TT所有完成任务数加一,runningTasks.remove(taskStatus.getTaskID())则是将该task从runningTasks队列中移除,所以可以知道runningTasks中只包含未完成的task信息。

接下来是清除TaskInProgress的TaskStatus的临时信息(diagnosticInfo),从clearStatus()方法的注释可以看出diagnosticInfo信息只是在Task向TaskTracker,或者TaskTracker向JobTracker发送一个状态更新信息时的临时诊断信息,所以在发送完成之后需要清除。

到这里整个TaskTracker发送心跳信息的过程就完成了,方法返回值是HeartbeatResponse对象,即心跳的返回值。