我做了近十年的数据平台发现很多刚接触大数据的同学容易把Hadoop和MapReduce划等号甚至以为YARN只是MapReduce的一个附属组件。等到真正上手维护一个多团队共用、跑着Spark和Flink任务的集群时才意识到YARN的资源管理才是整个集群能不能稳定运转的核心。今天这篇就围绕YARN的资源管理展开把我平时排查问题、做容量规划、调调度器参数这些实际经验一并讲透。1. 先弄明白YARN到底在管什么从两层调度说起要理解YARN先要摆脱一个惯性思维——觉得资源管理就是“哪台机器空闲就把任务扔过去”。实际上在一个真正的分布式集群里资源管理要解决的是三个问题谁可以占用多少内存和CPU、如何让多个任务公平地共享集群、任务本身又该怎么被拆分和调度。YARN用一套“全局调度加应用内调度”的两层结构回答了这些问题。第一层是全球调度。ResourceManager后续简称RM是全局的资源决策者它接收各个NodeManager后续简称NM定时上报的节点资源总量和实时占用情况然后根据用户提交的作业请求把集群资源划分成一个又一个的Container。打个比方RM像个物业经理NM就是每栋楼的楼管楼管知道自己这栋楼有多少空房、住了多少人物业经理根据这些信息决定哪家公司能分到几套房。第二层是应用内调度。每个分布式计算应用在被RM批准启动后会生成一个ApplicationMaster后续简称AM。AM是应用的“包工头”它拿着RM批给的资源清单再去和NM协商启动具体的执行容器。为什么要多绕这一层呢因为不同的计算框架对任务拆分逻辑完全不一样。MapReduce一个Map任务要跑多久、Shuffle要多少内存Spark的Executor怎么分布、Stage内部怎么并行这些私事RM根本不需要知道。把应用内部的调度权下放给AMRM就只需要管“资源多少钱能租”不用管“租来的房怎么装修”。这个设计带来的直接好处是YARN不需要为每一种计算框架定制资源逻辑。MR、Spark、Flink、Tez只要是实现了YARN的接口规范就能在同一个集群里共存。而框架侧只需要关心如何用好AM拿到的资源描述即可。架构上这一层隔离后来被很多人认为比简单的“统一资源池”想得更深因为它让资源的分配语义变得非常干净。如果你要跟别人解释YARN的原理可以从这四个角色切入。Container是一个通用的资源封装单元你可以把它简单理解成一台虚拟小机器有固定的内存上限和CPU配额。AM在这个Container里运行向RM申请后续执行用的Container。任务真正的计算、Shuffle、聚合都在这些后续Container里完成。RM通过心跳管理NM的生命周期和Container的清理NM则负责真正启动和销毁Container进程。2. 作业提交后到底发生了什么一次完整的心跳之旅理解组件的关系后再把一个作业从提交到结束的完整链路走一遍很多参数配置就变得容易理解了。第一步是客户端向RM提交Application。这里很多人会忽略一个关键细节——提交的机器上要有可用的配置文件和认证信息。所谓“可用”不光是host配置对上了还包括YARN服务所在的端口能够连通。生产环境我见过太多“提交作业一直卡在ACCEPTED”的情况最后排查发现是客户端机器上没有正确配置core-site.xml导致RM根本拿不到合法的访问身份。第二步RM收到Application后会根据当前队列的资源使用情况和调度策略决定是否接受。接受以后RM会找一个资源相对空闲的NM指示它启动ApplicationMaster对应的Container。这一步在日志里对应的是“Allocating Application Master”。肉眼看的事件序列是RM收到提交请求进行队列准入检查然后调用调度器的allocator分配AM Container。如果一直停在ACCEPTED不往下走八成就是队列资源被占满或者是提交用户没有队列的使用权限。第三步AM启动后向RM注册然后通过RM提供的API领取自己可用的资源列表。这一步之后才是真正意义上的“动态调度循环”。AM会按自己的算法计算这次作业需要多少个执行容器分批向RM发起请求RM每次通过心跳响应给出分配的Container列表。这里有个常见误解以为每个任务运行前都要经过RM做一次全局判定。实际上AM拿到首批“资源令牌”之后后续的分配大部分是在AM本地完成的RM只是在心跳响应里告诉NM“你可以启动这几个Container”。第四步NM收到启动Container的指令后会先把Container的运行环境准备好——下载作业依赖的本地资源、建立日志目录、设置环境变量最后通过一个子进程启动真正的任务代码。任务完成或失败后AM负责汇总状态再向RM报finished。RM清理Application的元数据整个流程结束。我把这套流程比作“订单-施工-验收”。客户端下单RM做物业审批AM是施工队长NM是楼管兼包工头。任何一环卡住整个作业就会以不同表现卡死这句话对我们的排障特别有用——看到作业一直卡在“WAITING”和一直卡在“RUNNING”时排查方向完全不一样。3. 资源模型是避坑的根源内存、vcore和Container的数学关系YARN的资源模型里有两个核心指标——内存和虚拟核vcore。内存是绝对硬约束vcore只是调度参考。这个“参考”二字很重要因为vcore不像是物理CPU那样直接绑核它只是一个权重与配额的抽象。先看内存。每个节点能够被YARN使用的最大内存由yarn.nodemanager.resource.memory-mb决定。注意这个值不是用来配置节点物理内存的而是告诉YARN“你这栋楼有多少房可以出租”。物理机上还跑着操作系统、系统服务、DataNode进程这些都要留出空间。所以合理做法是给NodeManager预留物理机的80%到90%作为可调度资源剩下10%到20%留给系统。假设一台物理机是256GB内存那么yarn.nodemanager.resource.memory-mb通常设在200GB左右。在这个总池子里YARN把内存切成固定大小的“最小单位”由yarn.scheduler.minimum-allocation-mb控制默认是1024MB。作业申请的每个Container内存必须是这个值的整数倍。对应的还有yarn.scheduler.maximum-allocation-mb即单个Container能申请到的内存上限默认是8192MB。这个上限在生产环境经常要调大否则跑大查询的Spark作业申请不到所需内存报了MemoryError还一头雾水。vcore同理yarn.nodemanager.resource.cpu-vcores设置节点总虚拟核数yarn.scheduler.minimum-allocation-vcores和yarn.scheduler.maximum-allocation-vcores控制每个Container的虚拟核范围。实际项目中我会建议把“核心数”和“内存兆数”一一对应着看。比如一个Container是4GB内存加2个vcore那么200GB的总内存池最多能分配大约50个这类Container。但“最多”不等于“应该”。难点在于物理机的超卖怎么控。某个团队总是申请不到资源看指标内存还剩不少这通常是内存碎片问题——各个Container占用内存参差不齐导致可分配的大块连续额度不够。举个例子一个队列里同时跑了一个8GB的Spark任务和两个5GB的MapReduce任务再来申请一个10GB的大Container时就可能拿不到尽管总剩余内存大于10GB。解决方案可以是增大yarn.scheduler.maximum-allocation-mb的余量也可以调整队列内的资源上限但要小心超卖过多导致节点内存被打满、NodeManager被系统强杀。CPU上也有类似的坑。物理核只有16个但vcore随便配了32个高峰期CPU Load飙到几十、任务之间互相抢资源最后所有任务的执行时间一起劣化。YARN在资源隔离上只做了cgroup的轻量管控远没有内存那么强制。所以我的经验是vcore总数控制在物理核数的1.5到2倍左右宁可让任务排队也不要把机器拖垮。4. 调度器的现实选择为什么我很少用FIFOYARN内置三种调度器FIFO、Capacity和Fair。教科书上会分别讲它们的原理真实生产环境里选型更像是一门“权衡的艺术”。FIFO就是先进先出先提交的作业独占全部资源。这在单用户、单一业务线的实验环境还能跑一旦多人共用一个跑几小时的批量任务就可能让后面的即席查询饿死。所以稍微正式一点的集群都会选Capacity或Fair。Capacity调度器的核心逻辑是“队列预设比例切分”。管理员可以在配置里定义多个队列每个队列设定一个保证资源占比。比如dev队列40%data队列35%default队列25%。资源不够时队列之间不能抢占对方的保证份额就算data队列空了多余的资源可以临时借出去但一旦那个队列有任务提交借出去的资源要能还回来。这个机制适合部门职责分明、需求波动又很明显的场景比如生产团队和数据研发团队分开。Fair调度器更强调“公平性”它会在所有运行的作业之间尽量平均分配资源。适合即席查询多、作业大小差异大、任意一个作业都无权独占资源的场景。Fair在配置上更复杂要设置各个队列的权重、最小资源、最大资源。一个作业如果需求很大在Fair下很可能会被拆分到不同的Container里任务的本地性会被牺牲一些。来一个实际对比。某公司的集群一开始用了Capacity分了三层队列大数据开发、分析、临时跑批各占一块。后来发现分析团队经常会有一些临时大查询而大数据开发的常规同步任务又几乎占满队列两边经常“互相打架”。最后我把分析团队迁到了Fair调度器的独立队列设了权重让临时任务在队列空时最多使用资源总量的60%同时限制了单用户的并发度效果立刻好了很多。结论是队列规划不能只看技术参数一定要和业务使用模式对着设计模式变队列也要跟着调。调度器配置文件里还有几个容易埋坑的参数例如每个用户底层可同时运行的任务数yarn.scheduler.capacity.maximum-applications以及队列的maximum-am-resource-percent。这个AM资源占比如果不调整默认值是0.1意思是这个队列最多用10%的资源来跑ApplicationMaster。遇到大量小作业并发提交时AM本身就可能把这个配额占满然后所有的新作业只能排队等AM资源表现就是集群看着资源一大把但任务就是起不来。5. 实战调优那些我调过才知道的参数细节在真正动手调优前一定要先想清楚集群里跑的作业是什么型。跑MR批处理多一点还是Spark实时流多一点还是混布混布的场景下资源参数往往要留更多缓冲。我给出一个相对稳妥的起步参数。单节点256GB内存、32物理核YARN可调度资源我通常设为yarn.nodemanager.resource.memory-mb204800 yarn.nodemanager.resource.cpu-vcores48 yarn.scheduler.minimum-allocation-mb2048 yarn.scheduler.maximum-allocation-mb24576 yarn.scheduler.minimum-allocation-vcores1 yarn.scheduler.maximum-allocation-vcores12 yarn.nodemanager.pmem-check-enabledtrue yarn.nodemanager.vmem-check-enabledfalse为什么要关掉虚拟内存检查这个参数很多人都吃过亏。YARN默认检查Container的虚拟内存占用而JVM使用direct buffer、JNI等特性时虚拟内存会远大于物理内存经常出现日志里明确显示物理内存还在安全范围内却报出“Virtual memory exceeds”的错误导致任务被误杀。关掉以后真正依赖物理内存的约束跟上了反而少了很多莫名其妙的失败。内存参数定下来以后还要注意Container的大小和任务类型的匹配。比如Spark的一个Executor通常在4GB到16GB之间对应到JVM的spark.executor.memory和spark.executor.memoryOverhead前者是堆内内存后者是堆外内存。YARN收到的Container内存会包含两者所以如果spark.executor.memoryOverhead设得偏小Spark任务很容易出现堆外内存溢出而YARN日志里看到的Container内存又没打满排查起来非常头大。这里我分享一个真实的调优记录。有个离线报表任务数据量突然涨了五倍任务一直卡在Reduce阶段反复失败。看日志里面Container的物理内存使用一直在增长最后被NodeManager强制杀掉。那时候我先调大了yarn.scheduler.maximum-allocation-mb到32GB同时把Spark的Executor内存从8GB提到16GB堆外从1GB加到2.5GB任务就稳定跑完了。整个过程看起来是“加内存”关键是把YARN的容器上限和应用自己的内存参数一起调了只改一边几乎没用。6. 队列资源配置的坑小心AM比例和层级继承队列配置在生产里是最容易被忽视但又最容易出问题的地方。Capacity调度器支持队列的层级结构比如规划为root.风险队列.batch和root.风险队列.realtime。层级结构能做的事很多但同时也带来了配置上的继承陷阱。一个常见错误是只给叶子队列配置了资源百分比而父队列没有明确底层AM资源比例导致兄弟队列各自约束混乱。实际上Capacity调度器里父队列的maximum-am-resource-percent默认值是0.1会作为子队列的默认继承值。若你在叶子队列里没显式设置AM资源就是子队列总资源的10%。Job数量一旦多了AM就会成为瓶颈。另一个坑是yarn.scheduler.capacity.root.队列名.maximum-capacity。如果只设置了capacity40而没有设置maximum-capacity那么在集群资源空闲时这个队列理论上可以使用100%的资源。很多管理员没意识到这一点平时一切正常一旦别的队列突然有大任务进来资源抢占和相互挤压就会变成连锁反应。所以我的做法是给每个队列同时设置capacity和maximum-capacity。比如开发队列保证40%最大不超过70%生产队列保证50%最大不超过90%剩下的10%留给系统缓冲区。这些值看起来拍脑袋其实背后要考虑的是哪个队列对延迟最敏感哪个队列能承受排队这些敏感度排序直接决定了最大值的分配。Fair调度器配置的情况会更复杂一些因为它的资源分配不光是“百分比切分”还有“权重”的概念。权重越大的队列在竞争时获得的资源越多任务数相同的情况下1:2的权重基本能满足“一个要吞吐一个要实时”的典型需求。但权重的调优要持续观察不是配好了就一劳永逸业务规模一变权重就必须跟着变。7. 两类常见故障排查从“卡住”到“被Kill”的思考路径故障排查是最能体现经验的地方我就把平时在集群维护中遇到最多的两类故障拿出来拆解一遍。第一类是“集群有资源任务却一直等待”。检查的时候先排除队列资源满的情况看RM界面里的活跃队列状态确认待提交作业属于哪个队列、已用资源是多少。如果队列资源看着很充足下一步要看AM资源比例和单用户并发限制。现在YARN的很多问题都是集中在“应用数”而不是资源量上一个用户短时间内提交了几百个作业触发了yarn.scheduler.capacity.maximum-applications限制后续作业就会一直排着。排查命令上可以多用yarn application -list和yarn top来看每个应用的实际分配情况。第二类是“Container被NodeManager杀掉”。这类问题几乎都和内存超限有关。排查时先看日志里的错误码如果报的是物理内存超限就要从任务本身的堆内存、堆外内存参数入手结合节点的实际物理内存余量判断是调大Container上限还是减少同节点并发。如果是虚拟内存超限优先考虑关掉vmem检查而不是盲目加资源。再补充一个容易被忽略的部分框架会向NodeManager申请GPU资源。如果集群中有GPU并要求在YARN里调度GPU参数没配对也会出现Container启动后立即失败报错指向显存不足但实际物理内存还很宽裕。其实大部分故障的第一现场都不在YARN日志里而要在任务自己的stdout、stderr和GC日志里找。Container被反复拉起和Kill不能只盯着YARN反复调参先看任务代码有没有内存泄漏、数据倾斜、单Reducer过大往往根因很简单。资源只是最后兜底的那一层。8. 监控和容量规划别等集群被打爆才想起来补聊完参数和故障最后一个绕不开的话题是监控和容量规划。YARN自身可以在Web UI上看到每个队列的实时使用情况但生产环境通常会把指标接入集中监控平台。值得盯的关键指标包括每个队列的资源使用率、Container分配的成功/失败数、节点黑名单数量、AM数占应用总数的比例、以及各个队列的等待时间。容量规划这件事我把它拆成两个节奏。短节奏是每季度复盘一次队列的资源水位看哪些队列长期在90%以上跑哪些长期在20%以下空转。长期空转的队列资源应该往繁忙队列倾斜而不是任由峰值互相踩踏。长节奏是容量模型的计算根据业务增长的速率和任务增长数量得出未来一年的资源需求再倒推硬件采购计划。但这里有个现实问题就是硬件采购往往有滞后性所以建议给每个队列预留10%到15%的buffer别卡着理论上下限做配置。监控数据还能反过来帮助我们验证作业的资源申请是否合理。比如某个Spark任务申请了32GB的Container但监控上显示实际运行峰值只有不到12GB这就是明显的“资源浪费型”作业不光占用队列额度还会挤掉其他任务。遇到这种作业可以直接调小Executor内存GC表现和吞吐量反而可能更好。至于那些一人占一个超大Container跑跑停停的任务我个人建议直接从队列层面限制maximum-allocation-mb。防止单任务把整个集群的资源申请走这种限流手段远比复核每个任务的代码更直接。从YARN的架构到资源模型再到调度器配置和故障排查这一套玩下来基本就能把一个集群的资源管理从“能跑”推进到“跑得稳”。很多问题的本质不是参数不够多而是没有把资源粒度、队列模型和业务模式三者对齐。分享的这些经验都来自实际集群的反复调试配置思路和参数方向可以参考但具体数值还是要根据自己集群的物理规格、任务特征去实测调整。希望这篇能帮你在动手配YARN的时候少走几个弯路。
阅读完成 · 觉得有帮助?