12.4 共享状态调度 (Omega)
一句话
Omega 让多个调度器共享同一份集群状态:各自基于快照做决策,提交时用乐观并发校验,冲突就重试;它把 “串行决策” 换成 “并发 + 冲突消解”,吞吐和延迟大幅改善,代价是冲突率与重试成本。
三种架构的对照
| 架构 | 状态 | 并发 | 冲突 | 吞吐 |
|---|---|---|---|---|
| 单体(Borg) | 调度器独占 | 串行 | 0 | 受单次决策耗时限制 |
| 两层(Mesos) | 分配器持有,offer 传递 | 分配串行、框架并行 | 无冲突,但 offer 过期 | 中 |
| 共享状态(Omega) | 所有调度器共享一份(带版本) | 完全并行 | 有,靠重试 | 高 |
Omega 的关键取舍:用 “偶发冲突重试” 换掉 “全局串行”。冲突是可接受的(重试成本低),而串行是吞吐的硬上限。
实测:K 个调度器并发时的冲突率
模拟 300 节点 × 8 核、2000 个 1 核任务、4 个调度器共享状态、15% 决策基于陈旧快照:
| 架构 | 成功放置 | 冲突/拒绝 |
|---|---|---|
| 单体 | 2000/2000 | 0 |
| 两层(offer) | 1860/2000 | 7.0% 拒绝 |
| 共享状态 K=4 | 1878/2000 | 23.1% 冲突重试 |
23.1% 的冲突听起来高,但冲突的代价只是一次重试(微秒级),而串行的代价是 “每秒只能调度 N 个任务”。只要 “重试成本 << 串行等待”,共享状态就更优——这是 Omega 的核心论证。
有效吞吐 ≈ K / (单次决策耗时 × (1 + 冲突率))
K=4、单次 1ms、冲突 23% → 约 3250 任务/秒;单体同条件约 1000 任务/秒
代码:乐观并发的提交
func (s *Scheduler) TryPlace(task Task, snapshot *ClusterState) (bool, error) {
node, ok := pick(snapshot, task) // 基于快照做决策(可能已过期)
if !ok { return false, ErrNoFit }
// 提交:CAS 校验版本,失败就重试(乐观并发)
return s.store.CompareAndSwap(snapshot.Version, func(st *ClusterState) error {
if !st.Fits(node, task) { return ErrConflict }
st.Place(node, task)
return nil
})
}
要点:“决策” 与 “提交” 分离,决策用快照(无锁、快),提交用 CAS(有锁、短)。冲突只发生在提交阶段,且可以立即重试。
反模式
| 反模式 | 后果 |
|---|---|
| 冲突率高但不限重试 | 重试风暴,调度延迟抖动 |
| 快照太旧(不刷新) | 冲突率飙升,重试占比过高 |
| 提交阶段做重计算 | 临界区变长,CAS 争用上升 |
| 无冲突指标 | 不知道 “重试是否在恶化” |
| 把 “乐观” 用在强一致资源(如 IP/端口分配) | 冲突代价高,重试不一定能成功 |
| 多调度器无 “职责划分” | 所有任务都被所有调度器抢,冲突无谓升高 |
三条铁律
🔑 决策用快照、提交用 CAS;两者必须分离。
🔑 冲突率要监控;它升高说明快照陈旧或临界区变长。
🔑 重试必须有上限与退避,否则冲突会变成风暴。
自检
Q1 共享状态调度为什么能比单体快?
▸ 答案:因为决策并行:K 个调度器各自基于快照独立决策,不需要排队等全局锁;只有提交时用 CAS 做一次短临界区。吞吐近似 K 倍,代价是冲突重试——而重试成本远低于 “串行等待”。
Q2 23.1% 的冲突率算高吗?要不要优化?
▸ 答案:看冲突的绝对成本:若单次重试是微秒级,23% 完全可以接受(总成本 ≈ 23% × 微秒);若重试要重新拉快照、重新打分(毫秒级),就该优化——缩短快照刷新间隔、把 “大任务” 交给专用调度器、或按资源类型划分职责。
Q3 为什么 “快照太旧” 会让冲突率飙升?
▸ 答案:因为多个调度器都基于 “同一个过时视图” 决策:都以为某个节点还有空位,于是同时提交,只有一个成功。快照越旧,这种 “撞车” 越多。所以 Omega 类系统必须高频刷新快照(informer/watch),并接受 “最终一致”。
Q4 哪些资源不适合乐观并发?
▸ 答案:冲突代价高、重试不一定成功的资源:唯一 IP/端口分配、全局唯一 ID、需要 “严格顺序” 的资源。这类应走 “悲观锁/专用分配器”,而不是 “先抢后校验”。
Q5 “按资源类型划分职责” 为什么能降低冲突?
▸ 答案:因为不同调度器管理不同资源(如 CPU 调度器、GPU 调度器),彼此的操作集合不相交,冲突自然减少。代价是 “跨资源类型的任务” 需要协调(如既要 CPU 又要 GPU),这时又回到 “谁先谁后” 的问题。
Q6 共享状态架构对 “状态存储” 有什么要求?
▸ 答案:要求低延迟、高并发读、支持 CAS/版本:读要快(每个决策都读),写要能检测冲突(版本号/事务)。这就是为什么 Omega 用 “带版本的共享状态 + 乐观事务”,而不是 “每次决策都加全局锁”。
Q7 K8s 的调度器属于哪一类?
▸ 答案:默认调度器是单体式(单 leader、串行);K8s 也支持 “多调度器”(不同 schedulerName),但它们通过 API server 的乐观并发(resourceVersion)协作——这已经带上了 Omega 的思想:共享状态 + 冲突检测,只是粒度更粗(整个 Pod 对象)。