4. 默认调度队列(PriorityQueue)原理:ActiveQ、BackoffQ、UnschedulableQ 三队列模型
Kubernetes 默认调度器(kube-scheduler)的核心调度队列实现为 PriorityQueue。它并非单一队列,而是一个由三个子队列组成的多级排队系统,用于管理待调度 Pod 的生命周期状态,实现高效的调度循环与重试机制。
4.1 三队列模型与职责
PriorityQueue 内部维护三个独立的队列,分别对应 Pod 调度的不同阶段:
| 队列名称 | 数据结构 | 核心职责 | Pod 来源 |
|---|---|---|---|
| ActiveQ | 堆(Heap) | 存放等待调度的 Pod,按优先级排序 | 新 Pod、BackoffQ 到期、UnschedulableQ 重试 |
| BackoffQ | 堆(Heap) | 存放调度失败但处于退避期的 Pod | 调度失败且未超过最大重试次数 |
| UnschedulableQ | Map + 有序集合 | 存放因资源不足/节点不可调度而失败的 Pod | 调度失败且不可调度(非临时错误) |
关键设计原则:三队列模型将"等待调度"、"退避重试"、"等待资源变化"三种状态物理隔离,避免低优先级 Pod 阻塞高优先级 Pod,同时防止频繁失败的 Pod 消耗调度器 CPU。
4.2 队列数据结构详解
4.2.1 ActiveQ:基于堆的优先级队列
ActiveQ 使用 heap.Interface 实现,底层为数组。排序依据为 Pod 的 优先级(Priority) 和 创建时间戳。高优先级 Pod 始终排在低优先级 Pod 之前;同优先级下,先创建的 Pod 先出队。
// 核心排序逻辑(简化)
func less(a, b *QueuedPodInfo) bool {
// 1. 比较优先级(数值越大优先级越高)
if a.Pod.Spec.Priority != b.Pod.Spec.Priority {
return a.Pod.Spec.Priority > b.Pod.Spec.Priority
}
// 2. 同优先级比较时间戳
return a.Timestamp.Before(b.Timestamp)
}
4.2.2 BackoffQ:带退避时间的堆
BackoffQ 同样基于堆实现,但排序依据为 退避到期时间(BackoffTime)。Pod 每次调度失败后,退避时间按指数增长(如 1s → 2s → 4s → 8s),直到达到上限(默认 60s)。到期时间最早的 Pod 最先出队。
4.2.3 UnschedulableQ:Map + 有序集合
UnschedulableQ 采用 双索引结构:
- Map(podKey → podInfo):提供 O(1) 的查找与删除能力,用于快速判断 Pod 是否在队列中。
- 有序集合(按添加时间排序):用于周期性遍历,将可能已满足条件的 Pod 移回 ActiveQ。
性能优化点:UnschedulableQ 使用 Map 而非堆,因为该队列不需要按优先级排序——Pod 在此等待的是集群资源变化事件,而非调度顺序。
4.3 队列操作:Add、Pop、AddUnschedulableIfPresent
PriorityQueue 对外暴露三个核心操作,构成调度循环的完整闭环:
4.3.1 Add:将 Pod 加入 ActiveQ
当新 Pod 创建或 Pod 从其他队列转移至 ActiveQ 时调用:
- 检查 Pod 是否已在 ActiveQ 或 BackoffQ 中(防止重复入队)。
- 若 Pod 在 UnschedulableQ 中,先将其删除。
- 将 Pod 包装为
QueuedPodInfo,设置时间戳,压入 ActiveQ 堆。 - 触发条件变量通知调度器主循环。
4.3.2 Pop:从 ActiveQ 取出最高优先级 Pod
调度器主循环通过 Pop 获取下一个待调度 Pod:
- 若 ActiveQ 为空,阻塞等待条件变量通知。
- 从堆顶取出优先级最高的 Pod。
- 返回 Pod 信息,调度器开始执行调度算法。
// Pop 核心逻辑(简化)
func (p *PriorityQueue) Pop() (*QueuedPodInfo, error) {
p.lock.Lock()
defer p.lock.Unlock()
for p.activeQ.Len() == 0 {
p.cond.Wait() // 队列为空时阻塞
}
obj, _ := heap.Pop(&p.activeQ)
podInfo := obj.(*QueuedPodInfo)
return podInfo, nil
}
4.3.3 AddUnschedulableIfPresent:将调度失败的 Pod 加入 UnschedulableQ
当调度算法返回"不可调度"错误时调用:
- 检查 Pod 是否仍在 ActiveQ 中(防止并发问题)。
- 若 Pod 已不在 ActiveQ,直接返回(可能已被其他协程处理)。
- 将 Pod 从 ActiveQ 移除,加入 UnschedulableQ。
- 记录失败原因和添加时间,供后续重试判断。
避坑指南:AddUnschedulableIfPresent 必须检查 Pod 是否仍在 ActiveQ 中。若忽略此检查,可能导致 Pod 被重复加入 UnschedulableQ,造成内存泄漏或调度死循环。
4.4 三队列流转关系
下图展示了 Pod 在三个队列之间的完整生命周期流转:
4.5 队列操作的时间复杂度对比
| 操作 | ActiveQ | BackoffQ | UnschedulableQ |
|---|---|---|---|
| Add | O(log n) | O(log n) | O(1) |
| Pop | O(log n) | O(log n) | N/A(无 Pop 操作) |
| 查找 | O(n) | O(n) | O(1) |
| 删除 | O(log n) | O(log n) | O(1) |
工程启示:UnschedulableQ 的 O(1) 查找能力使其非常适合处理大量不可调度 Pod 的场景。当集群规模达到数千节点时,UnschedulableQ 的 Map 结构可避免因遍历堆而导致的性能瓶颈。
4.6 三队列模型的工程价值
- 优先级保障:ActiveQ 的堆结构确保高优先级 Pod 始终优先调度,不受低优先级 Pod 重试影响。
- 退避保护:BackoffQ 防止频繁失败的 Pod 反复触发调度算法,降低 CPU 消耗。
- 事件驱动:UnschedulableQ 结合集群资源变化事件,避免无效轮询,提升调度效率。
- 内存安全:三队列独立管理,Pod 不会在多个队列中重复存在,防止内存泄漏。