本届挑战赛赛题来源于华为公司实际面对的一个生产场景,即云上资源规划、调度的优化,而优秀的优化算法不仅能为云运营商节约上亿的运营成本,更能为客户提供更稳定、更流畅的云端体验。该赛题要求参赛者依据不同服务器、虚拟机以及某个时间段的订单需求,设计出购买服务器、放置虚拟机以及定价博弈等完善的调度策略,力图降低云运营商成本,提升盈利。因此我觉得这个题目是十分有趣而且有意义的,可以一窥云上资源调度的问题。我们的成绩是粤港澳赛区复赛第7,差一些进决赛,有点遗憾,但是也拿到了华为的面试直通卡。【附上没有整理的代码https://github.com/MiaoMiaoGarden/Huawei-CodeCraft-2021】
题目中描述了一些名词。
服务器类型:在公有云的运营场景中,我们的数据中心可以选购的服务器类型有多种。服务器使用NUMA架构,有单节点和双节点之分。双节点服务器可以分为A和B两个节点,服务器拥有的资源(CPU 和内存)均匀分布在这两个节点上,虚拟机不能横跨两个节点。
服务器成本:数据中心使用每台服务器的成本由两部分构成:硬件成本和能耗成本。硬件成本是在采购服务器时的一次性支出,用户可以根据自己的需求来自由选购。能耗成本是后续服务器使用过程中的持续支出,只要某一天这台服务器上放置了虚拟机,那么这一天就要计算能耗成本。
虚拟机类型:我们面向用户提供了多种类型的虚拟机售卖服务,用户可以根据自己的需求来自由选购。不同的虚拟机类型,有不同的CPU、内存需求。
单节点/双节点部署:由于服务器存在两个节点,对应的虚拟机也存在两种部署方式:单节点部署或双节点部署。单节点部署指的是一台虚拟机所需的资源(CPU和内存)完全由主机上的一个节点提供;双节点部署指的是一台虚拟机所需的资源(CPU 和内存)必须由一台服务器的两个节点同时提供,并且每个节点提供总需求资源的一半。
设计服务器、虚拟机的数据结构自然不必多说,直接讨论解决方案。从问题的定义来看,我们扮演的是云服务运营商的角色。保证用户的虚拟机请求得以满足的情况下,尽量降低自己的成本,这是首要目标(次要目标是决策速度,速度不能超过一定时限)。整体解决方案需要考虑三种行为的策略:购买服务器、放置虚拟机和虚拟机迁移。
成本分为硬件成本和能耗成本,购买服务器的策略优化主要目的是降低硬件成本。降低硬件成本要求我们购买服务器的花销尽可能少,尽可能少买并且尽可能买价格便宜的机器。我们查看给定的服务器资源和价格的关系,发现一些服务器的价格高,可能是因为cpu资源比较多,一些服务器的价格低,可能是因为mem资源比较多,当然也有两种资源都比较多的机器。总体来说,贵的机器基本上是有贵的理由的。买服务器有四种容易想到的策略:
从上面的考量中我们发现,服务器的购买与用户请求的虚拟机类型相关。我们的方案是在每次服务器不足以满足一台虚拟机请求的时候,就新购买一台。购买服务器的型号根据这个虚拟机请求来决定。我们设计了一个weight函数,这个函数比较复杂,因为权衡了很多因素。
left_Day是剩余天数,life是这台虚拟机的生命天数。
p0和p1是今天所有请求放置的虚拟机的cpu资源占总资源比例和mem资源占总资源比例。这决定了今天的虚拟机请求要求的主要是cpu还是mem资源。
last_cpu和last_mem是这个型号的服务器放置完这台虚拟机之后剩余的cpu和mem资源。这决定了这台服务器的剩余碎片大小,是否物尽其用,是否能供给其他虚拟机请求。
q0和q1是服务器上的cpu占总资源比例和mem占总资源比例。这说明了这台服务器资源主要是cpu还是mem,与p0和p1是供需关系。
设计了一个交叉熵函数来求得(p0, p1)和(q0, q1)之间的相似程度。js_1表示的是这台服务器放置了虚拟机之后的供需相似程度,js_new表示放置之前的供需相似程度。
用一个线性组合来求得一台服务器的得分。这台服务器的能提供的资源与需求资源的匹配程度的影响遍布剩余的天数,因此js_1以left_Day为权重,js_new类似。money是服务器的价格,money与资源的商表示服务器的性价比。如果这一天有大量的虚拟机插入请求,那么购买服务器的时候可以不用考虑太多的放置之前的供需相似程度。这个线性组合汇集了众多影响因素,通过调整参数可以增加对场景的适应性。(在历史数据量足够大、足够典型的情况下,根据历史数据调整参数应该是可以适应未来的虚拟机请求的。)
最后遍历所有可选的服务器型号,找到能放置这台虚拟机的得分最高的服务器购买一台。
以单节点虚拟机的放置为例子。server.fit()函数返回一个bool值,表示这个节点的剩余资源是否足够放置虚拟机,如果足够放置,才可能被选择。在能放得下的节点中选择代价最小的。我们用这两点计算代价:
最好的情况下是这台虚拟机放置完了之后,这个节点直接满了,物尽其用,否则的话,越放置剩余的资源越可能是不可利用的碎片。因此,如果放置完了这台虚拟机之后,该节点剩余资源越多,那么代价越大。
如果这台服务器还没开机,虚拟机的放置导致能耗成本要计算了,那么代价就更大了。
迁移这块儿其实不是我实现的,但是我参与了方案的设计,简单说说吧。迁移的目的是将虚拟机重新排布,需要重新排布的原因是这一天可能有许多虚拟机被删除了,因此空出了一些资源,那么可以把剩下的虚拟机归拢。迁移的好处有两个,一是空出了资源,类似OS内存管理的内存回收,会减少内部碎片,为以后放置大的虚拟机提供了空间;二是可以减少开机机器的数量,从而减少能耗成本。有了这两个目的驱动,加上迁移次数的限制(小于3n/100向下取整,n是正在运行的虚拟机总数),迁移的策略基本上就出来了:从比较空的机器迁移到比较满的机器。
这个迁移策略看似很容易想到,也十分自然,考虑的因素比较少,但却是经过了一番实验才选择的。如果服务器排序遍历的顺序不合适,可能出现不够好的迁移策略:例如按照利用率,服务器的排序是A<B<C<D,一台虚拟机V原本放置在A,如果C不能放置,它可能被迁移到B,然后C上的虚拟机迁移到了D,空出了位置给V,但V却再也不会被迁移了。这也是为什么我们的from_server和to_server的排序是按照不同的策略来进行,并且尝试了多种策略才选择到一个比较优的排序方案。
除了缩减成本之外,代码运行速度也曾经掣肘我们,因为代码要求必须在120s之内处理完所有的请求并给出决策。尤其是迁移代码,其实对速度来说十分关键,假如用m和n分别表示虚拟机和服务器节点数量,那么迁移会是O(mn)的复杂度,m和n轻易可以达到三位数的量。速度的优化基本上是和代码实现有关:
这比赛整挺好,就是考虑的东西太多,很考验分析问题的能力和工程能力,还有团队协作的能力。比赛过程为了找思路我也翻了一些云资源分配策略的论文,有一些方法比较高级:用上了蚁群算法和模拟退火这种运筹学上的优化算法,但这些方法既不好实现,又需要较大的计算量。还有一些论文预测未来的虚拟机请求,以此来帮助当下做决策。复赛的时候要求输出一天的结果之后才能得到下一天的请求输入,因此我们是直接用历史情况当作未来情况的预测,这种时序数据预测是假设数据是平稳的,其实准确率应该不高。