背景
目前ebay运行着多个hadoop集群,其中最大的集群有着10000+的NodeManager,总共的用于计算的内存数量高达4PB。每年都会有新的业务上线到Hadoop集群,同时现存的业务的数据量也在持续的增长。基于这个情况,我们每年需要购置更多的服务器来满足日益增长的计算需求。按照20%的年化增长速率来计算的话,每年需要投资在计算资源上大约数千万美元的费用。这是一笔庞大的开支。
降本增效一直是基础架构团队追求的目标之一。如果我们能够从软件层面,让现有的硬件发挥更大的功效,更高效的利用,那就可以极大的减少需要投资到硬件上的成本,从而帮助公司把这一部分成本转化成利润。
为了实现降本增效的目标,过去几年我们团队采取了多种方式。其中在提升计算资源的计算效率的角度,我们使用过静态超卖为集群提升了10%的计算资源。在此基础之上,我们团队后续又为了进一步提升超卖的效率,开创性的开发了动态超卖功能,进一步提升了集群30%的计算资源。为公司节省了数千万美元的硬件成本。
下面从超卖的可行性,静态超卖和动态超卖的细节,以及上线的效果几个章节进行我们的超卖之旅。
可行性
通过分析集群的计算资源的使用效率,我们发现了当前我们可以进行超卖,并且进一步分析了可以进行超卖的根源。
在ebay我们使用YARN作为hadoop资源调度器进行资源调度,用户申请资源的时候只需要指定需要使用的内存数量即可。用户提交hadoop job时指定每个task需要使用的内存数量进行资源的申请,然后YARN会保留出对应的内存数量分配给用户的hadoop job。这个过程中YARN并不会检测用户的实际内存使用情况,这就会发生一个问题,那就是用户名义上申请的内存数量是大于用户实际需要使用的内存数量。这就会造成资源被占用了,但是却被空置浪费。
比如下图中虽然从调度器的角度看集群的计算资源已经完全分配出去了,但是当我们观察每个物理机的时候,会发现大部分机器都有很多的剩余可用内存被闲置。
资源浪费的来源
(点击可查看大图)
1. 因为运行在YARN上的hadoop作业大部分都是使用JAVA语言编写的,JAVA语言编写的程序因为GC的原因,往往需要申请大于其真实需要使用的内存来防止OOM出现。
(点击可查看大图)
2. 因为hadoop job是分布式运行的,并且每个task处理的数据,数据量都是有差别的,并且是在变化的,所以用户无法在运行之前精确的知道每个task究竟需要多少内存才能正常运行,所以用户在为每个task申请内存的时候就需要根据木桶理论预估最大的task需要的内存量,然后为每个task都申请同样大小的内存数量,对于小task来说,这就造成了冗余的资源申请。
(点击可查看大图)
3. NodeManager上无时无刻都在运行着来自不同用户的不同作业。而这些作业都有申请多余的内存。这些多余的内存累加在一起,占据了相当比例的NodeManager的内存。导致物理机的内存使用效率低下。
从上面几个角度的分析,我们可以知道,整个系统中天然存在着巨大的计算资源的超卖空间。那么我们应该如何进行超卖的设置和实施呢?
静态超卖
最开始我们采用了静态超卖的方式来提升系统的资源使用率。这是最简单直观的方式,并且见效也非常迅速。
所谓超卖,在现实世界我们有飞机票的超卖的案例。其实就是如果我们的系统有500G内存,那么我们提供服务的时候对外提供比如700G的内存服务,这样可以增加整体的资源量,提升计算资源的效率。
下面是具体的实现细节。
每个NodeManager上可以用来分配给YARN用来调度的资源是通过yarn.nodemanager.resource.memory-mb来进行控制的。每个NodeManager的物理机的系统内存取决于其真实的物理内存的大小。因为我们观测到当我们把yarn.nodemanager.resource.memory-mb设置为物理机内存大小的时候,即使YARN把资源都分配出去,物理机的系统内存依然有超过一半的剩余。所以很简单的一个思路就是我们可以把yarn.nodemanager.resource.memory-mb的值配置为一个大于真实物理机内存数量的值,从而做到简单的静态超卖。超卖的比例,需要根据我们集群上运行的作业的profile来进行调整。在我们分析了整个集群的资源空闲数据之后,我们统一对所有的物理机进行了10%的静态超卖配置,从而实现了全局10%的资源增长。
(点击可查看大图)
静态超卖的关键点在于要设置合理的超卖比例,以防止过于激进的超卖引起物理机操作系统级别的OOM,进而导致hadoop作业task的失败重跑,从而增加了作业的运行时间,甚至是hadoop作业的失败,从而进行作业的重试。这对有SLA要求的作业来说,是不可接受的。所以超卖比例相对比较保守,需要统计所有的物理机过去一段时间free memory的情况,来进行判断,找出合理的超卖值。
动态超卖
静态超卖之后,我们发现物理机的资源使用效率有所提升,但是大部分物理机在YARN满负载的情况下CPU使用率依然比较低,以及剩余内存空间依然很多,这些都意味着资源的浪费。为了进一步的提升物理机资源的使用率,我们提出了新的超卖策略。
那就是从YARN的层面针对每台物理机的实际资源使用情况进行动态的资源调整,从而避免了使用一刀切的静态超卖中出现的过于保守的情况,达到进一步提升资源的使用效率,又可以针对物理内存使用比较多的机器精准的实现单点的机器保护,降低其上的负载,避免出现某个机器过热而引起作业运行不稳定的情况。
(点击可查看大图)
1
功能设计
设计目标
1. 提升物理机的资源使用效率
2. 保证系统可靠性,对SLA作业提供服务
我们扩展了ResourceManager和NodeManager之间的心跳协议,从而让NodeManager可以通过心跳信息向ResourceManager汇报当前物理机的内存使用情况,以及操作系统的load数据。ResourceManager收到心跳消息之后根据这些数据进行判断来决定每个NodeManager进行capacity的重新设定。设定的目标有2个,一个是尽可能多的使用操作系统的物理内存,另一个是尽可能的保护物理机的运行不要出现系统级别的OOM和系统load过高的情况。
(点击可查看大图)
2
防御性设计
该功能的设计目标之一就是要实现SLA作业的支持,所以系统的稳定性是重中之重。
我们为这个功能设计了允许超卖的上限和下限。在这两个范围之间,会根据当前运行在目标NodeManager上的作业的申请的内存量和实际使用的内存量来计算当前的超卖比例,根据这个超卖比例来预测后面我们可以进行超卖的比例。同时按照一定的衰减策略为过去一段时间内分配到目标NodeManager上的task进行内存预留,来防止这些task的内存使用需求有突发性的升高。
针对系统最低允许的剩余内存,最多可以运行的task个数,最大system load进行了约束,来防止系统出现不稳定情况。
(点击可查看大图)
3
性能优化
在进行资源动态超卖的过程中ResourceManager端需要调用到2个函数updateNodeResource和 updateClusterResource。而这两个函数都有着很重的开销,频繁的调用会导致ResourceManager的调度性能的降低。为了减少动态超卖带来的额外系统开销,针对updateClusterResource我们实现了异步周期性更新,虽然降低了实时性,但是在满足业务需求的情况下,极大的降低了系统开销。针对updateNodeResource,我们对因为动态超卖需要进行资源更新的情况进行了rate limit,只有计算出来的目标NodeManager capacity的变动超过一定阈值才会触发NodeManager的资源变动。通过这两项改动,实现了平均3ms的feature overhead。
4
相关功能的交互
updateNodeResource
YARN提供了如下的命令行工具来对NodeManager的资源进行调整。动态超卖的实现会覆盖通过这个命令修改的结果。
(点击可查看大图)
在我们内部,针对资源量被通过上面的命令设置为0的情况,会跳过动态超卖的逻辑。
Preemption
我们的集群中打开了Preemption功能,需要确保preemption的配置在动态超卖的情况下,不能过于敏感,因为在动态超卖的情况下每个机器的资源量会被动态调整,从而整个cluster的资源总量发生变化,进而每个queue的资源量发生变化,那就意味着Preemption的决定可能会被推翻,为了减少不必要的preemption,那就需要尽量降低preemption的敏感度。
上线效果
我们已经在ebay最大的hadoop集群上线了这个功能,并且支撑着我们的内部SLA任务,目前我们通过这个功能实现了30%的资源增加。并且进一步的提升了系统的CPU使用率,以及内存资源的使用率。
(点击可查看大图)
END