Hadoop平台的最大优势就是充分地利用了廉价的PC机,这也就使得集群中的工作节点存在一个重要的问题——节点所在的PC机内存资源有限(这里所说的工作节点指的是TaskTracker节点),执行任务时常常出现内存不够的情况,如:堆溢出错误;同时,该PC机也可能部署了其它集群的工作节点。针对这个问题,Hadoop专门在TaskTracker节点内部设计了一个后台线程——任务内存管理器(TaskMemoeryManagerThread),来管理工作节点使用的内存。其核心思想就是:一方面监控每一个正在执行的任务所占用的内存量,当某一个任务所占用的内存超过它所设置的最大使用内存时,就kill掉这个任务;另一方面也统计TaskTracker节点当前使用内存的总量,当这个总量超过管理员设置的内存上限值时,它就会选择一些合适的任务kill掉,以使得该工作节点使用的内存总量总是低于这个阈值。这个内存管理组件是可开关的,意思就是说如果TaskTracker节点设置的内存的使用上限值,则TaskTracker节点在其内部就会开启这个管理组件,否则,TaskTracker节点就不会开启这个管理组件。当我们部署的Hadoop集群与其它的集群共享硬件平台时,往往需要为集群中的工作节点配置内存使用上限制。另外,如果我们的Hadoop集群独享硬件平台的话,笔者也建议设置这个内存使用上限值,以便TaskTracker节点可以开启内存管理器,其原因将会在下面详细讲到。

首先来看看如何给一个TaskTracker节点设置内存使用上限?这个上限值totalMemoryAllottedForTasks通过该节点上设置的可同时执行的Map/Reduce任务最大数量和执行每一个Map/Reduce任务可使用的最大内存来确定,其具体计算如下:

  1. public static final long DISABLED_MEMORY_LIMIT = -1L;
  2. static final String MAPRED_CLUSTER_MAP_MEMORY_MB_PROPERTY = "mapred.cluster.map.memory.mb";
  3. static final String MAPRED_CLUSTER_REDUCE_MEMORY_MB_PROPERTY = "mapred.cluster.reduce.memory.mb";
  4. maxCurrentMapTasks = conf.getInt("mapred.tasktracker.map.tasks.maximum", 2);
  5. maxCurrentReduceTasks = conf.getInt("mapred.tasktracker.reduce.tasks.maximum", 2);
  6. mapSlotMemorySizeOnTT = fConf.getLong( JobTracker.MAPRED_CLUSTER_MAP_MEMORY_MB_PROPERTY,  JobConf.DISABLED_MEMORY_LIMIT);
  7. reduceSlotSizeMemoryOnTT = fConf.getLong(JobTracker.MAPRED_CLUSTER_REDUCE_MEMORY_MB_PROPERTY, JobConf.DISABLED_MEMORY_LIMIT);
  8. totalMemoryAllottedForTasks = maxCurrentMapTasks * mapSlotMemorySizeOnTT + maxCurrentReduceTasks * reduceSlotSizeMemoryOnTT;

首先必须强调的是,TaskTracker节点的内存管理器所监控的内存使用量指的是JVM实例使用的内存(JVM进程是该工作节点为执行分配的Map/Reduce任务而开启)。当一个TaskTracker节点设置了内存使用上限值时,它就会在启动的时候开启这个内存管理器TaskMomeryManagerThread,显然,TaskMomeryManagerThread是一个后台工作线程,它的工作流程如下:

  1. public void run() {
  2. LOG.info("Starting thread: " + this.getClass());
  3. while (true) {
  4. // Print the processTrees for debugging.
  5. if (LOG.isDebugEnabled()) {
  6. StringBuffer tmp = new StringBuffer("[ ");
  7. for (ProcessTreeInfo p : processTreeInfoMap.values()) {
  8. tmp.append(p.getPID());
  9. tmp.append(" ");
  10. }
  11. LOG.debug("Current ProcessTree list : " + tmp.substring(0, tmp.length()) + "]");
  12. }
  13. //监控新添加的任务
  14. synchronized (tasksToBeAdded) {
  15. processTreeInfoMap.putAll(tasksToBeAdded);
  16. tasksToBeAdded.clear();
  17. }
  18. //取消对已完成任务的监控
  19. synchronized (tasksToBeRemoved) {
  20. for (TaskAttemptID tid : tasksToBeRemoved) {
  21. processTreeInfoMap.remove(tid);
  22. }
  23. tasksToBeRemoved.clear();
  24. }
  25. long memoryStillInUsage = 0;
  26. //计算正在节点上执行的任务所占用的内存总和
  27. for (Iterator<Map.Entry<TaskAttemptID, ProcessTreeInfo>> it = processTreeInfoMap.entrySet().iterator(); it.hasNext();) {
  28. Map.Entry<TaskAttemptID, ProcessTreeInfo> entry = it.next();
  29. TaskAttemptID tid = entry.getKey();
  30. ProcessTreeInfo ptInfo = entry.getValue();
  31. try {
  32. String pId = ptInfo.getPID();
  33. // Initialize any uninitialized processTrees
  34. if (pId == null) {
  35. // get pid from pid-file
  36. pId = getPid(ptInfo.pidFile);
  37. if (pId != null) {
  38. // PID will be null, either if the pid file is yet to be created
  39. // or if the tip is finished and we removed pidFile, but the TIP
  40. // itself is still retained in runningTasks till successful
  41. // transmission to JT
  42. // create process tree object
  43. ProcfsBasedProcessTree pt = new ProcfsBasedProcessTree(pId);
  44. LOG.debug("Tracking ProcessTree " + pId + " for the first time");
  45. ptInfo.setPid(pId);
  46. ptInfo.setProcessTree(pt);
  47. }
  48. }
  49. // End of initializing any uninitialized processTrees
  50. if (pId == null) {
  51. continue; // processTree cannot be tracked
  52. }
  53. LOG.debug("Constructing ProcessTree for : PID = " + pId + " TID = " + tid);
  54. ProcfsBasedProcessTree pTree = ptInfo.getProcessTree();
  55. pTree = pTree.getProcessTree(); // get the updated process-tree
  56. ptInfo.setProcessTree(pTree); // update ptInfo with proces-tree of
  57. // updated state
  58. long currentMemUsage = pTree.getCumulativeVmem();
  59. // as processes begin with an age 1, we want to see if there
  60. // are processes more than 1 iteration old.
  61. long curMemUsageOfAgedProcesses = pTree.getCumulativeVmem(1);
  62. long limit = ptInfo.getMemLimit();
  63. LOG.info("Memory usage of ProcessTree " + pId + " :" + currentMemUsage + "bytes. Limit : " + limit + "bytes");
  64. //检查当前任务所占用的内存是否超过了它所设置的最大内存使用量
  65. if (isProcessTreeOverLimit(tid.toString(), currentMemUsage, curMemUsageOfAgedProcesses, limit)) {
  66. // Task (the root process) is still alive and overflowing memory.
  67. // Clean up.
  68. String msg = "TaskTree [pid=" + pId + ",tipID=" + tid + "] is running beyond memory-limits. Current usage : " + currentMemUsage + "bytes. Limit : " + limit + "bytes. Killing task.";
  69. LOG.warn(msg);
  70. taskTracker.cleanUpOverMemoryTask(tid, true, msg);
  71. //kill掉当前正在执行的任务,由于它的内存使用超过限制.
  72. pTree.destroy();
  73. it.remove();
  74. LOG.info("Removed ProcessTree with root " + pId);
  75. } else {
  76. // Accounting the total memory in usage for all tasks that are still
  77. // alive and within limits.
  78. memoryStillInUsage += currentMemUsage;
  79. }
  80. } catch (Exception e) {
  81. // Log the exception and proceed to the next task.
  82. LOG.warn("Uncaught exception in TaskMemoryManager " + "while managing memory of " + tid + " : " + StringUtils.stringifyException(e));
  83. }
  84. }
  85. //如果内存使用总量超过设置的上限值则组要kill合适的正在执行的任务
  86. if (memoryStillInUsage > maxMemoryAllowedForAllTasks) {
  87. LOG.warn("The total memory in usage " + memoryStillInUsage + " is still overflowing TTs limits " + maxMemoryAllowedForAllTasks + ". Trying to kill a few tasks with the least progress.");
  88. killTasksWithLeastProgress(memoryStillInUsage);
  89. }
  90. // Sleep for some time before beginning next cycle
  91. try {
  92. LOG.debug(this.getClass() + " : Sleeping for " + monitoringInterval + " ms");
  93. Thread.sleep(monitoringInterval);
  94. } catch (InterruptedException ie) {
  95. LOG.warn(this.getClass() + " interrupted. Finishing the thread and returning.");
  96. return;
  97. }
  98. }

从上面的代码可以看出,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节点上的内存管理器的更多相关文章

  1. BBS项目详解(forms快速创建登陆页面,登陆验证、通过阅读器进行头像上传的预览、内存管理器)

    BBS项目涉及的知识点 django中知识点 钩子函数(局部钩子和全局钩子) 1.局部钩子就是用来做合法性校验,比如用户名有没有被使用等 2.全局的就是用来做对比校验,比如两次输入的密码是否一致 3. ...

  2. STL内存管理器的分配策略

    STL提供了很多泛型容器,如vector,list和map.程序员在使用这些容器时只需关心何时往容器内塞对象,而不用关心如何管理内存,需要用多少内存,这些STL容器极大地方便了C++程序的编写.例如可 ...

  3. PHP V5.2 中的新增功能,第 1 部分: 使用新的内存管理器

    PHP V5.2:开始 2006 年 11 月发布了 PHP V5.2,它包括许多新增功能和错误修正.它废止了 5.1 版并被推荐给所有 PHP V5 用户进行升级.我最喜欢的实验室环境 —— Win ...

  4. C/C++内存管理器

    C标准库提供了malloc,free,calloc,realloc,C++标准库还提供了new, new[], delete, delete[].这些用来管理内存,看起来够用了,为啥还要自己写一个内存 ...

  5. spark内存管理器--MemoryManager源码解析

    MemoryManager内存管理器 内存管理器可以说是spark内核中最重要的基础模块之一,shuffle时的排序,rdd缓存,展开内存,广播变量,Task运行结果的存储等等,凡是需要使用内存的地方 ...

  6. Netty内存管理器ByteBufAllocator及内存分配

    ByteBufAllocator 内存管理器: Netty 中内存分配有一个最顶层的抽象就是ByteBufAllocator,负责分配所有ByteBuf 类型的内存.功能其实不是很多,主要有以下几个重 ...

  7. DLL何时需共享内存管理器

    Delphi创建DLL时,IDE自动生成的文档中写得很清楚,当在DLL中以动态数组或String做为参数或返回值时(即RTL自动维护的数据类型),请在每个工程文件的第一个单元加上ShareMem.这样 ...

  8. android的低内存管理器【转】

    本文转载自:http://blog.csdn.net/haitaoliang/article/details/22092321 版权声明:本文为博主原创文章,未经博主允许不得转载. 安卓应用不用太在意 ...

  9. Linux内存描述之内存节点node–Linux内存管理(二)

    日期 内核版本 架构 作者 GitHub CSDN 2016-06-14 Linux-4.7 X86 & arm gatieme LinuxDeviceDrivers Linux内存管理 #1 ...

随机推荐

  1. 利用PinYin4j 实现List中的对象按数字,字母, 汉字排序

    要排序的对象: import net.sourceforge.pinyin4j.PinyinHelper; import net.sourceforge.pinyin4j.format.HanyuPi ...

  2. <<易货>>项目Postmortem结果

    设想和目标 我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述? 一开始想做的事情还是太多,没有形成整个app的核心功能,浪费了很多时间. 是否有充足的时间来做计划? 有 ...

  3. QQServer_update

    import java.awt.*; import javax.swing.*; import java.net.*; import java.io.*; import java.awt.event. ...

  4. git各种撤销操作

    撤销git add: git reset HEAD+路径 或者git reset --+路径 撤销commit: 1.回退到具体的嘻哈值 git reset --hard     2.回退后仍包含本地 ...

  5. Git与Repo入门(转载)

    aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAykAAADuCAIAAACyDd+sAAAAA3NCSVQICAjb4U/gAAAgAElEQVR4Xu ...

  6. css技术和实例

    今天,我为大家收集精选了30个使用纯CSS完成的强大实践的优秀CSS技术和实例,您将在这里发现很多与众不同的技术,比如:图片集.阴影效果.可扩展按钮.菜单等-这些实例都是使用纯CSS和HTML实现的. ...

  7. 【转载】Spark系列之运行原理和架构

    参考 http://www.cnblogs.com/shishanyuan/p/4721326.html 1. Spark运行架构 1.1 术语定义 lApplication:Spark Applic ...

  8. Android版本升级同时Sqlite数据库的升级及之前数据的保留

    http://www.cnblogs.com/wang340/archive/2013/05/06/3063135.html http://www.eoeandroid.com/forum.php?m ...

  9. spring中的Log4jConfigListener作用和webapp.root的设置

    转:http://blog.sina.com.cn/s/blog_7bbf356c01016wld.html 使用spring中的Log4jConfigListener有如如下好处:     1. 动 ...

  10. 安卓虚拟机启动失败intel haxm未安装

    1:环境是android studio 在AVD中启动显示,提示当前电脑为安装HAXM emulator: ERROR: x86 emulation currently requires hardwa ...