今年的软挑最终止步于粤港澳赛区第16名,总成本为16亿3979万6349,赛区第一名总成本为15亿3903万4817。
虽然没进入决赛,但是拿到了华为面试直通卡,也喜提广州一日游,算不虚此行了。决赛虽然还在继续,但是已与我无关,遂写一篇博客记录比赛历程,分享经验。
关注公众号 打代码的苏比特,获取java面试百问百答和面试高频算法题~
初赛赛题与复赛赛题略有不同,但是我们团队的思路初赛、复赛均适用,所以此处只描述复赛题目。另外,赛题为华为版权所有,此处只做简单描述。
云上资源的规划和调度是云计算场景中非常重要的一个优化问题。好的优化算法能够为云运营商节约上亿的运营成本,并为客户提供更稳定、更流畅的云端体验。
服务器类型:在公有云的运营场景中,我们的数据中心可以选购的服务器类型有多种,服务器可以分为A 和B两个节点,服务器拥有的资源(CPU 和内存)均匀分布在这两个节点上。
服务器成本:数据中心使用每台服务器的成本由两部分构成:硬件成本和能耗成本。硬件成本是在采购服务器时的一次性支出,能耗成本是后续服务器使用过程中的持续支出。
虚拟机类型:我们面向用户提供了多种类型的虚拟机售卖服务,用户可以根据自己的需求来自由选购。不同的虚拟机类型,有不同的CPU、内存需求。
单节点/双节点部署:由于服务器存在两个节点,对应的虚拟机也存在两种部署方式:单节点部署或双节点部署。单节点部署指的是一台虚拟机所需的资源(CPU和内存)完全由主机上的一个节点提供;双节点部署指的是一台虚拟机所需的资源(CPU 和内存)必须由一台服务器的两个节点同时提供,并且每个节点提供总需求资源的一半。
本题使用交互式评测,你的程序应该从标准输入读取输入,并将输出打印至标准输出。交互协议如下:
<---- input ----> (用于说明,该行非实际交互数据)
2
(NV603, 92, 324, 53800, 500)
(NV604, 128, 512, 87800, 800)
2
(c3.large.4, 2, 8, 0)
(c3.8xlarge.2, 32, 64, 1)
3 2
2
(add, c3.large.4, 5)
(add, c3.large.4, 0)
2
(del, 0)
(add, c3.8xlarge.2, 1)
<---- output ----> (用于说明,该行非实际交互数据)
(purchase, 2)
(NV603, 1)
(NV604, 1)
(migration, 0)
(0, A)
(0, B)
<---- input ----> (用于说明,该行非实际交互数据)
3
(add, c3.large.4, 2)
(del, 1)
(del, 2)
<---- output ----> (用于说明,该行非实际交互数据)
(purchase, 0)
(migration, 0)
(1)
(purchase, 0)
(migration, 0)
(1, B)
总成本低的方案胜出。总成本包含两部分:购买服务器的整体硬件成本以及服务器消耗的整体能耗成本。整体硬件成本即将选手输出的方案中所有购买的服务器的硬件成本相加。整体能耗成本的计算方式为:在处理完每一天的所有操作后(包括迁移,创建和删除),裁判程序会将当前有负载(至少部署了一台虚拟机)的服务器视为开机状态,没有任何负载的服务器视为关机状态,以此计算当天的能耗成本。整体能耗成本即每一天的能耗成本的总和。
baseline。拿到初赛题目的时候,先写了第一个java版本的baseline,直接大力出奇迹,先冲个5000台服务器,能满足所有要求。主要是解决IO以及代码提交的问题。
服务器部署策略。部署策略为最简单、朴素的策略,从第一个服务器开始,遍历到能满足当前请求的服务器,将当前请求部署在该服务器上。类似于操作系统中磁盘分配的FirstFit算法。
优化购买策略。在第一个大力出奇迹的版本上,加入全新的服务器购买策略。
实现迁移。
定义服务器资源剩余率。计算方式为:(剩余CPU数*100/初始CPU数)+(剩余Memory数*100/初始Memory数),如此初始的资源剩余率为200,随着使用资源剩余率逐渐下降。代码如下:
//当服务器上未部署虚拟机时,资源剩余率为200
A_Core + B_Core) * 100 / CoresInit +
(A_Memory + B_Memory) * 100 / MemoriesInit
server类实现Comparable接口,方便排序
static class Server implements Comparable<Server>
将serverList按服务器资源利用率进行排序
//因为Server类实现了Comparable接口,所以可以直接调用该方法进行排序
Collections.sort(serverList);
简单朴素的迁移策略,将服务器利用率小的服务器上的虚拟机,迁移到服务器利用率大的服务器上,争取将利用率高的服务器填满,将利用率低的服务器排空。
serverList排序之后,从服务器利用率小的向前遍历,拿到资源利用率最小的服务器,遍历该服务器上的虚拟机,分别拿出每一个虚拟机,再从服务器利用率高的向后遍历,尝试将拿出的虚拟机部署到该服务器上。
但是这个遍历策略有一个十分致命的额问题,它采用了三层for循环,时间复杂度过高,后续在提交代码的过程中发生了超时。
优化购买策略,加入服务器性价比排序。和迁移中的排序一样,定义一个服务器的性价比,购买服务器时,选择能满足要求且性价比最高的购买。性价比的计算方法为:服务器价格/(cpu数+内存数)。计算代码如下:
double performance = (price + 0.0) / (cores + memories);
新的购买策略。
读取当天的所有请求,存入todayRequestList中
依次处理todayRequestList中的请求,如果处理成功,则将该条记录从todayRequestList删除
若处理失败,则表示当前剩余的服务器资源不足以处理剩余的请求了,需要购买新的服务器。
此时,todayRequestList中为剩余的未处理的请求。统计todayRequestList中的请求,计算剩余请求总共需要的CPU及memory(totalCoreRequest和totalMemoRequest),和剩余请求中最大需求的CPU及Memory(maxCore和maxMemory)
遍历服务器List,找到可以满足maxCore和maxMemory需求,且花费最小的服务器。其中,花费不仅考虑服务器的价格,还考虑了服务器的电费消耗,即 用购买数量*购买该服务器时剩余待处理数据天数lastDay*电费,代码如下:
int cost = num * serverInfo.price + num * lastDay * serverInfo.powerCost;
统一输出。将所有的输出信息存入List中,包括服务器购买、迁移、虚拟机部署信息,在合适的时候,统一输出这些信息。
细节优化,减小程序运行时间。
迁移玄学调参。
最终的思路基本是延续了上面的心路历程中的最终版本,基本的流程图如上,其中,迁移部分一直使用着最简单朴素的方式,在流程图中未画出。
未经整理的代码:https://gitee.com/MicahYin/code-craft2021/tree/master