
Volcano 资源比较机制设计解析多维度资源比较的语义、defaultValue 机制与实现落地【免费下载链接】volcanoA Cloud Native Batch System (Project under CNCF)项目地址: https://gitcode.com/GitHub_Trending/vol/volcano本文基于 Volcano 调度器的设计文档 resource-comparison.md系统讲解 Volcano 调度器中资源比较函数的设计动机、九个推荐比较函数的语义矩阵、defaultValue缺失维度按零或无穷处理机制并结合 pkg/scheduler/api/resource_info.go 的源码实现与 allocate、preempt、reclaim、proportion 等插件的真实调用点帮助读者理解调度器如何在多资源维度CPU、内存、GPU 等标量资源下正确判断资源是否足够避免 proportion、preempt 等场景中反复出现的比较类缺陷。背景与问题资源比较函数的两大根因在 Volcano 的调度流程中大量逻辑依赖两个资源向量的比较节点空闲资源能否满足任务请求、队列已分配量是否超过应得量deserved、高优作业能否从低优作业处抢占/回收资源等。设计文档在复盘 proportion 插件与 preempt action 的相关缺陷时发现大多数问题的根源集中在两处根因一对缺失维度缺少考虑。两个资源列表的维度常常不完全相同。例如L {cpu:1c, memory:1G}、R {cpu:2c, memory:2G, gpu:2}L没有gpu维度。此时缺失维度的默认取值可以理解为zero或infinity两种语义默认值取zeroL等价于{cpu:1c, memory:1G, gpu:0}此时各维度上L均小于R可以认为L R默认值取infinityL等价于{cpu:1c, memory:1G, gpu:max}gpu维度上L大于R因此不能认为L R但L仍然在部分维度上小于R。设计文档指出改造前的资源比较函数默认一律把缺失维度当作zero处理这在不同语义的场景中会得出相反的结论。根因二资源比较函数覆盖不全。原有函数无法覆盖全部比较场景导致后续开发者在关键位置误用。文档给出的典型例子是判断节点空闲资源能否满足任务请求——正确语义是任一维度的资源量不满足请求节点即不可用代码应写成if node.idle.LessPartly(task.request) { break }而不是用全维度小于语义的函数去判断。为此设计文档梳理了所有资源比较场景给出了完整的函数族设计并已在当前仓库中落地。推荐函数矩阵九个比较函数与转换关系设计文档给出的推荐函数表如下完整继承自 docs/design/resource-comparison.md函数名语义示例原始函数使用方转换关系l.Less(r, defaultValue)l的所有维度值均小于rL{cpu:1c, memory:2G} R{cpu:2c, memory:4G}Less(rr)proportion独立实现l.LessEqual(r, defaultValue)l的所有维度值均小于或等于rL{cpu:1c, memory:2G} R{cpu:1c, memory:4G}LessEqual(rr)/LessEqualStrict(rr)allocate/preempt/reclaim/overcommit/proportion/reservation/topology独立实现l.LessPartly(r, defaultValue)l的部分维度值小于rL{cpu:4c, memory:2G} | R{cpu:2c, memory:4G}无topology独立实现l.LessEqualPartly(r, defaultValue)l的部分维度值小于或等于rL{cpu:4c, memory:2G} | R{cpu:2c, memory:2G}无无独立实现l.Equal(r, defaultValue)l全维度等于r且r全维度等于lL{cpu:1c, memory:2G} R{cpu:1c, memory:2G}无无独立实现l.Greater(r, defaultValue)l的所有维度值均大于rL{cpu:2c, memory:4G} R{cpu:1c, memory:2G}无无!l.LessEqualPartly(r, defaultValue)l.GreaterEqual(r, defaultValue)l的所有维度值均大于或等于rL{cpu:2c, memory:4G} R{cpu:2c, memory:2G}无无!l.LessPartly(r, defaultValue)l.GreaterPartly(r, defaultValue)l的部分维度值大于rL{cpu:4c, memory:2G} | R{cpu:2c, memory:4G}无无!l.LessEqual(r, defaultValue)l.GreaterEqualPartly(r, defaultValue)l的部分维度值大于或等于rL{cpu:2c, memory:2G} | R{cpu:2c, memory:4G}无无!l.Less(r, defaultValue)表中的|、|、|、|是设计文档自定义的数学符号表示部分维度满足关系。文档还说明Part 系列函数之间存在语义重叠但在具体应用中有其意义设计当时 Part 系列函数尚未被任何插件使用是面向后续扩展的预定义。从转换关系可以看出设计思想以 Less 家族为基元Greater 家族通过取反派生例如Greater !LessEqualPartly从而用最小的一组函数覆盖全序关系的九种组合避免为每种关系单独维护一套缺失维度处理逻辑。defaultValue 参数缺失维度按 zero 还是 infinity 处理推荐函数统一携带defaultValue参数用于指定 L、R 中空白维度应取什么值取值只能是zero或infinity之一。源码实现类型化的 DimensionDefaultValue在 pkg/scheduler/api/resource_info.go 中该语义由专门的枚举类型承载// DimensionDefaultValue means default value for black resource dimension type DimensionDefaultValue int const ( // Zero means resource dimension not defined will be treated as zero Zero DimensionDefaultValue 0 // Infinity means resource dimension not defined will be treated as infinity Infinity DimensionDefaultValue -1 )可以看到设计文档中提议的defaultValue stringzero/infinity 字符串在最终实现中被类型化为DimensionDefaultValue整型枚举从源码结构看这是为了避免裸字符串传参带来的拼写错误与隐式比较问题。Resource结构本身将资源分为固定维度与动态维度pkg/scheduler/api/resource_info.gotype Resource struct { MilliCPU float64 Memory float64 // ScalarResources ScalarResources map[v1.ResourceName]float64 // MaxTaskNum is only used by predicates; ... MaxTaskNum int }CPU 与内存是固定字段GPU、扩展资源、临时存储等动态维度放在ScalarResourcesmap 中——缺失维度正是指 map 中不存在的 key这正是defaultValue要解决的问题。defaultValue 在不同函数中的行为差异对比 Less 实现 与 LessPartly 实现 可以发现defaultValue并非一个全局开关而是与各函数的语义耦合的在Less/LessEqual全维度语义中当defaultValue Infinity时只要右向量rr中存在左向量r缺失的标量维度立即返回false——因为该缺失维度在左向量中被视为无穷大不可能小于/小于等于右向量的有限值。而遍历r自身存在的维度时若rr中查不到该维度且defaultValue Infinity则continue跳过视为相等方向不产生否决。在LessPartly/LessEqualPartly部分维度语义中当defaultValue Zero时只要rr存在r缺失的标量维度立即返回true——因为r的该维度按 0 处理必然小于rr的正值。这两种分支正是zero 与 infinity 两种默认语义在代码层面的直接体现也是修复文档中根因一的关键。另外值得注意的是LessEqual的容差设计pkg/scheduler/api/resource_info.golessEqualFunc : func(l, r, diff float64) bool { if l r || math.Abs(l-r) diff { return true } return false }比较使用minResource定义为0.1见 pkg/scheduler/api/resource_info.go作为浮点容差。从源码结构看这与设计文档中原始函数列里的LessEqual/LessEqualStrict两个函数的合并相呼应带容差的LessEqual吸收了过去宽松比较的语义而严格比较的场景则通过Less或按维度取值Get/ResourceNames来保证。三个典型案例defaultValue 对比较结果的影响设计文档给出了三组对照案例完整继承如下可直接用于理解两种默认语义的差异。案例一L {cpu: 1c, memory:1G}R {cpu: 2c, memory:2G, gpu:2}L 缺少 gpu 维度LRdefaultValue zero{cpu: 1c, memory:1G, gpu:0}{cpu: 2c, memory:2G, gpu:2}defaultValue infinity{cpu: 1c, memory:1G, gpu:max}{cpu: 2c, memory:2G, gpu:2}LessLessEqualLessPartlyLessEqualPartlydefaultValue zerotruetruetruetruedefaultValue infinityfalsefalsetruetrue案例二L {cpu: 1c, memory:1G, gpu: 1}R {cpu: 2c, memory:2G}R 缺少 gpu 维度LRdefaultValue zero{cpu: 1c, memory:1G, gpu:1}{cpu: 2c, memory:2G, gpu:0}defaultValue infinity{cpu: 1c, memory:1G, gpu:1}{cpu: 2c, memory:2G, gpu:max}LessLessEqualLessPartlyLessEqualPartlydefaultValue zerofalsefalsetruetruedefaultValue infinitytruetruetruetrue案例三L {cpu: 1c, memory:1G}R {gpu: 2}两者维度完全不重叠LRdefaultValue zero{cpu: 1c, memory:1G, gpu:0}{cpu: 0c, memory:0G, gpu:2}defaultValue infinity{cpu: 1c, memory:1G, gpu:max}{cpu: max c, memory: max G, gpu:2}LessLessEqualLessPartlyLessEqualPartlydefaultValue zerofalsefalsetruetruedefaultValue infinityfalsefalsetruetrue三组案例放在一起可以看到规律缺失维度的默认语义只影响全维度函数Less/LessEqual的结论方向案例一中 zero 为 true、infinity 为 false案例二恰好相反而 Part 系列函数只关心是否存在一个维度满足关系因此在这些案例中结论稳定为 true。这也是文档中注释Part scenarios are overlapped in part functions, but it makes sense when deal with specific applications的含义。源码实现纵深函数如何被实现与相互转换除前文分析的Less/LessEqual/LessPartly/LessEqualPartly外当前实现还包含设计表中其余函数Equal全维度相等判断同样带minResource容差GreaterPartly按设计表的转换关系实现直接复用LessEqualWithResourcesName并取反还额外返回超出维度的资源名列表便于上层输出哪些资源超了的可读信息LessEqualWithResourcesNamepkg/scheduler/api/resource_info.goLessEqual的带诊断变体返回不足的资源名切片供 reclaim/抢占等逻辑生成人类可读的原因。一个能体现文档修复误用的实例是资源减法。Sub 的实现 在相减前先断言func (r *Resource) Sub(rr *Resource) *Resource { assert.Assertf(rr.LessEqual(r, Zero), resource is not sufficient to do operation: %v sub %v, r, rr) return r.sub(rr) }即被减数在每一个维度上都足够缺失维度按 0 语义比较才允许相减这正是设计文档根因二所述函数误用问题被收敛后的表现——关键路径上明确声明了比较语义而不是隐式依赖旧函数的默认行为。此外 Diff 实现 也会根据defaultValue为缺失维度填充默认值后再求差Infinity 场景下差值以特殊标记表示进一步印证defaultValue是贯穿比较、求差、缩放MinDimensionResource整套运算的统一语义。调用点实战哪些 action 与插件在用哪些函数设计文档的Used plugins/actions一列标注了LessEqual被 allocate/preempt/reclaim/overcommit/proportion/reservation/topology 使用。当前仓库源码可以逐一印证这些调用点且全部显式传入api.Zero作为defaultValue位置调用语义pkg/scheduler/actions/allocate/allocate.gotask.InitResreq.LessEqual(n.Idle, api.Zero)节点空闲资源任一维度能否满足任务初始请求pkg/scheduler/actions/preempt/preempt.gopreemptor.InitResreq.LessEqual(node.FutureIdle(), api.Zero)抢占后节点的未来空闲能否容纳抢占者pkg/scheduler/actions/reclaim/reclaim.goresreq.LessEqual(availableResources, api.Zero)回收得到的资源能否满足请求pkg/scheduler/plugins/proportion/proportion.goattr.request.LessEqual(attr.deserved, api.Zero)队列请求量是否未超过应得量pkg/scheduler/plugins/capacity/capacity.goattr.deserved.LessPartly(totalDeserved, api.Zero)deserved 与总额的部分维度比较队列水位判断pkg/scheduler/plugins/task-topology/topology.gomaxResource.LessPartly(req, api.Zero)桶内剩余资源是否在某维度上小于任务请求pkg/scheduler/plugins/network-topology-aware/network_topology_aware.gominResource.LessEqual(hnResourceStatus.idle, api.Zero)拓扑层级聚合空闲能否满足最小资源两点值得注意调用方全部显式声明 defaultValue。改造前缺失维度默认按 zero 处理是隐式的改造后每个调用点都必须写明api.Zero或api.Infinity语义被提到阳光下这正是设计文档make it clear for later developers的目标。文档预言的 Part 系列函数已在当前代码中被启用。设计文档写作时LessPartly仅 topology 使用、其余 Part 函数暂无使用方而当前仓库中LessPartly已用于 capacity 插件比较 deserved/guarantee 与总额的水位和 task-topology 插件与设计表一致并有所扩展印证了预定义 Part 函数供后续使用的设计意图。测试验证两种默认语义的行为被用例固定pkg/scheduler/api/resource_info_test.go 对同一组资源用例分别以两种默认值驱动比较函数将设计表中的布尔矩阵固化为回归保障TestLess 相关用例同一组 L/R 分别调用test.resource1.Less(test.resource2, Zero)与Less(test.resource2, Infinity)验证缺失维度两种语义下结论不同TestLessEqual 相关用例同样以Zero与Infinity双路调用LessEqual断言。这类同输入、双默认值的测试结构与上文三个案例的表格形式一一对应是阅读该设计文档时最有价值的交叉验证材料。总结如何选择正确的比较函数结合设计文档与源码实现可以在调度器开发中按如下原则选函数判断资源 A 能否完全容纳请求 B节点空闲 vs 任务请求、队列 allocated vs deserved 等使用A.LessEqual(B, Zero)——任一维度不足即不满足缺失维度按 0 处理这也是 allocate/preempt/reclaim 等 action 的统一写法。判断A 是否在某个维度上小于/不足 B节点不可用早停、队列水位使用A.LessPartly(B, Zero)——文档中if node.idle.LessPartly(task.request) { break }即属此类。缺失维度无该资源应视为 0 还是无穷取决于业务语义资源请求/持有量比较一般选Zero而当缺失维度表示未声明即不受限的场景如某些额度/上限比较从Less/LessEqual的 Infinity 分支行为看设计已为此预留了完整的处理路径。需要可诊断输出时优先选用带资源名返回的变体LessEqualWithResourcesName、GreaterPartly可直接生成哪些资源超出/不足的调度原因信息。总体而言Volcano 的资源比较机制以九个函数 显式 defaultValue为骨架把原本散落在各插件中的隐式比较语义收敛为可枚举、可测试、可诊断的函数族docs/design/resource-comparison.md 中的设计矩阵与 pkg/scheduler/api/resource_info.go 的实现、各 action 插件的调用点形成了完整的设计—实现—调用闭环是理解 Volcano 调度器资源语义的必读材料。【免费下载链接】volcanoA Cloud Native Batch System (Project under CNCF)项目地址: https://gitcode.com/GitHub_Trending/vol/volcano创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考