阿里全球调度算法大赛 Alibaba Global Scheduling Algorithm Competition - GSAC2018 分初赛、复赛、决赛三个阶段。 初赛和复赛在阿里云的天池平台线上进行。初赛前 100 名的团队进入复赛,复赛的前 10 名获得决赛资格,最终只有 6 支队伍进行决赛 —— 这 6 支队伍到杭州阿里巴巴总部 进行现场答辩。 我们以master_deng
的队名三人组队,初赛排名是 12/2116;之后由于比赛规则,由两人继续参加复赛,排名是 11/100,止步于此。 从复赛的排行榜看,我们的成绩与前面的队伍的差距还是很大的。
初赛阶段从2018年6月初开始,到8月14日止,场景是 大规模集群中在线应用的部署 。 具体说,有 68k 多个 Pouch容器 ,分属于 9k 多个持续运行的在线应用,需要部署到不超过 6k 台机器上,限制是不能超过集群总的资源容量,还要满足应用间的干扰约束规则,最终实现较好的资源利用率和机器间的负载均衡。 需要注意的是,
比赛以分时的曲线的形式给出了应用的资源使用模型(CPU,内存)。刚开始有些想法:在运行中的某个时刻,将某个应用迁移到其它机器,从而将高峰和低谷时段不同的应用的资源曲线错峰填谷搭配,提高资源利用效率。不过在初赛解读(见附件PPT,及视频)中,比赛方 否定 了这个想法,虽然理论上看起来不错,但不符合实际,得不偿失。这样也让问题简化了不少。
比赛数据的初始状态中(集群的 初态),有些应用的容器实例已经部署到机器上了,还有一些等待部署,即机器不是完全空闲的状态。
最终选手提交的是一个<实例Id, 机器Id>
的列表,即部署/迁移动作列表。但并 不是 简单地给出实例及其最终部署的目标机器(集群的 终态),而是从 初态 到选手得到的 终态 的全部过渡动作,而且每步动作都要满足资源容量约束和应用干扰约束。好在比赛不限制动作的总数。
虽然部署的是在线应用,但 部署过程 本身是 离线的,即所有应用的资源模型都已知,据此计算出优化的终态,实施部署,之后集群才算开始运行;而不是在集群运行期间响应用户提交的新应用(在线部署)。离线部署对计算时间不太敏感,可以长一些,所以比赛没有硬性限制计算时间。
实际的集群是持续运行的,而且会有新应用提交,也有旧应用下线,但集群中的大部分应用是较稳定的。可以根据资源监控的历史数据对这些已知的应用建模,并且定期(比如每天)重新计算并调整集群的最优部署。初赛解读 中提到比赛的场景来自阿里内部的集群资源管理系统 Sigma。
因为 Sigma 兼容 Google 的 Kubernetes (k8s) API,之前我认为两者的资源管理方式也是差不多的,即 在线资源管理 。结合这次比赛,以及初赛解读中提到的 Sigma 是面向终态的架构设计,看来两者还是有很大差别的。 Sigma 补上了 k8s 缺失的 全局重调度 这个很有实践意义的重要功能。 k8s 有一个孵化项目 descheduler,其设计目标也是重调度,可以参考 相关讨论 #12140。 离线部署功能也可以从 Sigma 中划分出来,作为单独的组件;但两者有不少功能是可以共用的(比如约束检查),所以我认为合在一起更合适些。
机器分配(Assignment)问题是典型的 组合优化问题,也可以表示为装箱问题 Bin Packing,或直接说 Packing。规模较小的问题可以使用 Gurobi 或 CPLEX 等商业求解器按 0-1整数规划 求解。但比赛的数据规模超出了求解器的能力;另外,目标函数是指数函数,也难以直接使用求解器。
使用装箱的近似算法,比如首次适合算法(First fit,FF),肯定可以排在初赛的前 50 名,顺利进入复赛。 决赛的 6 支队伍仅有 4th - SuperUncle 只用 Best fit 算法,其它 5 支队伍都使用了某种 局部搜索,除了随机搜索(6th - 地球漫步,3rd - rivulet,2nd - greydog),还有 模拟退火 Simulated Annealing,SA (5th - yxgy,1st - 我就看看不提交),不过 1st - 我就看看队 在答辩中提到,他们的 SA 并没有改变温度,实际上也是随机搜索;yxgy 的 SA 跟 我就看看队 的是不同的。
集群共有 6000 台机器(Machine,不致混淆时,简记为 m),按资源容量(Capacity)分为 2 种,各 3000 台,Id 是连续的整数。 从机器数量来看,集群应该算是中等规模吧。
所谓在线应用(App),主要就是 Web 应用。共有 9338 个应用。应用是匿名的,通过 App Id 来区分。App Id 也是连续的整数。 每个应用有不同数量的实例(Instance,简记为 inst)。每实例就是一个 Pouch 容器。共有 68219 个实例。Inst Id 也是整数,但 不是 连续的。 平均每个应用有 7 个实例,但实际上应用的实例个数是很不均衡的:
应用之间有干扰(Interference,简记为 X)约束,表示为 <App_A, App_B, k>
这样的规则。其含义是:若某机器上已存在 App_A 的实例,那么允许存在的 App_B 的实例个数不能超过 k 个。
<App_A, App_A, k>
这样的规则实际是限制同一机器上 App_A 的实例总数不超过 k+1 个;对这种规则,即便 k=0,机器上还是可以部署一个 App_A 的实例的,当然也可以不部署<App_A, App_B, k>
与 <App_B, App_A, m>
,即正,反向的约束 没有直接的关系。也就是说,每个应用都有自己的偏好,App_A 可能很嫌弃 App_B,但 App_B 却对 App_A 毫不在意,可以 m ≠ k
,甚至可以不存在 <App_B, App_A, m>
这样的约束初赛解读中提到了 亲和/反亲和 约束,实际上这里的规则是硬性的,适合表达 反亲和(干扰)。
属于同一个应用 的实例有相同的 资源使用模型,包括下面几种资源,共用 200 个数值(维度)来描述:
98 个点的分时曲线大致 ~15 min 记录一个点,描述了应用实例在一天 24 h (实际是 24.5 h)的资源使用变化情况。
下图是将所有应用的CPU和内存资源使用量分别乘以该应用的实例个数后,分时刻求和,再除以集群资源总容量得到的 整体分时波动曲线。
此外,整体的硬盘使用率 居然达到了 ~94 %, 比CPU和内存还紧张!
应用之间在资源使用量和分时波动上都存在显著的不均衡:大部分的应用使用的资源很少,而且一天内基本没有波动。少数关键应用使用了大部分资源,而且有明显的波动趋势。
很容易想到分时曲线是从资源监控的历史数据统计出来的。模型的准确性总是值得讨论的,但比赛中不考虑这个问题。另外,上面提到不少应用的资源使用量在一天内没有波动,可能没有对全部应用建模,而只对部分 关键应用 建模了吧。
平均下来每台机器有11个实例。考虑到成本,要尽量 少占用 机器。比赛的问题就是 把 68k 多个实例部署到不超过 6000 台机器上,具体说,
每个实例部署时都不能违反这两个约束,最终整个机器的状态也不能违反。 若只考虑资源约束,可以认为是一个大规模的 多维向量装箱 问题,维度是200维,而且箱子(机器)是异构的。
具体的 优化目标 (Total cost score,简记为 score,越小越好) 是:
其中,$T=98$,是分时数据点的个数; $M=6000$,是机器总数;$s^t_j$ 代表机器 $j$ 在时刻 $t$ 的成本分数。 交换两个求和的顺序后,说明可以先分别计算 各机器在 98 个时刻的成本分数平均值 ,然后再合计集群中所有机器的成本。
如果机器 在时刻 没有实例部署,则 $s^t_j=0$ ;(*注) 否则, $s^t_j = 1 + \alpha(e^{\max(0,c-\beta)} - 1)$ 其中,$c$ 是机器 $j$ 在时刻 $t$ 的 CPU 利用率;$\alpha, \beta$是惩罚系数,分别为 $\alpha=10, \beta=0.5$。 如果提交结果不符合规范,成本分数为 $10^9$。成本分数 越低 越好。
*注:成本分数仅考虑 CPU 资源。 若 CPU 利用率为 0,按公式计算出 ,即 没有部署任何实例的空闲机器 的成本分数是 1。另一方面,若一台机器的 CPU 使用率在所有时刻均不超过 50%,其成本也是 1。为了区分两者,所以 规定空闲机器的成本为 0。 上面提到,每台机器可以分别计算在 98 个时刻的成本分数平均值 :如果所有时刻的 CPU 利用率都不超过 50%,机器的成本分数 也是 1;只要有一个时刻的 CPU 利用率超过 50%, 必然大于 1。
若 CPU 利用率不超过 50% ,则成本分数就是 1;超过 50% 后,就呈指数增长了。下面是成本公式的反函数,
若 ,则 ;即当 CPU 利用率从 50% 增加到 59.5%,成本分数就 翻倍 了。若 ,则 。
因此,优化目标是希望:
初始数据中,有约 30000 个(~ 44%)实例已经部署到 ~2500 台机器上了。初始部署的质量还是很高的:硬盘的利用率基本都在 90% 以上,甚至有的是 100%,而且都没有超出资源容量约束。 从成本分数上看,已部署的大型机器中,仅有一台的成本分数 > 1.0,即有些时刻的 CPU 利用率超过了 50%;而小型机器则有 400 多台的成本分数 > 1.0 。如果初始部署来自阿里的真实环境,那么看来算法对机器的异构性处理得不太好。
另外,有一百多个实例与所在机器上的其它应用的实例存在 违反干扰约束 的情况。看来发现了一个 Bug ;-)
由于大家对赛题理解存在偏差,一开始很多队伍的提交不满足格式要求,好在比赛方在7月9日开源了评分代码。 对上面提到的一百多个实例违反干扰约束的实例,我们开始是直接 跳过 这样的实例,保证机器上部署的实例都是满足两个约束的。但评分代码则 忽略约束,强制 将实例部署到机器上。参考评分代码,我们修正了这个处理逻辑。
数据和评分代码都公开了,比赛结束后,感兴趣的同学还可以尝试一下。
初赛数据中应用的 硬盘资源比 CPU 和内存要紧张得多,以至于仅考虑硬盘资源维度就可以得到比较好的部署结果。 7月5日,初赛刚开始排名,4th - SuperUncle 就给出了 5506 的最优解,随后 5506 分的队伍越来越多。月光鸣下(1st - 我就看看不提交) 在初赛结束后给出了 通过硬盘资源构造初赛最优解的思路,但还要在此基础上满足应用资源约束和干扰约束。 于是,比赛方在 7月26日 添加了初赛数据集 B。 相比最初的数据集 A,仍有 6000 台机器,大小各 3000 台;应用数仍为 9338 个;实例数增加了 5 个,为 68224 个。 发生变化的有:
初始状态已经部署了 61k 多个实例,仅剩 6900 多个实例尚未部署。因为 CPU 用量增加了,大部分机器的成本分数都超过了 1.0。这次没有违反资源容量约束或干扰约束的情况。
复赛从 8月15日 至 9月7日,比初赛时间短一些。这就要求选手初赛阶段的算法比较通用。复赛直接提供了 5 份数据集,除了初赛的在线应用,还增加了离线任务,场景也就变成了 在线应用(App)和离线作业(Job)混合部署(混部)。
这里首先明确一下名称:比赛的介绍中称为离线 任务,但这里将其称为离线 作业(Job)。一个作业(Job,如一个完整的 MapReduce 程序)包含多个任务(Task,一个执行阶段,如 Map 任务、Reduce 任务等)。
每个任务对应数据文件 job_info.x.csv
中的一行,其中 x 是 a,b,c,d,e 之一(job_info.e.csv
是空文件)。下面是 job_info.a.csv
的前 5 行,每行的格式为 离线任务Id, CPU 用量, Mem 用量, 实例数, 执行时间, 前驱任务Id列表
。
8886-18,0.50,0.25,111,98,
8886-14,0.75,0.38,20,88,
6801-2,0.50,0.50,3,128,
6801-3,0.50,0.25,13,122,6801-1,6801-2
6801-1,0.50,0.25,37,124,
8886-18
,赛题介绍中没有明确(导致答辩时各队伍的叫法都不同),这里我们把短横前面的部分称为 Job Id,如 8886
。同一作业的任务在数据文件中不一定是连续的,也不一定是顺序出现的,比如上面的例子。5 个数据集的 作业数/总任务数(行数) 分别为 a: 1085/5241, b: 1094/5637, c: 546/2840, d: 478/2250 和 e: 0<Task, Machine, BeginTime, Size>
,表示部署到某机器(Machine),在某一时刻(BeginTime,分钟)开始执行的离线任务(Task)的若干实例(Size)。这也是离线作业提交文件的格式。 部署机器不同,或开始执行时间不同,都不是同一批次。作业的资源占用、任务/实例个数、DAG复杂程度、执行时间也都是不均衡的,大部分是较小的作业。
我们可以根据 DAG 计算出作业的 关键路径(Critical Path),以及 最短执行时间。一旦某个关键路径上的任务结束,其后继就可以执行了。同一时段可以有多个任务都在关键路径上。非关键路径上任务 的 最早开始时间(BeginEarliest
) 与同时段的关键路径任务 的相同,最晚开始时间(BeginLatest
) 加上任务 的执行时间(Duration
)后,应与对应关键路径任务 的结束时间(End
)相同。 上面计算的都是作业本身的相对时间。一旦确定了作业中某个任务在 1470 分钟周期内的具体开始时刻(绝对时间),其前驱和后继的浮动区间也就随之确定了。
不过,复赛的成本分数公式并 没有涉及 离线作业的等待时间,只要不违反上面提到的 资源约束 和 顺序约束 即可。为了防止过载,让就绪的任务多等一段时间也是可以的。决赛答辩中,有的队伍就只进行了 拓扑排序,没有计算上面提到的关键路径参数。
上面提到,在线应用的 CPU 曲线有明显的峰谷。离线作业(Job)与在线应用(App)混部的主要目标就是 错峰填谷。通常后半夜在线应用的负载很低,这时候来执行离线作业,可以提高集群资源利用率。
复赛的 5 个数据集共用一个在线应用数据,跟 初赛数据集 B 的应用和干扰部分 是 一样的,共 9338 个应用,68224 个实例。 不同数据集的 实例 Id 稍有不同,但 每个应用的实例数量是相同的,因此实例 Id 不同并没有什么实质影响。
5 个数据集的机器配置和数量有所不同,具体如下表:a,b,e 共 8000 台机器;c 和 d 共 9000 台机器。a 和 b 都只有大型机器,资源总量最多;c,d 和 e 都有 6000 台小型机器,资源总量明显变少了,而且 e 的总量最少。
初始状态,5 个数据集的所有实例都 已经部署到机器上了。前面提到,可以不考虑初始部署,从空的集群开始执行 装箱 算法,生成优化的 终态。 也可以从 初态 开始,逐步调整实例的部署,这时就不能用装箱了。其实这也是比赛方暗示应该用 局部搜索(Local Search,LS) 算法。
有可能搜索过程很久,实例迁移的动作很多。要达到终态,可以输出搜索的 每一步实例迁移动作,但这可能存在不少冗余动作,即一个实例换了多个部署机器。 另一方面,可以比较 终态和初态的差异 ,生成迁移动作,这需要有足够的空闲资源(机器)进行腾挪,否则可能无法在资源和干扰的约束下到达终态。
复赛对在线应用的迁移增加了限制,采取多轮并发执行。一个迁移动作分为两个阶段:若 机器上的实例 要迁移到 ,先在目标机器 上新建一个实例 ,一轮结束后才删除 上的 ;即过渡阶段 占用了双份资源。可以在搜索的每步都保证这个限制,也可以仅在从终态差异生成迁移动作时考虑。 同一轮次的不同实例迁移可以并发执行。比赛限制的是迁移轮次不超过 3 次,一轮内能迁移的次数则没有限制。
迁移时先新建再删除是合理的,但我认为分轮次迁移并不太合理。相反,限制总的迁移次数更直观。 若资源分配算法比较好,可以将集群保持在比较理想状态,期望重调度需要迁移的实例不太多。
复赛还修改了成本公式,将惩罚系数 改为与机器上部署的实例个数 相关: 初赛阶段的成本公式规定如果机器 在时刻 没有实例部署,则 。因为所有应用实例的 CPU 使用量曲线都是>0的,所以只要机器上部署了实例,就不会有 CPU 利用率为 0 的时刻。换句话说,只要机器上有一个时刻的 CPU 利用率为 0,整个机器必然没有实例部署,如果不为 0,必然部署了实例。 复赛中,一台机器可以没有部署任何实例,但部署了离线 Task ,不能按初赛的规定令机器的分数为 0 。 没有 Task 执行的时刻,CPU 利用率为 0,对应的成本分数为 。如果有时刻 CPU 利用率超过了 50%,成本分数仍然是指数增加的。