本文共 4804 字,大约阅读时间需要 16 分钟。
最近在运维我们部门的Hadoop集群时,发现了很多Job OOM的现象。频繁的Full GC导致了严重的性能问题。Full GC会导致“stop the world”,一旦Full GC的时间超过几分钟,其他活动都会受到影响。因此,必须找到原因并解决它。本文将介绍我们是如何解决这个问题,并在此基础上进行了一些优化。
OOM的发生导致频繁的Full GC。首先需要确定具体原因。通常,第一个联想是上面的Job,Job的运行表现来自每个Task,而每个Task又表现为每个TaskAttempt。TaskAttempt运行在申请到的Container中。每个Container是一个独立的进程,可以通过jps命令在DataNode上看到大量名为“YarnChild”的进程,这些进程就是Container的启动进程。
初步估计是Container启动的JVM内存配置不足,导致内存不足。但问题并没有这么简单,这里还有些复杂。
内存配置不当:MapReduce任务的内存配置默认为1024MB(即1GB),但这可能不够,尤其是在处理大数据量或复杂任务时。错误的配置也可能导致JVM无法使用所需内存,从而引发OOM。
JVM配置错误:虽然mapreduce.map.memory.mb和mapreduce.reduce.memory.mb是MapReduce任务的内存配置,但实际上它们并不是Container启动的JVM的内存配置。JVM的内存上限由mapreduce.map.java.opts和mapreduce.reduce.java.opts决定。因此,理想的配置是java.opts的值必须大于等于memory.mb的值。如果配置不当,也会引发频繁的Full GC。
幸运的是,Hadoop已经对Container级别的内存进行了监控。对于所有启动的Container,Hadoop会额外启动一个名为container-monitor的线程,专门监控这些Container的物理内存和虚拟内存使用情况。相关的配置项包括:
yarn.nodemanager.pmem-check-enabledyarn.nodemanager.vmem-check-enabled默认情况下,这两项都启用。内存监控的作用是一旦Container使用的内存超过JVM的最大内存上限,Container会被杀死。
通过分析ContainersMonitorImpl.java可以看出,监控线程会在每隔一定时间(由YarnConfiguration.NM_CONTAINER_MON_INTERVAL_MS控制)遍历所有被监控的Container,检查它们的内存使用情况。如果内存使用超过限制,Container会被杀死。
在解决上述问题后,我开始思考为什么不对CPU使用率也进行监控。同样重要的指标,CPU使用率为什么不一起监控呢?
我总结了以下几点原因:
综上所述,CPU监控并未被加入到监控代码中(个人分析)。但Hadoop自身并未加CPU监控并不代表我们不能添加这样的监控功能。例如,对于那些内存并不多,但会耗尽大量CPU资源的程序(如开很多线程但每个线程都做简单操作的程序),添加CPU使用率监控会有帮助。
为了实现对CPU使用率的监控,我们需要:
/** Specifies whether cpu vcores check is enabled. */public static final String NM_VCORES_CHECK_ENABLED = NM_PREFIX + "vcores-check-enabled";public static final boolean DEFAULT_NM_VCORES_CHECK_ENABLED = false;
默认情况下,CPU使用率监控是关闭的。同时,还需要定义一个使用阈值(0~1之间),一旦某个Container的CPU使用率超过这个阈值,Container就会被杀死。
/** Limit ratio of Virtual CPU Cores which can be allocated for containers. */public static final String NM_VCORES_LIMITED_RATIO = NM_PREFIX + "resource.cpu-vcores.limited.ratio";public static final float DEFAULT_NM_VCORES_LIMITED_RATIO = 0.8f;
默认值为0.8,可以根据需要进行调整。
serviceInit方法中进行配置初始化:private boolean pmemCheckEnabled;private boolean vmemCheckEnabled;private boolean vcoresCheckEnabled;private float vcoresLimitedRatio;...pmemCheckEnabled = conf.getBoolean(YarnConfiguration.NM_PMEM_CHECK_ENABLED, YarnConfiguration.DEFAULT_NM_PMEM_CHECK_ENABLED);vmemCheckEnabled = conf.getBoolean(YarnConfiguration.NM_VMEM_CHECK_ENABLED, YarnConfiguration.DEFAULT_NM_VMEM_CHECK_ENABLED);vcoresCheckEnabled = conf.getBoolean(YarnConfiguration.NM_VCORES_CHECK_ENABLED, YarnConfiguration.DEFAULT_NM_VCORES_CHECK_ENABLED);LOG.info("Physical memory check enabled: " + pmemCheckEnabled);LOG.info("Virtual memory check enabled: " + vmemCheckEnabled);LOG.info("Cpu vcores check enabled: " + vcoresCheckEnabled);if (vcoresCheckEnabled) { vcoresLimitedRatio = conf.getFloat(YarnConfiguration.NM_VCORES_LIMITED_RATIO, YarnConfiguration.DEFAULT_NM_VCORES_LIMITED_RATIO); LOG.info("Vcores limited ratio: " + vcoresLimitedRatio);} LOG.debug("Constructing ProcessTree for : PID = " + pId + " ContainerId = " + containerId);ResourceCalculatorProcessTree pTree = ptInfo.getProcessTree();pTree.updateProcessTree();long currentVmemUsage = pTree.getVirtualMemorySize();long currentPmemUsage = pTree.getRssMemorySize();// if machine has 6 cores and 3 are used, // cpuUsagePercentPerCore should be 300% and // cpuUsageTotalCoresPercentage should be 50%float cpuUsagePercentPerCore = pTree.getCpuUsagePercent();float cpuUsageTotalCoresPercentage = cpuUsagePercentPerCore / resourceCalculatorPlugin.getNumProcessors(); } else if (isVcoresCheckEnabled() && cpuUsageTotalCoresPercentage > vcoresLimitedRatio) { msg = String.format( "Container [pid=%s,containerID=%s] is running beyond %s vcores limits." + " Current usage: %s. Killing container.\n", pId, containerId, vcoresLimitedRatio); isCpuVcoresOverLimit = true; containerExitStatus = ContainerExitStatus.KILLED_EXCEEDED_VCORES;}if (isMemoryOverLimit || isCpuVcoresOverLimit) { // Virtual or physical memory over limit. Fail the container and // remove the corresponding process tree LOG.warn(msg); // warn if not a leader if (!pTree.checkPidPgrpidForMatch()) { LOG.error("Killed container process with PID " + pId + " but it is not a process group leader."); } // kill the container eventDispatcher.getEventHandler().handle( new ContainerKillEvent(containerId, containerExitStatus, msg)); it.remove(); LOG.info("Removed ProcessTree with root " + pId);} else { 转载地址:http://gvng.baihongyu.com/