02
资源优化实践
接下来介绍一下小米内部在使用Hadoop Yarn过程中针对资源优化的一些实践。
1. 弹性调度
小米内部离线集群的使用情况如上图1所示,凌晨因大量例行任务资源占用比较多,白天任务数量相较于凌晨少很多,存在明显的资源低谷,导致白天存在大量的资源浪费。而期望的资源使用方式是如上图2所示,只保障低峰资源的使用,而对于高峰资源的需求,尝试使用公有云实例或者云主机去满足。另外实现这个同时也会带来一些其他问题,作业如何选择以及作业稳定性如何保障。
在作业筛选时,通过时间和优先级两个维度对作业进行筛选。时间维度方面,目前这批弹性节点有可预测的下线时间,所以在选取作业时会考虑作业的运行时长,在节点可预测下线的时间之前能满足作业运行结束的要求;还有一方面就是作业运行的开始时间或者作业的运行时间段不能和节点下线的时间吻合。优先级方面,目前不会针对高优的作业使用弹性调度,而是针对默认或者低优先级的作业使用弹性调度。
弹性调度方案设计初的目标就是对业务无感知的,目前内部有一个 infra-client 的组件,可以控制用户的依赖包与配置,通过该组件可以灰度用户配置,这样可以做到在用户无感知的情况下,达到弹性资源的使用。
为了保障使用弹性资源的作业稳定性,方案有如下一些细节优化:
①对于作业的大脑ApplicationMaster的调度,尽量避免去调度到弹性节点上,降低弹性资源缩容时对作业稳定性的影响。
②目前社区只支持单Label配置,不支持或表达式的配置。但是因为凌晨的弹性资源可能因为节点异常或者其他因素导致弹性资源的资源量小于作业的资源需求,导致作业 pending,在这种情况下需要可以使用集群的固定实例资源,出于这个需求内部开发支持配置Label或表达式支持同时使用固定实例资源与弹性资源。
③针对Spark作业稳定性保障,如果调度在弹性节点上,当节点下线后,shuffle 数据也会丢失,导致重算,增加了作业异常的风险,内部通过引入Remote Shuffle Service去解决该问题。
④因为当前弹性节点类型是有可预期下线时间点,所以在节点下线之前的一段时间内会对该节点进行Graceful Decommission,这样在节点下线之前的一段时间,会保证Yarn不会继续向这批节点调度作业,同时也要保证已有的作业能在预期的时间内运行完成,尽可能保障作业的稳定性。
关于弹性调度实现及架构的更多细节可以参考《小米Hadoop YARN弹性调度的探索与落地》这个分享。
最终在上线弹性调度之后的成果和预期的资源使用曲线基本吻合,而且在成本方面也有明显降低。
2. 单机节点资源超卖
在集群规模较大时,会遇到明显的资源浪费问题,具体现象是业务申请的container的资源使用量与实际的资源使用量不符合,但是从集群逻辑资源来看资源使用率非常高,从单机物理资源使用情况来看利用率较低,存在浪费,导致目前集群中大量的资源空闲。
基于这种情况,目前小米内部采用的是单机节点资源静态超卖的模式,单机节点资源进行上报时上报比主机资源可用内存更大的值给ResourceManager,即上报real + overuse的资源给ResourceManager,但是这种模式会导致单机出现稳定性问题。比如某个节点成为调度热节点时,节点的物理资源使用率可能接近单机的上限导致触发系统的omkiller,极端情况下会导致整个节点hang住,严重影响作业的生产保障。目前小米内部使用Cgroup进行节点稳定性管控,结合使用NodeManager内部的elastic + enforce两种机制去满足单机节点稳定性的目标。
接下来介绍一下如何使用Cgroup超卖NodeManager资源超卖情况下节点的稳定性。
资源超卖下 NM 基于Cgroup保障单机稳定性主要依赖于Elastic Memory Control的实现。
①单机内存利用率层面,在 Cgroup配置文件中针对hadoop-yarn的配置memory.limit_in_bytes=node.real,同时配置memory.oom_control的oom_kill_disable字段为1。这样,整个NM 的进程树的内存可使用总量受限于memory.limit_in_bytes的配置,一旦整个进程树的内存资源达到限制,内核会冻结整个进程树的执行。
另外,NM内部会监听内核的事件,当发生内存超用的发生时,NM监听的内核事件并触发管控策略,基于相关策略选择container进行kill,将单机利用率降低到安全阈值下,内核会使 NM 启动的 container 继续运行。在小米内部针对于kill的策略进行了扩展,在进行kill时优先选择优先级比较低的container进行kill,针对于同等优先级情况下选择最近启动的container进行kill。
②单个container级别设置memory.limit_in_bytes=reqest,管控单个container的内存限制。通过以上两种方式的限制就能满足单个节点资源超卖的这种机制。
ElasticMemoryController 机制在内部还有其他场景,比如针对流式作业小米内部有专有集群,之前出现过Flink Container Lost过多影响业务的情况。经排查是Flink任务由于堆外内存使用过多造成大量 Container 被NodeManager kill掉。对于这种情况,小米内部在流式集群落地了Elastic Memory Control机制,保障单机不触发OOM-KILLER的情况下,允许container进行资源超用,只有当达到了单机内存上限或者触发稳定性问题的时候,才会进行Container的kill。这样可以尽可能保障业务稳定的情况下让 flink 同学定位以及优化Flink堆外内存溢出或者其他可能的内存使用问题。
在流式集群上线Elastic Memory Control机制后,Flink Container Lost从日级别的1000+降低到了500+,下降了接近50%,对整个Flink作业的稳定性提升非常明显。
3. 基线任务执行优化
基线任务:业务可以通过小米内部的数据工厂配置基线,通过配置作业的预定产出时间,工厂可以推导出其上游各个依赖作业需要启动的时间。此时存在一个需求就是队列内基线优先级高的任务比基线优先级低的任务更快拿到资源,目前 Yarn 队列内优先级的支持通过 fifo + priority 的策略进行排序,但是内部需求想在同等优先级下实现公平分配,不同优先级下实现高优先级优先分配。
针对需求内部实现了 fair + priority 的比较器,去满足整个基线对于高优先级作业的执行的资源需求的优化。
小总结:
内部资源保障,主要分几个方面:
①首先尽可能去寻求公有云的弹性资源,降本增效。
②尽可能提升单机物理资源利用率。
③资源量一定的情况下高优先级作业可以更快获取资源。