TaskTracker节点上的内存管理器
Hadoop平台的最大优势就是充分地利用了廉价的PC机,这也就使得集群中的工作节点存在一个重要的问题——节点所在的PC机内存资源有限(这里所说的工作节点指的是TaskTracker节点),执行任务时常常出现内存不够的情况,如:堆溢出错误;同时,该PC机也可能部署了其它集群的工作节点。针对这个问题,Hadoop专门在TaskTracker节点内部设计了一个后台线程——任务内存管理器(TaskMemoeryManagerThread),来管理工作节点使用的内存。其核心思想就是:一方面监控每一个正在执行的任务所占用的内存量,当某一个任务所占用的内存超过它所设置的最大使用内存时,就kill掉这个任务;另一方面也统计TaskTracker节点当前使用内存的总量,当这个总量超过管理员设置的内存上限值时,它就会选择一些合适的任务kill掉,以使得该工作节点使用的内存总量总是低于这个阈值。这个内存管理组件是可开关的,意思就是说如果TaskTracker节点设置的内存的使用上限值,则TaskTracker节点在其内部就会开启这个管理组件,否则,TaskTracker节点就不会开启这个管理组件。当我们部署的Hadoop集群与其它的集群共享硬件平台时,往往需要为集群中的工作节点配置内存使用上限制。另外,如果我们的Hadoop集群独享硬件平台的话,笔者也建议设置这个内存使用上限值,以便TaskTracker节点可以开启内存管理器,其原因将会在下面详细讲到。
首先来看看如何给一个TaskTracker节点设置内存使用上限?这个上限值totalMemoryAllottedForTasks通过该节点上设置的可同时执行的Map/Reduce任务最大数量和执行每一个Map/Reduce任务可使用的最大内存来确定,其具体计算如下:
- public static final long DISABLED_MEMORY_LIMIT = -1L;
- static final String MAPRED_CLUSTER_MAP_MEMORY_MB_PROPERTY = "mapred.cluster.map.memory.mb";
- static final String MAPRED_CLUSTER_REDUCE_MEMORY_MB_PROPERTY = "mapred.cluster.reduce.memory.mb";
- maxCurrentMapTasks = conf.getInt("mapred.tasktracker.map.tasks.maximum", 2);
- maxCurrentReduceTasks = conf.getInt("mapred.tasktracker.reduce.tasks.maximum", 2);
- mapSlotMemorySizeOnTT = fConf.getLong( JobTracker.MAPRED_CLUSTER_MAP_MEMORY_MB_PROPERTY, JobConf.DISABLED_MEMORY_LIMIT);
- reduceSlotSizeMemoryOnTT = fConf.getLong(JobTracker.MAPRED_CLUSTER_REDUCE_MEMORY_MB_PROPERTY, JobConf.DISABLED_MEMORY_LIMIT);
- totalMemoryAllottedForTasks = maxCurrentMapTasks * mapSlotMemorySizeOnTT + maxCurrentReduceTasks * reduceSlotSizeMemoryOnTT;
首先必须强调的是,TaskTracker节点的内存管理器所监控的内存使用量指的是JVM实例使用的内存(JVM进程是该工作节点为执行分配的Map/Reduce任务而开启)。当一个TaskTracker节点设置了内存使用上限值时,它就会在启动的时候开启这个内存管理器TaskMomeryManagerThread,显然,TaskMomeryManagerThread是一个后台工作线程,它的工作流程如下:
- public void run() {
- LOG.info("Starting thread: " + this.getClass());
- while (true) {
- // Print the processTrees for debugging.
- if (LOG.isDebugEnabled()) {
- StringBuffer tmp = new StringBuffer("[ ");
- for (ProcessTreeInfo p : processTreeInfoMap.values()) {
- tmp.append(p.getPID());
- tmp.append(" ");
- }
- LOG.debug("Current ProcessTree list : " + tmp.substring(0, tmp.length()) + "]");
- }
- //监控新添加的任务
- synchronized (tasksToBeAdded) {
- processTreeInfoMap.putAll(tasksToBeAdded);
- tasksToBeAdded.clear();
- }
- //取消对已完成任务的监控
- synchronized (tasksToBeRemoved) {
- for (TaskAttemptID tid : tasksToBeRemoved) {
- processTreeInfoMap.remove(tid);
- }
- tasksToBeRemoved.clear();
- }
- long memoryStillInUsage = 0;
- //计算正在节点上执行的任务所占用的内存总和
- for (Iterator<Map.Entry<TaskAttemptID, ProcessTreeInfo>> it = processTreeInfoMap.entrySet().iterator(); it.hasNext();) {
- Map.Entry<TaskAttemptID, ProcessTreeInfo> entry = it.next();
- TaskAttemptID tid = entry.getKey();
- ProcessTreeInfo ptInfo = entry.getValue();
- try {
- String pId = ptInfo.getPID();
- // Initialize any uninitialized processTrees
- if (pId == null) {
- // get pid from pid-file
- pId = getPid(ptInfo.pidFile);
- if (pId != null) {
- // PID will be null, either if the pid file is yet to be created
- // or if the tip is finished and we removed pidFile, but the TIP
- // itself is still retained in runningTasks till successful
- // transmission to JT
- // create process tree object
- ProcfsBasedProcessTree pt = new ProcfsBasedProcessTree(pId);
- LOG.debug("Tracking ProcessTree " + pId + " for the first time");
- ptInfo.setPid(pId);
- ptInfo.setProcessTree(pt);
- }
- }
- // End of initializing any uninitialized processTrees
- if (pId == null) {
- continue; // processTree cannot be tracked
- }
- LOG.debug("Constructing ProcessTree for : PID = " + pId + " TID = " + tid);
- ProcfsBasedProcessTree pTree = ptInfo.getProcessTree();
- pTree = pTree.getProcessTree(); // get the updated process-tree
- ptInfo.setProcessTree(pTree); // update ptInfo with proces-tree of
- // updated state
- long currentMemUsage = pTree.getCumulativeVmem();
- // as processes begin with an age 1, we want to see if there
- // are processes more than 1 iteration old.
- long curMemUsageOfAgedProcesses = pTree.getCumulativeVmem(1);
- long limit = ptInfo.getMemLimit();
- LOG.info("Memory usage of ProcessTree " + pId + " :" + currentMemUsage + "bytes. Limit : " + limit + "bytes");
- //检查当前任务所占用的内存是否超过了它所设置的最大内存使用量
- if (isProcessTreeOverLimit(tid.toString(), currentMemUsage, curMemUsageOfAgedProcesses, limit)) {
- // Task (the root process) is still alive and overflowing memory.
- // Clean up.
- String msg = "TaskTree [pid=" + pId + ",tipID=" + tid + "] is running beyond memory-limits. Current usage : " + currentMemUsage + "bytes. Limit : " + limit + "bytes. Killing task.";
- LOG.warn(msg);
- taskTracker.cleanUpOverMemoryTask(tid, true, msg);
- //kill掉当前正在执行的任务,由于它的内存使用超过限制.
- pTree.destroy();
- it.remove();
- LOG.info("Removed ProcessTree with root " + pId);
- } else {
- // Accounting the total memory in usage for all tasks that are still
- // alive and within limits.
- memoryStillInUsage += currentMemUsage;
- }
- } catch (Exception e) {
- // Log the exception and proceed to the next task.
- LOG.warn("Uncaught exception in TaskMemoryManager " + "while managing memory of " + tid + " : " + StringUtils.stringifyException(e));
- }
- }
- //如果内存使用总量超过设置的上限值则组要kill合适的正在执行的任务
- if (memoryStillInUsage > maxMemoryAllowedForAllTasks) {
- LOG.warn("The total memory in usage " + memoryStillInUsage + " is still overflowing TTs limits " + maxMemoryAllowedForAllTasks + ". Trying to kill a few tasks with the least progress.");
- killTasksWithLeastProgress(memoryStillInUsage);
- }
- // Sleep for some time before beginning next cycle
- try {
- LOG.debug(this.getClass() + " : Sleeping for " + monitoringInterval + " ms");
- Thread.sleep(monitoringInterval);
- } catch (InterruptedException ie) {
- LOG.warn(this.getClass() + " interrupted. Finishing the thread and returning.");
- return;
- }
- }
从上面的代码可以看出,TaskMemoeryManagerThread的工作流程很简单,它每隔monitoringIntervalms 就会统计一次正在运行的任务所占用的系统总内存,如果该TaskTracker节点当前正在执行的任务占用的总内存超过设置的阈值,内存管理器就会kill掉一些正在执行的任务,以保证内存使用总量低于这个阈值。不过,在统计之前,它需要加上新运行的任务,删除已经运行完了的任务。Task内存使用量的统计间隔时间monitoringInterval是通过TaskTracker节点的配置文件来设置的,对应的配置项为:mapred.tasktracker.taskmemory.monitoring-interval。这里就有一个问题了,TaskTracker节点是把每一个Map/Reduce任务交给对应的一个JVM实例来执行的,那么内存管理器是如何准确的获取到这些JVM进程的内存使用量的?
首先,TaskTracker节点在开启一个JVM实例来运行一个Map/Reduce任务时,会得到这个JVM实例的进程Id号;然后,它会把这个Map/Reduce任务实例和对应的JVM进程Id号一起交给TaskMemoeryManagerThread来管理和监控。我们知道,在Linux操作系统中,进程的相关信息(如cpu使用率,内存使用量)都存储在/proc/*/stat目录下,例如,进程Id号为16961的进程相关信息存放在文件/proc/16961/stat中(如下图所示)。而TaskMemoeryManagerThread正是通过读取并解析这个文件来获取该进程的内存使用量。另外,Linux系统中有进程树的概念,即一个进程可以创建若干个进程,这样就可能存在这样的情况,JVM实例在执行Task的时候可能创建了子进程,所以,为了统计准确就为每一个JVM进程创建了一个进程树使得在计算一个任务耗费的内存时可以加上它所有孙子进程占用的内存了。

再来谈一下为什么要建议给一个TaskTracker节点配置内存上限值以便其开启内存管理器。如果一个TaskTracker节点不开启内存管理器的话,那么默认的,每一个JVM实例可无节度地使用内存,直至达到系统的总内存容量(可能还包括虚拟内存)。这样的情况经常会使得JVM实例抛出运行时堆溢出错误,同时发生错误的JVM实例可能运行的Task即将完成,这无疑会严重地影响Job的执行效率。但如果一个TaskTracker节点开启了内存管理器,则当它使用的内存总量达到设置的上限值,它会选择一些合适的任务kill掉来保证那些进度大的任务避免发生内存不够的错误,这个选择策略如下:
1).第一优先选择Reduce任务;
2).第二优先选择进度小的任务。
话又说回来,一个不会开启子进程的任务所能使用的内存上限最终取决于系统分配给对应的JVM实例的内存总量,为了解决一些特殊的作业内存限制问题,Hadoop在Job级别开放了一个设置参数来配置运行该作业任务的JVM内存分配,该配置项为:mapred.child.java.opts,值的形式如:–Xms256m –Xmx256m –Xmn64m。
笔者在研究Hadoop-0.20.2.0版本的时候发现了一个有关TaskTracker节点内存管理器的bug:当TaskTracker节点接到JobTracker节点的重启命令之后,会关闭一系列的相关组件,然后再初始化并重启这些组件,但如果TaskTracker节点配置了内存管理器之后,它在TaskTracker节点的重启之前不会被关闭,但在重启之后TaskTracker又会重新创建一个内存管理器,由于内存管理器对应一个后台线程,所以就使得系统中同时有多个存活的内存管理器。
[转]http://blog.csdn.net/xhh198781/article/details/7446686
TaskTracker节点上的内存管理器的更多相关文章
- BBS项目详解(forms快速创建登陆页面,登陆验证、通过阅读器进行头像上传的预览、内存管理器)
BBS项目涉及的知识点 django中知识点 钩子函数(局部钩子和全局钩子) 1.局部钩子就是用来做合法性校验,比如用户名有没有被使用等 2.全局的就是用来做对比校验,比如两次输入的密码是否一致 3. ...
- STL内存管理器的分配策略
STL提供了很多泛型容器,如vector,list和map.程序员在使用这些容器时只需关心何时往容器内塞对象,而不用关心如何管理内存,需要用多少内存,这些STL容器极大地方便了C++程序的编写.例如可 ...
- PHP V5.2 中的新增功能,第 1 部分: 使用新的内存管理器
PHP V5.2:开始 2006 年 11 月发布了 PHP V5.2,它包括许多新增功能和错误修正.它废止了 5.1 版并被推荐给所有 PHP V5 用户进行升级.我最喜欢的实验室环境 —— Win ...
- C/C++内存管理器
C标准库提供了malloc,free,calloc,realloc,C++标准库还提供了new, new[], delete, delete[].这些用来管理内存,看起来够用了,为啥还要自己写一个内存 ...
- spark内存管理器--MemoryManager源码解析
MemoryManager内存管理器 内存管理器可以说是spark内核中最重要的基础模块之一,shuffle时的排序,rdd缓存,展开内存,广播变量,Task运行结果的存储等等,凡是需要使用内存的地方 ...
- Netty内存管理器ByteBufAllocator及内存分配
ByteBufAllocator 内存管理器: Netty 中内存分配有一个最顶层的抽象就是ByteBufAllocator,负责分配所有ByteBuf 类型的内存.功能其实不是很多,主要有以下几个重 ...
- DLL何时需共享内存管理器
Delphi创建DLL时,IDE自动生成的文档中写得很清楚,当在DLL中以动态数组或String做为参数或返回值时(即RTL自动维护的数据类型),请在每个工程文件的第一个单元加上ShareMem.这样 ...
- android的低内存管理器【转】
本文转载自:http://blog.csdn.net/haitaoliang/article/details/22092321 版权声明:本文为博主原创文章,未经博主允许不得转载. 安卓应用不用太在意 ...
- Linux内存描述之内存节点node–Linux内存管理(二)
日期 内核版本 架构 作者 GitHub CSDN 2016-06-14 Linux-4.7 X86 & arm gatieme LinuxDeviceDrivers Linux内存管理 #1 ...
随机推荐
- 关于PHP HTML <input type="file" name="img"/>上传图片,图片大小,宽高,后缀名。
在我们的系统中,不免要上传图片,视频等文件,在上传中,需要做的一些判断,文件大小等方面. 注意: 在php.ini 中的post_max_size,upload_max_filesize默认为2M,在 ...
- [转载] HTTP请求的TCP瓶颈分析
原文: http://bhsc881114.github.io/2015/06/23/HTTP%E8%AF%B7%E6%B1%82%E7%9A%84TCP%E7%93%B6%E9%A2%88%E5%8 ...
- 线程高级应用-心得8-java5线程并发库中同步集合Collections工具类的应用及案例分析
1. HashSet与HashMap的联系与区别? 区别:前者是单列后者是双列,就是hashmap有键有值,hashset只有键: 联系:HashSet的底层就是HashMap,可以参考HashSe ...
- commons-logging日志系统
日志的重要性是随着系统的膨胀而显现的,在一个庞大的系统中查错没有各种日志信息 是寸步难行的.所以在系统加入日志是必须的. 最原始的日志方式,就是在程序的适当地方添加System.out.prin ...
- Spring对Hibernate事务管理
谈Spring事务管理之前我们想一下在我们不用Spring的时候,在Hibernate中我们是怎么进行数据操作的.在Hibernate中 我们每次进行一个操作的的时候我们都是要先开启事务,然后进行数据 ...
- Java中List的使用
package ch8; import java.util.*; /** * Created by Jiqing on 2016/11/27. */ public class ListTest { p ...
- C++中的虚函数与纯虚函数
这个吧,我也不怎么知道,所以,大家来看这两篇文章哦: http://blog.csdn.net/hackbuteer1/article/details/7558868 http://blog.csdn ...
- 转:C++语言的15个晦涩特性
转自 http://blog.jobbole.com/54140/ 操作符重载和检查顺序 重载,(逗号),||或者&&操作符会引起混乱,因为它打破了正常的检查规则.通常情况下,逗号操作 ...
- 转:Unicode汉字编码表
转自:http://blog.csdn.net/huangxy10/article/details/10012119 Unicode汉字编码表 1 Unicode编码表 Unicode只有一个字符集 ...
- 晒幸福, qq空间晒法
qq空间晒法 1.成为老婆之后,还是说新交的女朋友,这会让女朋友感动