3. 资源模型与抽象:资源定义、表示方法与调度策略

各位同学,今天我们来聊聊算力调度器里最基础、也最容易被忽视的部分——资源模型与抽象。说实话,我见过不少调度器项目,功能做得花里胡哨,但底层资源模型没设计好,最后全崩了。嗯,咱们从根上把它理清楚。

3.1 资源定义:CPU、内存、GPU、网络带宽

先问个问题:什么是资源?在调度器眼里,资源就是能被分配、被计量、被回收的东西。我个人习惯把资源分成两类:可压缩资源和不可压缩资源。

  • CPU:可压缩资源。你给一个任务分配1个核,它用不完,别人还能用。超卖风险低。
  • 内存:不可压缩资源。分配了8GB就是8GB,用超了直接OOM Kill。我在项目中遇到过,内存超卖导致整个节点雪崩,那叫一个惨。
  • GPU:特殊资源。显存不可压缩,算力可以一定程度共享。但要注意,GPU的调度粒度很粗,动不动就是整卡分配,浪费严重。
  • 网络带宽:软性资源。很难精确控制,一般用流量整形或QoS来保证。

核心原则:不可压缩资源必须精确记账,可压缩资源可以适当超卖,但要有兜底机制。

3.2 资源表示方法:向量模型 vs 位图模型

资源定义清楚了,怎么表示它?说白了,调度器需要一种数据结构来描述“当前节点还剩多少资源”。我常用的有两种:向量模型和位图模型。

3.2.1 向量模型

把资源看成多维向量。比如一个节点有CPU、内存、GPU三种资源,那它的资源状态就是一个三维向量:[cpu_cores, mem_gb, gpu_count]。

// 向量模型示例
type ResourceVector struct {
    CPU    float64 // 核心数
    Memory float64 // GB
    GPU    int     // 张数
}

// 节点剩余资源
nodeFree := ResourceVector{CPU: 8.0, Memory: 32.0, GPU: 2}

// 任务需求
taskReq := ResourceVector{CPU: 2.0, Memory: 8.0, GPU: 1}

// 能否调度?逐维比较
if nodeFree.CPU >= taskReq.CPU && 
   nodeFree.Memory >= taskReq.Memory && 
   nodeFree.GPU >= taskReq.GPU {
    // 可以调度
}

向量模型的好处是直观、计算快。但有个坑——它假设资源之间是独立的。你想想看,实际场景里CPU和内存是联动的,一个任务要2核CPU,通常也得配4GB内存。我曾经踩过这个坑,只检查单维资源,结果调度出去的任务因为内存不足反复被kill。

我的建议:向量模型适合做快速过滤,但最终决策要结合资源亲和性策略。

3.2.2 位图模型

位图模型更适合细粒度资源,比如CPU核心的分配。每个核心用一个bit表示,0表示空闲,1表示已分配。

// 位图模型示例(16核CPU)
type CPUBitmap uint16

// 初始状态:全部空闲
var freeCPUs CPUBitmap = 0b0000000000000000

// 分配第3核(从0开始计数)
func (b *CPUBitmap) Allocate(core int) {
    *b |= 1 << core
}

// 检查第5核是否空闲
func (b *CPUBitmap) IsFree(core int) bool {
    return (*b & (1 << core)) == 0
}

位图模型在CPU调度里特别有用。我记得在做一个实时调度器时,需要保证关键任务独占某些核心,位图模型让分配和回收都变成了O(1)操作,性能极好。

注意:位图模型不适合内存这种连续大块资源。你总不能按字节去分配内存吧?那调度器自己就先炸了。

3.3 资源预留与超卖

好,资源怎么表示我们搞清楚了。接下来是调度器设计的核心难题:预留和超卖怎么平衡?

3.3.1 资源预留

预留就是“这块资源我预定了,别人别碰”。典型场景:数据库集群需要保证最低资源。

  • 静态预留:启动时就固定好,比如给Kubernetes系统组件预留2核4GB。简单粗暴,但浪费。
  • 动态预留:根据负载动态调整。我做过一个方案,用水位线触发预留调整,效果不错,但实现复杂。

3.3.2 资源超卖

超卖就是“账面上有100核,实际敢卖120核”。为什么敢?因为大部分任务用不满。

但超卖是把双刃剑。我曾经在一个离线计算集群里,把超卖比设到了1:5,结果突发流量一来,所有节点内存爆满,任务大面积失败。那次事故让我学到一个教训:超卖必须有限度,而且要有驱逐机制。

超卖安全公式:
可超卖量 = (节点总资源 - 系统预留) × 超卖系数
超卖系数建议:CPU 1.5~2.0,内存 1.0~1.2(几乎不超卖)

3.4 知识体系总览

下面这张图是我自己总结的资源模型核心逻辑,画成SVG方便大家理解。

资源模型与抽象核心逻辑 资源定义 CPU / 内存 / GPU / 网络 资源表示方法 向量模型 / 位图模型 调度策略 预留 / 超卖 / 驱逐 资源分类 • CPU:可压缩,可超卖 • 内存:不可压缩,精确记账 • GPU:显存不可压缩 • 网络:软性资源,QoS控制 关键:不可压缩资源 是调度瓶颈 表示方法对比 向量模型: [cpu, mem, gpu] 多维比较 适合快速过滤,资源独立假设 位图模型: 每个核心1bit,O(1)分配 适合CPU细粒度调度 不适合连续大块资源 调度策略 预留: 静态/动态预留 保证关键任务 超卖: CPU 1.5~2.0倍 内存几乎不超卖 必须有驱逐机制

3.5 避坑指南

最后,分享几个我亲身踩过的坑:

  • 不要迷信单一模型:向量模型和位图模型各有适用场景。我见过有人非要用位图管理内存,结果代码写得跟天书一样,性能还差。
  • 超卖一定要有上限:我曾经把超卖比设到1:3,想着反正任务用不满。结果某个任务突然峰值,直接把节点打爆了。现在我的原则是:CPU超卖不超过2倍,内存不超过1.2倍。
  • 预留资源要动态调整:静态预留太浪费,动态预留又太复杂。折中方案是用水位线触发调整,比如内存使用率超过80%时,自动释放部分预留。

一句话总结:资源模型是调度器的地基。地基没打好,上面盖多高的楼都得塌。向量模型做快速过滤,位图模型做细粒度分配,预留和超卖要平衡好,别贪心。

好了,这一章的内容就到这里。资源模型这块,大家有什么问题可以随时交流。记住,调度器的核心就是“分好资源”,分不好,再好的算法也是白搭。

想要更多资料,联系可可