YARN同时支持内存和CPU两种资源的调度,默认只支持内存,如果想进一步调度CPU,需要自己进行一些配置。
什么是虚拟cpu
目前的CPU被划分成虚拟CPU(CPU virtual Core),这里的虚拟CPU是YARN自己引入的概念,初衷是,考虑到不同节点的CPU性能可能不同,每个CPU具有的计算能力也是不一样的,比如某个物理CPU的计算能力可能是另外一个物理CPU的2倍,这时候,你可以通过为第一个物理CPU多配置几个虚拟CPU弥补这种差异。用户提交作业时,可以指定每个任务需要的虚拟CPU个数。
内存资源的多少会会决定任务的生死,如果内存不够,任务可能会运行失败;相比之下,CPU资源则不同,它只会决定任务运行的快慢,不会对生死产生影响。
由于CPU资源的独特性,目前这种CPU分配方式仍然是粗粒度的。很多任务可能是IO密集型的,消耗的CPU资源非常少,如果此时为它分配一个CPU,则是一种浪费,完全可以和其他几个任务共用这个CPU.也就是说,我们需要支持更细粒度的CPU表达方式。借鉴亚马逊EC2中CPU资源的划分方式,即提出了CPU最小单位为EC2 Compute Unit(ECU),一个ECU代表相当于1.0-1.2 GHz 2007 Opteron or 2007 Xeon处理器的处理能力。YARN提出了CPU最小单位YARN Compute Unit(YCU),目前这个数是一个整数,默认是720,由参数yarn.nodemanager.resource.cpu-ycus-per-core设置,表示一个CPU core具备的计算能力(该feature在目前已知版本中不存在,https://issues.apache.org/jira/browse/YARN-1089 & https://issues.apache.org/jira/browse/YARN-1024),这样,用户提交作业时,直接指定需要的YCU即可,比如指定值为360,表示用1/2个CPU core,实际表现为,只使用一个CPU core的1/2计算时间。注意,在操作系统层,CPU资源是按照时间片分配的,你可以说,一个进程使用1/3的CPU时间片,或者1/5的时间片。
关于虚拟内存,官方提供了以下几个参数
(1) yarn.nodemanager.vmem-pmem-ratio = 2.1
Ratio between virtual memory to physical memory when setting memory limits for containers. Container allocations are expressed in terms of physical memory, and virtual memory usage is allowed to exceed this allocation by this ratio.
每单位的物理内存总量对应的虚拟内存量,默认是2.1,表示每使用1MB的物理内存,最多可以使用2.1MB的虚拟内存总量。
(2)yarn.nodemanager.resource.cpu-vcores=64
该节点上YARN可使用的虚拟CPU个数,默认是8.注意,目前推荐将该值设值为与物理CPU核数数目相同。如果你的节点CPU核数不够8个,则需要调减小这个值,而YARN不会智能的探测节点的物理CPU总数。
(3) yarn.scheduler.minimum-allocation-vcores=1
单个任务可申请的最小虚拟CPU个数,默认是1,如果一个任务申请的CPU个数少于该数,则该对应的值改为这个数。
(4)yarn.scheduler.maximum-allocation-vcores=64
单个任务可申请的最多虚拟CPU个数,默认是32。
(5) yarn.nodemanager.vmem-check-enabled(undefined,default=true)
Whether virtual memory limits will be enforced for containers. 是否启动一个线程检查每个任务正使用的虚拟内存量,如果任务超出分配值,则直接将其杀掉,默认是true。
yarn.nodemanager.vmem-check-enabled 参数十分简单,就是对vmemLimit监控的开关a. yarn.nodemanager.vmem-pmem-ratio 这个参数可能是最让人困惑的了,网上搜出的资料大都出自官方文档的解释,不够清晰明彻。大数据培训
下面结合源代码和大家解释一下这个参数到底在控制什么。
首先,NodeManager 接收到 AppMaster 传递过来的 Container 后,会用 Container 的物理内存大小 (pmem) * yarn.nodemanager.vmem-pmem-ratio 得到 Container 的虚拟内存大小的限制,即为 vmemLimit:
long pmemBytes = container.getResource().getMemory() * 1024 * 1024L;
float pmemRatio = container.daemonConf.getFloat(YarnConfiguration.NM_VMEM_PMEM_RATIO, YarnConfiguration.DEFAULT_NM_VMEM_PMEM_RATIO);
long vmemBytes = (long) (pmemRatio * pmemBytes);
然后,NodeManager 在 monitor 线程中监控 Container 的 pmem(物理内存)和 vmem(虚拟内存)的使用情况。如果当前 vmem 大于 vmemLimit 的限制,或者 olderThanAge(与 JVM 内存分代相关)的内存大于限制,则 kill 掉进程:
if (currentMemUsage > (2 * vmemLimit)) {
isOverLimit = true;
} else if (curMemUsageOfAgedProcesses > vmemLimit) {
isOverLimit = true;
}
kill 进程的代码如下:
if (isMemoryOverLimit) {
// kill the container
eventDispatcher.getEventHandler().handle(new ContainerKillEvent(containerId, msg));
}
这里的 vmem 究竟是不是 OS 层面的虚拟内存概念呢?我们来看一下源码是怎么做的。
ContainerMontor 就是上述所说的 NodeManager 中监控每个 Container 内存使用情况的 monitor,它是一个独立线程。ContainerMonitor 获得单个 Container 内存(包括物理内存和虚拟内存)使用情况的逻辑如下:
Monitor 每隔 3 秒钟就更新一次每个 Container 的使用情况;更新的方式是:
查看 /proc/pid/stat 目录下的所有文件,从中获得每个进程的所有信息;
根据当前 Container 的 pid 找出其所有的子进程,并返回这个 Container 为根节点,子进程为叶节点的进程树;在 Linux 系统下,这个进程树保存在 ProcfsBasedProcessTree 类对象中;
然后从 ProcfsBasedProcessTree 类对象中获得当前进程 (Container) 总虚拟内存量和物理内存量。
由此大家应该知道了,内存量是通过 /proc/pid/stat 文件获得的,且获得的是该进程及其所有子进程的内存量。所以,这里的 vmem 就是 OS 层面的虚拟内存概念。
总结
yarn默认yarn.nodemanager.vmem-check-enabled 开启检测一个线程检查每个任务正使用的虚拟内存量VmemLimit,如果任务超出分配值,则直接将其杀掉.
VmemLimit=(pmem) * yarn.nodemanager.vmem-pmem-ratio ;
在map阶段,VmemLimit= mapreduce.map.memory.mb(1G) * yarn.nodemanager.vmem-pmem-ratio = 2.1G;
在reduce阶段, VmemLimit = mapreduce.reduce.memory.mb(1G) * yarn.nodemanager.vmem-pmem-ratio = 2.1G;
基于当前集群job提交情况,虚拟内存不够用的job属于少数,相比关闭VmemLimit的监控,推荐调整pmem-ratio,如有必要可再job session中加大Memory进行优化。