转自:http://blog.csdn.net/Androidlushangderen/article/details/50282593

前言

最近在运维我们部门的hadoop集群时,发现了很多Job OOM的现象,因为在机器上可以用命令进行查看,full gc比较严重.我们都知道,full gc带来的后果是比较大的,会"stop the world"的,一旦你的full gc elapsed time超过几分钟,那么其他的活动都得暂停这么多时间.所以Full gc一旦出现并且异常,一定要找到根源并将其解决.本篇文章就为大家讲述一下我们是如何解决这类问题并且在这个基础上做了一些别的什么优化的.

Full Gc源于何处

OOM发生了,导致频繁的Full Gc的现象,首先就要想到底是什么导致Full gc.第一个联想一定是上面跑的Job,Job的运行时的体现又是从每个task身上来的,而每个Task又是表现于每个TaskAttempt.而TaskAttempt是跑在所申请到的container中的.每个container都是一个独立的进程,你可以在某台datanode上用jps命令,可以看到很多叫"YarnChild"的进程,那就是container所起的.找到了源头之后,我们估计会想,那应该是container所启的jvm的内存大小配小了,导致内存不够用了,内存配大些不就可以解决问题了?事实上问题并没有这么简单,这里面的水还是有点深的.

为什么会发生Full Gc

一.为什么会发生full gc,第一种原因就是平常大家所说的内存配小了,就是下面2个配置项:

  1. public static final String MAP_MEMORY_MB = "mapreduce.map.memory.mb";
  1. public static final String REDUCE_MEMORY_MB = "mapreduce.reduce.memory.mb";

默认都是1024M,就是1个G.

二.另外一个原因估计想到的人就不太多了,除非你真的在生活中碰到过,概况来说一句话:错误的配置导致.上面的这个配置其实并不是container设置他所启的jvm的配置,而是每个Task所能用的内存的上限值,但是这里会有个前提,你的jvm必须保证可以用到这么多的内存,如果你的jvm最大内存上限就只有512M,你的task的memory设的再大也没有,最后造成的直接后果就是内存一用超,就会出现full gc.上面的2个值更偏向于理论值.而真正掌控jvm的配置项的其实是这2个配置:

  1. public static final String MAP_JAVA_OPTS = "mapreduce.map.java.opts";
  1. public static final String REDUCE_JAVA_OPTS = "mapreduce.reduce.java.opts";

所以理想的配置应该是java.opts的值必须大于等于memory.mb的值.所以说,这种配置不当的方式也会引发频繁的full gc.

Container内存监控

不过比较幸运的是针对上面所列举的第二种问题,hadoop自身已经对此进行了contaienr级别的监控,对于所有启动过container,他会额外开启一个叫container-monitor的线程,专门有对于这些container的pmem(物理内存),vmem(虚拟内存)的监控.相关的配置属于如下:

  1. String org.apache.hadoop.yarn.conf.YarnConfiguration.NM_PMEM_CHECK_ENABLED = "yarn.nodemanager.pmem-check-enabled"
  2. String org.apache.hadoop.yarn.conf.YarnConfiguration.NM_VMEM_CHECK_ENABLED = "yarn.nodemanager.vmem-check-enabled"

默认都是开启的.内存监控的意思就是一旦这个container所使用的内存超过这个jvm本身所能使用的最大上限值,则将此conyainer kill掉.下面简单的从源代码的级别为大家分析一下,过其实不难.首先进入到ContainersMonitorImpl.java这个类.

  1. @Override
  2. protected void serviceInit(Configuration conf) throws Exception {
  3. this.monitoringInterval =
  4. conf.getLong(YarnConfiguration.NM_CONTAINER_MON_INTERVAL_MS,
  5. YarnConfiguration.DEFAULT_NM_CONTAINER_MON_INTERVAL_MS);
  6. ....
  7. pmemCheckEnabled = conf.getBoolean(YarnConfiguration.NM_PMEM_CHECK_ENABLED,
  8. YarnConfiguration.DEFAULT_NM_PMEM_CHECK_ENABLED);
  9. vmemCheckEnabled = conf.getBoolean(YarnConfiguration.NM_VMEM_CHECK_ENABLED,
  10. YarnConfiguration.DEFAULT_NM_VMEM_CHECK_ENABLED);
  11. LOG.info("Physical memory check enabled: " + pmemCheckEnabled);
  12. LOG.info("Virtual memory check enabled: " + vmemCheckEnabled);
  13. ....

在serviceInit方法中就会从配置中读取是否开启内存监控功能,并输出日志信息.然后我们直接进入到此类的MonitorThread监控线程类中.

  1. ....
  2. private class MonitoringThread extends Thread {
  3. public MonitoringThread() {
  4. super("Container Monitor");
  5. }
  6. @Override
  7. public void run() {
  8. while (true) {
  9. // Print the processTrees for debugging.
  10. if (LOG.isDebugEnabled()) {
  11. StringBuilder tmp = new StringBuilder("[ ");
  12. for (ProcessTreeInfo p : trackingContainers.values()) {
  13. tmp.append(p.getPID());
  14. tmp.append(" ");
  15. }
  16. ....

在监控线程的run方法中,他会对所监控的container做遍历判断处理

  1. @Override
  2. public void run() {
  3. while (true) {
  4. ....
  5. // Now do the monitoring for the trackingContainers
  6. // Check memory usage and kill any overflowing containers
  7. long vmemStillInUsage = 0;
  8. long pmemStillInUsage = 0;
  9. for (Iterator<Map.Entry<ContainerId, ProcessTreeInfo>> it =
  10. trackingContainers.entrySet().iterator(); it.hasNext();) {
  11. Map.Entry<ContainerId, ProcessTreeInfo> entry = it.next();
  12. ContainerId containerId = entry.getKey();
  13. ProcessTreeInfo ptInfo = entry.getValue();
  14. try {
  15. String pId = ptInfo.getPID();
  16. ....

我们以物理内存监控的原理实现为一个例子.

首先他会根据pTree拿到进程相关的运行信息,比如内存,CPU信息等

  1. LOG.debug("Constructing ProcessTree for : PID = " + pId
  2. + " ContainerId = " + containerId);
  3. ResourceCalculatorProcessTree pTree = ptInfo.getProcessTree();
  4. pTree.updateProcessTree();    // update process-tree
  5. long currentVmemUsage = pTree.getVirtualMemorySize();
  6. long currentPmemUsage = pTree.getRssMemorySize();
  7. // if machine has 6 cores and 3 are used,
  8. // cpuUsagePercentPerCore should be 300% and
  9. // cpuUsageTotalCoresPercentage should be 50%
  10. float cpuUsagePercentPerCore = pTree.getCpuUsagePercent();
  11. float cpuUsageTotalCoresPercentage = cpuUsagePercentPerCore /
  12. resourceCalculatorPlugin.getNumProcessors();

然后拿到内存使用上限值

  1. // Multiply by 1000 to avoid losing data when converting to int
  2. int milliVcoresUsed = (int) (cpuUsageTotalCoresPercentage * 1000
  3. * maxVCoresAllottedForContainers /nodeCpuPercentageForYARN);
  4. // as processes begin with an age 1, we want to see if there
  5. // are processes more than 1 iteration old.
  6. long curMemUsageOfAgedProcesses = pTree.getVirtualMemorySize(1);
  7. long curRssMemUsageOfAgedProcesses = pTree.getRssMemorySize(1);
  8. long vmemLimit = ptInfo.getVmemLimit();
  9. long pmemLimit = ptInfo.getPmemLimit();

而这个pememLimit就不是pTree的信息,而是来自于外界所启动container时候所传进来的值,这个值其实就是java.opts的值.

  1. ContainerId containerId = monitoringEvent.getContainerId();
  2. switch (monitoringEvent.getType()) {
  3. case START_MONITORING_CONTAINER:
  4. ContainerStartMonitoringEvent startEvent =
  5. (ContainerStartMonitoringEvent) monitoringEvent;
  6. synchronized (this.containersToBeAdded) {
  7. ProcessTreeInfo processTreeInfo =
  8. new ProcessTreeInfo(containerId, null, null,
  9. startEvent.getVmemLimit(), startEvent.getPmemLimit(),
  10. startEvent.getCpuVcores());
  11. this.containersToBeAdded.put(containerId, processTreeInfo);
  12. }
  13. break;

然后是内存监控的核心逻辑

  1. ....
  2. } else if (isPmemCheckEnabled()
  3. && isProcessTreeOverLimit(containerId.toString(),
  4. currentPmemUsage, curRssMemUsageOfAgedProcesses,
  5. pmemLimit)) {
  6. // Container (the root process) is still alive and overflowing
  7. // memory.
  8. // Dump the process-tree and then clean it up.
  9. msg = formatErrorMessage("physical",
  10. currentVmemUsage, vmemLimit,
  11. currentPmemUsage, pmemLimit,
  12. pId, containerId, pTree);
  13. isMemoryOverLimit = true;
  14. containerExitStatus = ContainerExitStatus.KILLED_EXCEEDED_PMEM;
  15. ....

传入当前的内存使用量和限制值然后做比较,isProcessTreeOverLimit最终会调用到下面的这个方法.

  1. /**
  2. * Check whether a container's process tree's current memory usage is over
  3. * limit.
  4. *
  5. * When a java process exec's a program, it could momentarily account for
  6. * double the size of it's memory, because the JVM does a fork()+exec()
  7. * which at fork time creates a copy of the parent's memory. If the
  8. * monitoring thread detects the memory used by the container tree at the
  9. * same instance, it could assume it is over limit and kill the tree, for no
  10. * fault of the process itself.
  11. *
  12. * We counter this problem by employing a heuristic check: - if a process
  13. * tree exceeds the memory limit by more than twice, it is killed
  14. * immediately - if a process tree has processes older than the monitoring
  15. * interval exceeding the memory limit by even 1 time, it is killed. Else it
  16. * is given the benefit of doubt to lie around for one more iteration.
  17. *
  18. * @param containerId
  19. *          Container Id for the container tree
  20. * @param currentMemUsage
  21. *          Memory usage of a container tree
  22. * @param curMemUsageOfAgedProcesses
  23. *          Memory usage of processes older than an iteration in a container
  24. *          tree
  25. * @param vmemLimit
  26. *          The limit specified for the container
  27. * @return true if the memory usage is more than twice the specified limit,
  28. *         or if processes in the tree, older than this thread's monitoring
  29. *         interval, exceed the memory limit. False, otherwise.
  30. */
  31. boolean isProcessTreeOverLimit(String containerId,
  32. long currentMemUsage,
  33. long curMemUsageOfAgedProcesses,
  34. long vmemLimit) {
  35. boolean isOverLimit = false;
  36. if (currentMemUsage > (2 * vmemLimit)) {
  37. LOG.warn("Process tree for container: " + containerId
  38. + " running over twice " + "the configured limit. Limit=" + vmemLimit
  39. + ", current usage = " + currentMemUsage);
  40. isOverLimit = true;
  41. } else if (curMemUsageOfAgedProcesses > vmemLimit) {
  42. LOG.warn("Process tree for container: " + containerId
  43. + " has processes older than 1 "
  44. + "iteration running over the configured limit. Limit=" + vmemLimit
  45. + ", current usage = " + curMemUsageOfAgedProcesses);
  46. isOverLimit = true;
  47. }
  48. return isOverLimit;
  49. }

有2种情况会导致内存超出的现象,1个是使用内存超出限制内存2倍,理由是新的jvm会执行fork和exec操作,fork操作会拷贝父进程的信息,还有1个就是内存年龄值的限制.其他的上面注释已经写的很清楚了,如果还看不懂英文的话,自行找工具翻译.

最后如果发现container内存使用的确是超出内存限制值了,之后,就会发送container kill的event事件,会触发后续的container kill的动作.

  1. ....
  2. } else if (isVcoresCheckEnabled()
  3. && cpuUsageTotalCoresPercentage > vcoresLimitedRatio) {
  4. msg =
  5. String.format(
  6. "Container [pid=%s,containerID=%s] is running beyond %s vcores limits."
  7. + " Current usage: %s. Killing container.\n", pId,
  8. containerId, vcoresLimitedRatio);
  9. isCpuVcoresOverLimit = true;
  10. containerExitStatus = ContainerExitStatus.KILLED_EXCEEDED_VCORES;
  11. }
  12. if (isMemoryOverLimit) {
  13. // Virtual or physical memory over limit. Fail the container and
  14. // remove
  15. // the corresponding process tree
  16. LOG.warn(msg);
  17. // warn if not a leader
  18. if (!pTree.checkPidPgrpidForMatch()) {
  19. LOG.error("Killed container process with PID " + pId
  20. + " but it is not a process group leader.");
  21. }
  22. // kill the container
  23. eventDispatcher.getEventHandler().handle(
  24. new ContainerKillEvent(containerId,
  25. containerExitStatus, msg));
  26. it.remove();
  27. LOG.info("Removed ProcessTree with root " + pId);
  28. } else {
  29. ...

这就是container的内存监控的整个过程.我们当时又恰巧把这个功能给关了,所以导致了大量的Ful gc的现象.

为什么只对Container内存做监控

对于小标题上的问题,不知道有没有哪位同学想过?当时我在解决掉这个问题之后,我就在想,同样是很关键的指标,CPU的使用监控为什么不在ContainersMonitorImpl一起做掉呢.下面是我个人所总结出来的几点原因.

1.内存问题所造成的结果比CPU使用造成的影响更大,因为OOM问题一旦发生,就会引起gc.

2.内存问题比较CPU使用问题更加常见.因为大家在平常生活或写程序时,经常发碰到类似"啊,内存不够用了"等类似的问题,相对比较少碰到"CPU不够用了"的问题.

3.内存问题与Job运行规模,数据量使用规模密切相关.内存的使用与Job所处理的数据量密切相关,一般大Job,处理数据量大了,内存使用自然会变多,CPU也会变多,但不会那么明显.

综上3点原因,所以CPU监控并没有被加入到监控代码中(个人分析而言).

但是hadop自身没有加CPU监控并不代表我们不可以加这样的监控,有一些程序可能就是那种应用内存并不多,但是会耗尽很多CPU资源的程序,比如说开大量的线程,但是每个线程都在做很简单的操作,就会造成机器线程占比过高的问题.基于这个出发点,我添加了CPU使用百分比的监控.

Container的Cpu使用率监控

首先你要定义是否开启此功能的配置:

  1. /** Specifies whether cpu vcores check is enabled. */
  2. public static final String NM_VCORES_CHECK_ENABLED = NM_PREFIX
  3. + "vcores-check-enabled";
  4. public static final boolean DEFAULT_NM_VCORES_CHECK_ENABLED = false;

因为是新功能,默认是关闭的,然后你还需要定义1个使用阈值,在0~1之间,就是说一旦某个container的使用CPU的百分比超过这个值,就会被kill.

  1. /** Limit ratio of Virtual CPU Cores which can be allocated for containers. */
  2. public static final String NM_VCORES_LIMITED_RATIO = NM_PREFIX
  3. + "resource.cpu-vcores.limited.ratio";
  4. public static final float DEFAULT_NM_VCORES_LIMITED_RATIO = 0.8f;

默认这个值0.8,这个可以你随便设置.监控代码的逻辑,与内存监控完全类似,我将比较快的带过.

多定义2个变量值

  1. private boolean pmemCheckEnabled;
  2. ...
  3. private boolean vcoresCheckEnabled;
  4. private float vcoresLimitedRatio;

然后在serviceInit中进程配置初始化工作

  1. ...
  2. pmemCheckEnabled = conf.getBoolean(YarnConfiguration.NM_PMEM_CHECK_ENABLED,
  3. YarnConfiguration.DEFAULT_NM_PMEM_CHECK_ENABLED);
  4. vmemCheckEnabled = conf.getBoolean(YarnConfiguration.NM_VMEM_CHECK_ENABLED,
  5. YarnConfiguration.DEFAULT_NM_VMEM_CHECK_ENABLED);
  6. vcoresCheckEnabled =
  7. conf.getBoolean(YarnConfiguration.NM_VCORES_CHECK_ENABLED,
  8. YarnConfiguration.DEFAULT_NM_VCORES_CHECK_ENABLED);
  9. LOG.info("Physical memory check enabled: " + pmemCheckEnabled);
  10. LOG.info("Virtual memory check enabled: " + vmemCheckEnabled);
  11. LOG.info("Cpu vcores check enabled: " + vcoresCheckEnabled);
  12. if (vcoresCheckEnabled) {
  13. vcoresLimitedRatio =
  14. conf.getFloat(YarnConfiguration.NM_VCORES_LIMITED_RATIO,
  15. YarnConfiguration.DEFAULT_NM_VCORES_LIMITED_RATIO);
  16. LOG.info("Vcores limited ratio: " + vcoresLimitedRatio);
  17. }

然后利用monitor监控代码中已计算出的cpu百分比变量

  1. LOG.debug("Constructing ProcessTree for : PID = " + pId
  2. + " ContainerId = " + containerId);
  3. ResourceCalculatorProcessTree pTree = ptInfo.getProcessTree();
  4. pTree.updateProcessTree();    // update process-tree
  5. long currentVmemUsage = pTree.getVirtualMemorySize();
  6. long currentPmemUsage = pTree.getRssMemorySize();
  7. // if machine has 6 cores and 3 are used,
  8. // cpuUsagePercentPerCore should be 300% and
  9. // cpuUsageTotalCoresPercentage should be 50%
  10. float cpuUsagePercentPerCore = pTree.getCpuUsagePercent();
  11. float cpuUsageTotalCoresPercentage = cpuUsagePercentPerCore /
  12. resourceCalculatorPlugin.getNumProcessors();

最后进行大小判断即可

  1. ....
  2. } else if (isVcoresCheckEnabled()
  3. && cpuUsageTotalCoresPercentage > vcoresLimitedRatio) {
  4. msg =
  5. String.format(
  6. "Container [pid=%s,containerID=%s] is running beyond %s vcores limits."
  7. + " Current usage: %s. Killing container.\n", pId,
  8. containerId, vcoresLimitedRatio);
  9. isCpuVcoresOverLimit = true;
  10. containerExitStatus = ContainerExitStatus.KILLED_EXCEEDED_VCORES;
  11. }
  12. if (isMemoryOverLimit || isCpuVcoresOverLimit) {
  13. // Virtual or physical memory over limit. Fail the container and
  14. // remove
  15. // the corresponding process tree
  16. LOG.warn(msg);
  17. // warn if not a leader
  18. if (!pTree.checkPidPgrpidForMatch()) {
  19. LOG.error("Killed container process with PID " + pId
  20. + " but it is not a process group leader.");
  21. }
  22. // kill the container
  23. eventDispatcher.getEventHandler().handle(
  24. new ContainerKillEvent(containerId,
  25. containerExitStatus, msg));
  26. it.remove();
  27. LOG.info("Removed ProcessTree with root " + pId);
  28. } else {

对了,还要在这里添加1个新的ExitStatus退出码:

  1. /**
  2. * Container terminated because of exceeding allocated cpu vcores.
  3. */
  4. public static final int KILLED_EXCEEDED_VCORES = -108;

CPU监控代码的改动就是这么多.此功能的完整代码可以查看文章末尾的链接.在这里我要特别申请一下,此功能代码由于我在本地电脑上不支持ProcfsBasedProcessTree,导致单元测试没法跑通,所以我还没有完整测过,理论上是OK,大家可以拿去试试,可以给我一些反馈.希望能带给大家收获.

相关链接

Github patch链接:https://github.com/linyiqun/open-source-patch/tree/master/yarn/others/YARN-VcoresMonitor

从Container内存监控限制到CPU使用率限制方案的更多相关文章

  1. 如何监控redis的cpu使用率

    redis默认是单线程运行的,为了充分利用机器的cpu,正常情况下一台服务器上会装多个实例.如果通过top命令监控机器的cpu的话,监控值很笼统,不能精确到单redis实例的cpu使用率监控.而且ce ...

  2. zabbix 监控windows端cpu使用率百分比

    参考网站:http://www.fyluo.com/?post=108 zabbix自带的模版没有CPU使用率(百分比)这个监控项,那么我们可以通过添加计数器的方式实现CPU百分比的监控. 在zabb ...

  3. zabbix监控windows系统CPU使用率

    参考网站:https://blog.csdn.net/reblue520/article/details/76287113 Zabbix 自带的模块没有 CPU 使用率(百分比)这个监控项,我们可以通 ...

  4. zabbix监控Linux服务器CPU使用率大于40%的时候报警(实践版)

    zabbix自带的模板里面有监控项,所以监控项就不用创建了,直接创建触发器就可以了,触发器细节如下: 名称:CPU使用率大于40% 严重性:严重 表达式:{121.201.54.50:system.c ...

  5. python指定cpu使用率,与内存占用率

    python指定cpu使用率,与内存占用率 """ runing.py -c 2 -t 0.01 -m 1000 -c cpu核数,不加-c参数为最大核数 -t cpu运 ...

  6. 使用python函数持续监控电脑cpu使用率、内存、c盘使用率等

    方法一: # import time 导入time模块 # import psutil 导入psutil模块 # def func(): # while True: ------->持续监控得w ...

  7. [No0000112]ComputerInfo,C#获取计算机信息(cpu使用率,内存占用率,硬盘,网络信息)

    github地址:https://github.com/charygao/SmsComputerMonitor 软件用于实时监控当前系统资源等情况,并调用接口,当资源被超额占用时,发送警报到个人手机: ...

  8. linux下实现CPU使用率和内存使用率获取方法

    想获取一下目标机运行时linux系统的硬件占用情况,写了这几个小程序,以后直接用了. 方法就是读取proc下的文件来获取了. cpu使用率:    /proc/stat ,内存使用情况:     /p ...

  9. Linux常用命令及详细说明 — 结合工作(侧重性能监控,包括CPU、内存、IO、网络、磁盘等)

    (一)Linux监控的几个常用命令(对于服务器后端程序猿很重要,必须掌握): 命令 功能 命令 功能 iostat 统计CPU及网络.设备和分区IO的数据 vmstat 展示给定时间服务器的状态值(包 ...

随机推荐

  1. Xilinx FPGA 的PCIE 设计

    写在前面 近两年来和几个单位接触下来,发现PCIe还是一个比较常用的,有些难度的案例,主要是涉及面比较广,需要了解逻辑设计.高速总线.Linux和Windows的驱动设计等相关知识. 这篇文章主要针对 ...

  2. Android AOP之路三 Android上的注解

    一.简单介绍 啥是注解.不懂的能够先看我上一篇文章. 在android 里面 注解主要用来干这么几件事: 和编译器一起给你一些提示警告信息. 配合一些ide 能够更加方便快捷 安全有效的编写java代 ...

  3. zabbix rpm 安装 新增zabbix yum 源 并更新

    需要安装的包: # rpm -qa|grep zabbix zabbix-server-2.4.3-1.el6.x86_64 zabbix-web-mysql-2.4.3-1.el6.noarch z ...

  4. formidable处理多文件上传

    首先,在html页面中,表单上传文件的控件需要加上multiple选项,或者multiple="multiple". 然后,在nodejs程序中处理post数据的路路由中使用for ...

  5. CNN及其可解释性

    https://stats385.github.io/readings https://arxiv.org/pdf/1311.2901.pdf A Mathematical Theory of Dee ...

  6. javascript基础拾遗(七)

    1.对象的继承__proto__ var Language = { name: 'program', score: 8.0, popular: function () { return this.sc ...

  7. 每日英语:Tencent Fights for China's Online Shoppers

    In the war for the Chinese Internet, messaging giant Tencent is taking the battle to rival Alibaba's ...

  8. 【原理】Java的ThreadLocal实现原理浅读

    当前线程的值传递,ThreadLocal 通过ThreadLocal设值,在线程内可获取,即时获取值时在其它Class或其它Method. public class BasicUsage { priv ...

  9. 【转帖】(一)unity4.6Ugui中文教程文档-------概要

    原帖至上,移步请戳:(一)unity4.6Ugui中文教程文档-------概要 unity4.6中的一个重要的升级就是GUI ,也把它称为UGUI ,废话我不多说,大家可以百度了解一下. 虽然现在处 ...

  10. ExecutorService-10个要诀和技巧【转】

    http://ifeve.com/executorservice-10-tips-and-tricks/