走近伏羲,谈5000节点集群调度与 性能优化

jopen 10年前

5K项目是飞天平台的里程碑,系统在规模、性能和容错方面都得到了飞跃式的发展,达到世界领先水平。伏羲作为飞天平台的分布式调度系统,能 支持单集群5000节点,并发运行10000作业,30分钟完成100TB数据Terasort,性能是当时Yahoo!在SortBenchmark上 世界纪录的两倍。

伏羲介绍

“飞天”是阿里巴巴的云计算平台,其中的分布式调度系统被命名为“伏羲”(代码名称Fuxi),名字来自我国古代神话人物。伏羲主要负责管理集群的 机器资源和调度并发的计算任务,目前支持离线数据处理(DAG Job)和在线服务(Service),为上层分布式应用如ODPS/OSS/OTS提供稳定、高效、安全的资源管理和任务调度服务,为阿里巴巴集团打造 数据分享第一平台的目标提供了强大的计算引擎。

伏羲系统设计上采用M/S架构(如图1所示),系统有一个被称为“伏羲Master”的集群控制中心,其余每台机器上会运行一个叫做“伏羲 Agent”的守护进程,守护进程除了管理节点上运行的任务外,还负责收集该节点上的资源使用情况,并将之汇报给控制中心。控制中心与伏羲Agent之间 使用心跳机制,以监测节点健康状态。当用户向伏羲Master提交一个任务时, 伏羲Master会调度出一个可用节点在其上启动任务的主控进程AppMaster,主控进程随后会向伏羲Master提出资源请求,得到伏羲 Master分配的资源后,AppMaster通知相应节点上的伏羲Agent开始运行任务Worker。伏羲是一个支持多任务并发的调度系统,控制中心 伏羲Master负责在多个任务之间仲裁,支持优先级、资源Quota配额和抢占。

走近伏羲,谈5000节点集群调度与 性能优化

使用伏羲,用户可以运行常见的MapReduce任务,还可以托管在线服务,满足不同应用场景的需求。多用户可以共享集群,伏羲支持配置分组的资源配额,限定每个用户组可以使用的计算资源。紧急任务如重要数据报表可以提高任务优先级来优先使用计算资源。

5K带来的挑战

在5K项目攻坚过程中,我们看到大型云计算平台从设计到实现每一步都可能存在性能“陷阱”,原因主要有三个方面:规模放大效应,当系统扩展到数千节 点时,原本非瓶颈与规模成正比的环节,其影响会被放大;木桶效应,很多时候,系统中99%的地方都被优化过,完成剩下1%的优化看起来也只是“锦上添 花”,然而那1%很可能就会成为影响系统性能的致命的瓶颈;长路径模块依赖,有些请求处理过程可能需要跨越多个模块(包括外部模块),而外部模块性能的不 稳定性最终可能会影响到这个请求的处理性能和稳定性。

5K项目是一场全方位战役,给伏羲系统带来规模、性能、稳定、运维等多方面的技术挑战,例如下面的性能“陷阱”。

■ 通信消息DDoS:在5000规模的集群中,不同进程之间的RPC请求数量会随规模猛增,网络中总请求数可达10000 QPS,极易造成系统中单点进程的消息拥塞,从而导致请求处理严重超时。另外消息处理还存在队头阻塞(HoL)问题。

■ 关键函数OPS:伏羲Master是资源调度的中心节点,内部关键调度函数的OPS必须达到极高的标准,否则就可能因为木桶效应影响到集群整体的调度性能。

■ 故障恢复对外部模块依赖:伏羲Master具有对用户透明的故障恢复功能(Failover),其恢复过程依赖写在Nuwa上的Checkpoint(注:Nuwa是飞天平台的协同系统,如名字服务)。因此,整体恢复速度会受到Nuwa访问速度的影响。

我们做了大量伏羲优化工作来规避上述的性能“陷阱”,涉及到架构设计、实现细节和模块依赖,透过现象看本质,从最底层性能分析入手一步步找到瓶颈。下面结合具体的实战例子来分享优化过程。

伏羲优化实战

通信性能优化

在5K项目初期阶段,我们测试大规模并发作业时发现,当作业数量超过1000时就容易出现运行时间变长的现象。分析监控曲线和日志,我们发现AppMaster发给伏羲Master的资源请求出现大量消息超时,AppMaster迟迟拿不到资源,资源请求处理的延时很高。

消息从到达伏羲Master进程到最终被处理返回的总时间主要包括在队列中等待时间和实际处理的时间,因此延时高无非是两个原因:消息处理本身的 OPS下降;消息堆积在待处理队列中未被及时处理。顺着这一思路,在通过Profiling发现伏羲Master资源调度关键函数并没有占到整个消息处理 延时的大部分后,罪魁祸首就只剩下消息堆积了。在绘出了伏羲Master中资源调度消息队列中消息堆积的曲线之后,果然发现当作业数量增加时,堆积的请求 数量剧增(如图2所示),每一条请求的处理时间也较小规模时高出很多。

走近伏羲,谈5000节点集群调度与 性能优化

为什么在伏羲Master队列中会堆积如此多的消息?在伏羲系统中,守护进程伏羲Agent和AppMaster都需要向负责资源调度的伏羲 Master查询资源状态,在通信策略上采用了定期Polling的方式,缺省是每秒查询一次。采用Polling通信方式主要基于其简单性,能比较有效 地应对网络故障,消息传递发送过程比较自然有规律。然而在5000规模集群中,这个策略必须进行调整优化,否则会造成伏羲Master被大量请求 “DDoS攻击”而无法服务。

定位到消息堆积的问题后,我们立即对消息通信策略进行了流控,算法简单有效:发送端检查如果上次询问的请求结果已经返回,表明目前伏羲Master 请求处理较为顺畅,则间隔一个较短的时间后进行下一次询问。反之,如果上次询问的请求超时,说明伏羲Master较忙(例如有任务释放大批资源待处理 等),发送端则等待较长时间后再发送请求。通过这种自适应流控的通信策略调整,伏羲Master消息堆积问题得到了有效解决。

此外,我们还解决了伏羲Master消息的队头阻塞(HoL)问题。AppMaster需要与伏羲Master通信获得资源调度结果,同时也与伏羲 Agent通信进行Worker的启停。由于伏羲Agent数量远大于伏羲Master,在极端情况下,如果AppMaster采用同一个线程池来处理这 些消息,那么伏羲Master消息会被前面大量的伏羲Agent消息阻塞。我们将消息处理的全路径包括从发送到处理完毕等各个时间段进行了 Profling,结果印证了队头阻塞现象。当一个任务的Worker较多时,AppMaster需要与之通信的伏羲Agent也会增多,观察到 AppMaster拿到资源的时间明显变长。针对队头阻塞问题,我们通信组件中加入了独立线程功能达到QoS的效果,并应用在AppMaster处理伏羲 Master消息的通信中。如图3所示,伏羲Master的消息单独使用一个线程池,其余消息则共用另一个线程池。

走近伏羲,谈5000节点集群调度与 性能优化

通过上面的两项性能优化,伏羲系统内部的通信压力得到显著降低,提高了通信效率。AppMaster与伏羲Master之间的资源请求通信得到改 善,任务提交后能很快分配到资源开始运行,提高了多并发任务场景下任务的完成速度。例如,经过这个优化,用户通过ODPS客户端对海量数据进行Ad hoc的SQL查询处理速度能得到显著提升。

关键函数优化

在5K项目中我们还重点关注系统中的关键函数性能,那里也可能藏着“陷阱”。伏羲Master在调度资源时的一个关键操作是:比较一个节点的空闲资 源能否满足该节点上排队等待的所有资源请求,从而决定该资源分配给哪个任务。这个函数的调用次数会与机器规模和请求数量成正比,因此其速度对伏羲 Master的调度OPS有决定性影响。

伏羲在调度资源时支持多个维度,如内存、CPU、网络、磁盘等,所有的资源和请求都用一个多维的键值对表示,例如 {Mem:10,CPU:50,net:40,disk:60}。因此,判断一个空闲资源能否满足一个资源请求的问题可以简单地抽象成多维向量的比较问 题,例如R:[r1,r2,r3,r4] > Q:[q1,q2,q3,q4],其中1、2、3、4等数字表示各个维度,当且仅当R各个维度均大于Q时才判断R>Q。比较次数决定了这个操作的时 间复杂度。最好情况下只需比较1次即可得出结果,如判断[1,10,10,10]大于[2,1,1,1]失败;最差需要D次(D为维度数),如判断 [10,10,10,1]大于[1,1,1,10]需比较4次。在资源调度高频发生时,必须对这里的比较进行优化。

我们通过Profiling分析了系统运行时资源空闲与请求情况,在资源充足时通常值最大的维度最难满足,因此在资源调度场景我们采用基于主键的优 化算法:对每个资源请求的最大值所在维度定义为该向量的主键,当有空闲资源时首先比较主键维度是否满足请求,如果在主键上满足再比较其他维度。此外,对一 个节点上排队等待所有请求的主键值再求一个最小值,空闲资源如果小于该最小值则无需再比较其他请求。通过主键算法,我们大大减少了资源调度时向量比较次 数,伏羲Master一次调度时间优化到几个毫秒。注意到资源请求提交后不会改变,因此计算主键的系统开销可以忽略不计。

伏羲Master关键调度性能的优化增强了系统的规模扩展能力,用户利用飞天平台能管理更大规模的集群,容纳更多的计算任务,发挥出云计算平台的成本优势。

模块依赖性能优化

伏羲Master支持故障恢复,在重启后进行故障恢复时需要从Nuwa读取所有任务的描述文件(Checkpoint)以继续运行用户任务。考虑到 之前Nuwa服务在服务器端对文件内容没有做持久化,伏羲Master在读取了Checkpoint后还会再写一次Nuwa,这个回写操作性能依赖于 Nuwa模块。在5000节点的集群上,名字解析压力的显著增加导致Nuwa在Server的回写操作上也出现了性能下降问题,最终通过模块依赖传递到了 伏羲Master,从而影响了故障恢复的性能。经测试观察,一次Checkpoint回写就消耗70秒,这大大降低了伏羲系统的可用性。

我们对伏羲Master故障恢复进行了优化。首先,从伏羲Master的角度,在故障恢复时刚刚读取的Checkpoint内容在Nuwa服务器端 是不会发生改变的,因此读取Checkpoint后没有必要回写到服务器端,只需要通知本地的Nuwa Agent让其代理即可,Agent会负责服务器宕机重启时向服务器推送本地缓存的文件内容。于是与Nuwa团队的同学合作,在Nuwa API中新增加一个只写本地的接口,这样伏羲Master规避了在故障恢复时回写Checkpoint的性能风险。优化后,在5000节点集群和并发 5000任务的测试规模下,一次故障恢复中处理Checkpoint操作仅需18秒(主要时间在一次读取)。可见在分布式系统中,对外部模块的依赖哪怕只 是一个RPC请求也可能是“性能陷阱”,在设计和实现时尽量避免出现在关键路径上。

故障恢复是分布式系统保证可用性必须具备的功能,经过优化,伏羲Master的快速故障恢复增强了飞天计算平台的可用性和稳定性,屏蔽了硬件故障,使用户的使用过程不受影响。

工程经验

高质量代码没有捷径可走,也不能只靠制度流程,唯有认真二字:作者认真、Reviewer认真、测试认真。

■ 任何一个Item,无论是解决Bug还是新增Feature,都必须在动手写代码前讨论清楚方案,Code Review不能代替方案讨论。在讨论时作者需要回答两个问题:这个解决方法真的可行吗?副作用是什么?这些讨论需要记录在Wiki或者BugFree等 工具上进行跟踪。

■ 小步快跑,尽早提交Code Review,很多问题在这个阶段就能发现,不必等到测试中发现,代价大。

■ 代码Reviewer对Item有一半的责任,因此Review时不是简单过一遍字面完事的。我采用的Checklist有:是否准确反映了之前讨论好的 方案;是否存在死锁、“性能陷阱”;模块化封装是否足够;函数名变量名是否规范,日志格式是否规范;注释是否足够。一段代码Review迭代10次左右是 很常见的。

■ 一定要有针对性的测试验证。

■ 代码提交时关联相应的Bug和Review ID,便于后续追溯。

总结

以上和大家分享了5K项目的一些实践经验,伏羲系统在5K项目中还做了很多有意义的系统优化和技术探索,参与其中收获颇丰。性能是功能的一部分,是 系统生死线而非锦上花。5K项目只是阿里云计算平台技术发展的一个开始,未来会在更大规模和更丰富计算模型等方面进一步发展,为用户构筑可用可靠的云计算 引擎,进一步降低成本,挖掘数据价值。