简介:一份面向云计算学习者与系统设计人员的 Word 文档,系统介绍云计算资源分配算法的分类、原理与调度机制。内容以静态分配和动态分配为主线,解析最大最小公平、最优化、遗传等典型算法,并覆盖任务分配、负载均衡、能耗管理和任务迁移等调度环节;同时延伸到网络带宽调度、云游戏体验优化、机器学习训练资源调配等实际场景,便于读者理解不同算法的适用条件与取舍。文件总数为 1 个 docx 文档,包体约 21KB,目录结构简洁,适合移动端与本地快速查阅。已有 388 人学习下载。文档后半部分还集中综述了云计算调度算法的发展趋势,包括多元异构环境、多维度优化、强化学习与人工智能融合、云原生调度等方向,可作为课程报告、技术调研或方案设计前的参考笔记。
1. 云计算资源分配算法:先看清调度器在替谁做决策
如果你在云计算运维岗位待过半年,大概率会遇到这种需求:集群里明明还剩几十个核,线上却一直报容量不足;资源没占满,新任务却被拒之门外。多数人第一反应是加机器,其实问题出在调度入口——资源怎么分、按什么顺序分、分完留多少缓冲,这就是云计算资源分配算法要回答的事。它介于底层虚拟化与上层业务之间,决定每一份 CPU、内存、带宽落到哪台节点上。这篇文章写给正在做调度系统、K8s 二次开发、边缘云选型,或者想把集群成本压下来的人:先讲清楚分配模型,再给一套能直接照抄的最小实现,最后把最常踩的几个坑摆出来。
2. 资源分配在分什么:从核数与内存到多维约束模型
2.1 抽象资源与物理资源:分配的是配额,不是真实占用
不少老手在分配资源时有个误解:觉得把任务放上节点,分配过程就结束了。实际上资源分配算法操作的对象是“抽象配额”,不是物理占用。一个容器声明了 2 个 CPU、4 GB 内存,调度器就按照这个声明去累计节点负载,但真实使用量可能只有 0.3 个 CPU、几百 MB。也就是说,调度器手里握着的是一张“记账表”,而不是实时的性能计数器。
这张记账表的意义在于:分配决定了任务的“上限承诺”。节点 CPU 超卖之后,能不能扛住所有任务的峰值,取决于每份配额叠加后的结果。这也是为什么算法设计里第一件事不是写排序函数,而是定义清楚“可调度资源”和“已分配资源”的边界。可调度资源指节点总容量减去系统预留、驱逐缓冲、系统守护进程占用的部分;已分配资源是所有负载声明配额的累加值。两者相减才是真正能继续分出去的空间。
常见的翻车案例是把物理机标称的 64 核当成可调度核数,忘了宿主机操作系统、监控采集器、Kubelet 或虚拟化层本身也要吃资源。结果是算法算得完美,节点一部署就 CPU 抢占严重。我一般会参照云计算平台自身的节点预留策略,先给每台节点划出固定比例的不可调度区,再交给上层算法去排序,否则后面所有公式都建立在一个错误基数上。
2.2 多维约束:CPU、内存、带宽与 GPU 显存怎么折算
单看 CPU 不足以做可靠分配,因为真实负载的类型差异极大。计算密集型的批处理任务要的是核数和主频;在线 Web 服务吃内存和响应延迟;AI 训练与推理任务直接要求 GPU 显存。若只用单一维度做分配,很快会出现“CPU 均衡但内存倾斜”或“显存充足但 GPU 算力空闲”的怪象。
一个较稳的建模方式是把每个节点的可用资源看成多维向量,任务的请求也做成同维向量。例如:
- CPU 维度:按核数计量,换算成 100m 或者 0.1 核的细粒度单位;
- 内存维度:按 GB/MB 计量,同时区分内存上限与内存请求;
- 带宽维度:按 Mbps/Gbps 计量,适合有流量洪峰的业务;
- GPU 维度:按显存 GB 并附上算力型号标签,不跨型号混用。
不同维度之间往往不能简单折算,于是分配算法的核心退化为一个约束优化问题:在满足每个节点各维度不超卖的前提下,把任务放到使整体碎片最小的节点上。碎片是资源分配里很隐蔽的敌人,比如一个节点剩余 8 个 CPU 和 30 GB 内存,来了一个 6 CPU 4 GB 的任务非常合适;可如果按内存排序分配,把它丢到一个 10 核只剩 2 GB 的节点上,CPU 就白白废掉了。多维度同时参与排序,是避免这种浪费的底线。
2.3 用“云覆盖度”量化分配质量:一个能说服老板的指标
资源分配做得好不好,不能靠感觉。业界常用“云覆盖度”这个口径来做宏观评估:在观察周期内,集群中被有效利用的节点资源比例。计算方式是统计每台节点实际利用率超过某个阈值(通常是 60% 或 70%)的时长总和,除以集群总节点数与总时间跨度。
云覆盖度与实际利用率不完全一样,它更强调“资源有没有被持续、稳定地使用”。如果一台节点一会儿满载、一会儿空闲,平均利用率可能不低,但覆盖度很低,说明分配算法没有把负载熨平。这个指标最适合拿来评估不同算法在同一份历史负载下的表现:同一批任务回放三次,分别跑先到先得、贪心适配与启发式策略,谁能让覆盖度更高、分配失败率更低,谁就更适合当前业务。
不建议一上来就以最大利用率作为优化目标,那会把集群逼近过载边缘,一旦业务峰值到来,调度器救都救不回来。覆盖度配合 5 分钟粒度的延迟统计一起看,比单看平均值有用得多。这个指标在后面的验证章节还会出现,它是算法调优的审判官。
3. 主流资源分配算法怎么选:先到先得、贪心与启发式的适用边界
3.1 先到先得与轮询:为什么最朴素的策略仍在生产环境存活
提到资源分配,很多人的第一反应是 Kubernetes 的调度器,再往下想就是各种复杂的评分策略。但生产环境里依然有大量系统在跑先到先得(FCFS)或者简单轮询。原因很务实:这类算法实现成本低、行为可预测、出问题容易追责。当一个集群的任务类型很单一,比如全是定时跑批的离线作业,任务之间没有优先级差异、运行时长相近,先到先得完全够用。
轮询策略则是把新任务轮流放到各节点上,逻辑上是雨露均沾。它的优点是分配速度快,不需要实时计算每个节点的多维指标,适合节点数少、资源规格统一的边缘小集群。缺点是完全没有考虑任务大小与节点剩余资源的匹配关系,容易造成一部分节点内存吃紧、另一部分 CPU 闲着。如果业务混合了长任务与短任务、大任务与小任务,轮询的碎片率会迅速上升。
我的建议是:节点数少于 20 台、业务类型单一、没有明显波峰波谷时,先用轮询顶住,不要过度设计。把精力花在监控上,等数据证明碎片率真的高了,再引入更复杂的算法。从头开始就上启发式或预测式算法,通常会增加排查问题的难度,尤其是当分配结果受随机因子影响时,复现都变得玄学。
3.2 贪心适配与最佳适配:资源碎片才是隐形杀手
贪心适配算法是生产环境最常见的折中选择,核心逻辑是:每来一个新任务,在所有节点里找“剩余资源刚好能满足、又不会余量过大”的那个。这其实是装箱问题(Bin Packing)的在线版本,经典做法是最佳适配递减(Best Fit Decreasing)或首次适配递减(First Fit Decreasing)。这些算法不是最优解,但能以很低的计算成本拿到接近最优的放置结果。
我在实现这类算法时一般会先按任务的资源请求量从大到小排序,再依次放置。原因是:大任务对节点碎片的影响最直接,先放大任务能避免后期出现“节点拆得七零八落、任何大块头都塞不进去”的局面。这个思路有一个隐藏参数:任务等待队列的长度。队列越长,排序收益越明显;队列只有三五个任务时,排序带来的提升可以忽略。
贪心算法也有硬伤:它对在线场景不友好。真实集群里任务是一个一个来的,不是批量一次性到达,此时无法对整个队列做全局排序,只能按到达顺序局部贪心。为了缓解这个问题,可以引入一个很小的缓冲窗口——把新任务放进等待队列,累计三到五个任务后再集中做一次贪心放置。这个技巧在许多云计算平台的批量调度组件里都能看到,代价是单任务的启动时间增加一两秒,收益是节点碎片率明显降低。
3.3 启发式与预测型分配:让算法具备“回头看”的能力
当集群负载波动剧烈、业务类型混杂,纯贪心策略就开始露怯了。你无法判断当前空闲的节点,半小时后是否会被一个大任务打满;也无法判断某个经常被分到任务的节点,是不是只是碰巧总是第一个被扫到。这时就该上启发式策略,它本质上是在贪心的基础上加入“经验规则”参与决策。
一个很常见的启发式做法是节点评分制:调度器对每个候选节点计算多个维度的得分,再把得分加权求和。比如 Kubernetes 的默认调度器就是这样——它对节点做 0 到 100 的打分,综合了资源匹配度、亲和性、节点负载等多种因素。权重本身就是启发式经验的落点:如果业务更在意延迟抖动,就把内存剩余量的权重调高;如果业务更在意吞吐,就把 CPU 带宽权重调高。没有绝对正确的权重,只有适合当前负载的权重。
预测型分配在启发式之上再引入时间维度的判断,它会根据历史利用率曲线推测未来一段时间的空闲水位,把任务放到“预计将来也会空闲”的节点上。做预测型分配最常见的问题不是模型不准,而是数据口径不对。比如用 1 小时平均利用率做预测,却把分钟级的调度决策建立在小时级数据上,预测必然滞后。要落地预测,至少按 1 分钟粒度采样,保留 7 天窗口,并且把工作日与周末分开建模。如果这些前置工作做不到,宁可继续用贪心加打分的组合方案,也别强行上预测模型——后者的翻车概率远比想象的高。
4. 落地一个最小可用的资源分配模块:打分排序与超卖参数
4.1 定义节点与请求模型:先让数据结构贴近真实约束
动手写分配算法之前,建议先把节点和任务的数据结构定义清楚。很多人直接拿二维数组存 CPU 和内存,写到后面会发现缺少标签、缺少时间戳、缺少状态字段,导致算法没法扩展。我常用的做法是用数据类描述节点,把每个维度的总量和已分配量分开存:
from dataclasses import dataclass, field @dataclass class Node: node_id: str # 节点唯一标识 cpu_total: float = 16.0 # 可调度 CPU 核数 mem_total: float = 64.0 # 可调度内存,单位 GB cpu_alloc: float = 0.0 # 已分配 CPU 核数 mem_alloc: float = 0.0 # 已分配内存 GB labels: dict = field(default_factory=dict) # 节点标签,如 gpu型号、磁盘类型 @property def cpu_free(self): return self.cpu_total - self.cpu_alloc @property def mem_free(self): return self.mem_total - self.mem_alloc节点类里最容易被忽略的是 labels 字段,它承担着亲和性过滤的作用。比如任务声明了“需要 GPU 型号为 A100”,调度器得先通过标签把所有不符合的节点过滤掉,再做资源评分。如果把这张标签过滤表单一化,后期做异构集群时会非常痛苦。cpu_free 和 mem_free 用属性实现,是为了保证任何地方读取剩余量都是实时计算,避免多处维护副本导致数据不一致。
任务请求模型同样重要,它需要声明资源需求、优先级、运行时长预估和标签约束。实际生产系统里,任务往往还会带上“硬性需求”与“软性偏好”,硬性需求不满足就直接跳过该节点,软性偏好只影响打分高低。
4.2 核心打分函数:Python 实现多目标排序
在过滤掉不满足约束的节点之后,就进入核心打分阶段。下面是一段最精简的打分实现,它综合了资源匹配度和节点均衡度两个目标:
def node_score(node: Node, req_cpu: float, req_mem: float, cpu_weight: float = 0.5, mem_weight: float = 0.5) -> float: # 1. 计算部署后节点剩余资源的均衡度 cpu_remain = node.cpu_free - req_cpu mem_remain = node.mem_free - req_mem if cpu_remain < 0 or mem_remain < 0: return -1.0 # 负数表示不可放置 cpu_ratio = cpu_remain / node.cpu_total mem_ratio = mem_remain / node.mem_total # 2. 资源均衡度:两个维度剩余比例越接近,分数越高 balance = 1.0 - abs(cpu_ratio - mem_ratio) # 3. 资源余量:剩余越多,对新任务越友好,但也不希望太浪费 spare = (cpu_ratio + mem_ratio) / 2.0 # 4. 加权求和,权重可由上层配置 score = cpu_weight * balance + mem_weight * spare return round(score, 4)这段代码的逻辑不长,但有两个参数值得解释。cpu_weight 和 mem_weight 是两个自由可调参数:如果你的服务是内存敏感型,就把 mem_weight 调高到 0.7 甚至 0.8;如果对 CPU 突发敏感,就反过来调。balance 的作用是让分配结果尽量保持节点各维度资源消耗同步,避免出现 CPU 还剩一半、内存全空的局面。spare 是为了防止把节点塞得过满,给后续突发任务留一点空间。
这个打分函数在实际调度时还有一个使用前提:打分前必须先做约束过滤。比如任务要求节点拥有 3 GB 可用内存,那么 free 不足的节点应该直接剔除,而不是让打分函数返回负分后再忽略。过滤和打分分开做,代码的单元测试会好写很多,排错时也能立刻定位到是过滤条件写错还是权重配错。
4.3 超卖比与安全边际:两个必须写进配置的参数
打分函数只能决定“放到哪”,不能决定“放多少”。集群要不要超卖、超卖到什么程度,属于容量规划层面的决策,但它会直接影响算法可用性。为了说清这个问题,先引入超卖比的概念:节点可分配总量与实际物理容量的比值。
# 分配算法配置示例 scheduler: filter_backoff_ms: 200 # 过滤失败后重试间隔 cpu_over_ratio: 2.0 # CPU 超卖比,生产建议 1.2~2.0 mem_over_ratio: 1.0 # 内存不建议超卖,超过 1.0 容易 OOM score_weights: cpu: 0.5 mem: 0.5CPU 超卖比设为 2.0 意味着:一台 16 核的物理机,调度器最多可以分配出 32 核的配额。这在 CPU 密集的批处理集群里很常见,因为它赌的是“所有任务不可能同时跑满”。如果业务有明确的波峰时段,比如每天上午十点大量定时任务同时启动,超卖比最好降到 1.2 以下,否则 CPU 饥饿会直接拉高任务延迟。
内存是我个人最不愿意超卖的维度。内存申请后基本是独占占用,不具备弹性回收能力,超卖内存的后果不是性能下降,而是 OOM 驱逐。一旦发生驱逐,任务可能要重新排队,时间成本远大于当初省下的那点内存。所以我的配置习惯是 mem_over_ratio 恒等于 1.0,只有在测试环境中为了压测驱逐链路才会临时调到 1.2。把这两个参数放到 YAML 配置而不是硬编码在代码里,是因为调它们比改代码频繁得多——资源分配算法的调参,一半时间都在动这两个值。
5. 资源分配踩坑实录:五个让调度器翻车的现场
5.1 现象:明明还有空闲资源,新任务却一直调度不上去
排查时发现节点剩余资源充足,但调度器总报资源不足。原因多半出在“可调度资源”与“真实剩余资源”的口径分裂上:监控面板展示的是物理机维度利用率,而调度器用的是节点对象里的配额账本。如果节点上有大量未被纳管的容器或虚拟机,它们吃着内存却不记账,调度器以为还有 20% 空间,实际早已超卖。解决办法是把所有负载的配额声明统一收口到分配账本上,并定期对账,把“孤儿负载”纳入统计。
5.2 现象:算法选型没问题,但关键节点的 CPU 忽高忽低
这是打分权重的经典翻车现场。节点得分只考虑了资源余量均衡,却没考虑负载的突发特性。某个节点剩余资源很多,被反复选中,结果多个突发型任务叠加后 CPU 飙到 95%,而其他节点空闲。解决的思路不是推翻算法,而是给分配结果加一道“预期峰值校验”:每个节点打分前,先计算已有任务的历史峰值总和,除以节点容量,超过 0.8 就直接降低该节点得分。这比事后靠监控告警去补救要省心得多。
5.3 现象:内存明明够用,任务却频繁被驱逐
大多数情况下,调度器给任务分配的内存配额小于任务真实峰值。容器内部有页缓存、临时文件,都会产生额外占用。如果直接用容器声明值做调度依据,不加上系统层的预留缓冲,一旦多个任务同时达到峰值,OOM Killer 就会挑选优先级低的任务下手。对此我的习惯是:在所有任务的声明值基础上增加 5% 到 10% 的调度缓冲,这笔账由调度器计算,而不是让业务改声明。内存与 CPU 不同,CPU 可以超卖后靠内核 CFS 调度抢占,内存没有抢占机制,只能靠预留和驱逐兜底。
5.4 现象:低优先级任务长期饿死,高优先级任务排队排到超时
单纯的优先级抢占式分配会造成饥饿:低优先级任务永远被排在队尾,高优先级任务一旦持续涌入,低优先级直接没有执行窗口。解决这个问题需要引入“配额轮转”机制,为低优先级队列保留一个最小执行量。具体做法是:在每个调度周期里,先按优先级降序尝试分配,但每次最多分配固定数量的高优先级任务,剩下的时间段强制处理低优先级队列。让低水位队列有喘息空间,整体资源利用率反而上升,因为避免了高优先级任务因相互等待而空转。
5.5 现象:加了预测模型后,分配质量反而比贪心还差
预测型分配翻车通常不是模型收敛问题,而是输入特征的时间粒度选错了。用小时级特征做分钟级调度决策,预测结果天然滞后,等于按昨天的天气安排今天穿什么。另外,很多系统把节假日的特征也纳入训练,结果让模型学会了“节假日波动”,但实际上业务节假日行为和普通工作日差异巨大。我的建议是:预测模型上线前先做离线回放,用过去两周的完整历史数据让模型与贪心算法同场竞技。如果预测方案没有显著提升云覆盖度,就别让模型上线。生产环境的稳定性比一点点利用率更值钱。
6. 从分配算法到运维闭环:验证指标与一次只动一个参数
6.1 用历史数据回放验证算法改动
算法改完不敢直接上生产,这是对的。建议先做历史数据回放:取最近一周的生产请求日志,在同一批数据和同一批节点规格下,分别运行旧算法与新算法,对比三个指标——分配成功率、平均云覆盖度、驱逐次数。
| 指标 | 旧算法 | 新算法 | 判断标准 |
|---|---|---|---|
| 分配成功率 | 98.2% | 99.1% | 提升明显 |
| 云覆盖度 | 64% | 71% | 超过 5 个百分点 |
| 驱逐次数 | 12 | 3 | 不能变差 |
回放时要注意,节点初始状态必须一致,不能把第一次运行后的分配状态带到第二次运行里,否则对比结果没有意义。每一步改动都单独跑一轮,混在一起改会让问题彻底黑匣子化。回放通过之后,灰度上线时先从 5% 的流量切起,观察一个完整业务周期再放开。这三步听起来慢,但比线上翻车再回滚痛快得多。
6.2 调优习惯:先修口径,再动权重
我给团队定的调优规矩是:一次只动一个参数,并且每次改动必须记录当时的技术指标。所谓“先修口径”,是指先排查监控数据与调度器账本是否一致、云覆盖度计算是否用了同一个时间窗口,再调整算法权重。口径不对时调权重,等于在错误的数据上做精细雕刻,越调越偏。改动记录也不需要很复杂,一行备注写上改动日期、参数前后值和对应的分配成功率即可。坚持一个月,就会发现很多当时觉得玄学的调度问题,背后其实是某个参数在某个负载形态下被放大的结果。
资源分配算法做到最后,拼的不是花哨的策略,而是对自身系统负载的理解。我现在调整一个权重之前,会先问自己三个问题:这个改动是为了哪个业务场景;影响范围是所有节点还是特定标签节点;回滚条件是什么。把这三个问题想清楚再动手,比反复试参数要可靠得多。希望帮到你。
本文还有配套的精品资源,点击获取