为什么混部总是“看起来很美”?
批处理和在线服务混部,核心矛盾不是资源不够,而是 interference(干扰)在线服务的延迟和批处理任务的吞吐,在同一个物理机上天然互斥。 想把利用率从 30% 拉到 70%,靠的不是把容器都塞到一个宿主机里,而是把“时间片”、“内存带宽”、“CPU 缓存”这些底层资源的手术刀动好,业内专家指出,混部失败的案例,十有八九是只做了“部署混部”,没做“资源隔离”。
混部的前提:先分清谁有资格“搭车”
不是所有批处理任务都适合和在线服务住在一起,GPU 训练、全量离线日志分析,这些“硬骨头”任务要么长期占满核心,要么突发巨量 IO,混进来就是灾难。
真正的候选者,是所谓的 “弹性空闲” 作业,它们的特征非常明确:
- 可被抢占:任务中途挂掉,重启就能继续,比如数据管道中的增量计算、Spark 的 shuffle 阶段。
- 资源使用有波峰:白天在线服务忙,批处理资源让路;凌晨在线服务空闲,批处理火力全开。
- 对绝对时延不敏感:跑完就行,早 5 分钟晚 5 分钟没区别。
行业共识认为,判断标准只需一条:如果你的批处理任务对“端到端完成时间”有硬性 SLA,那就别混。
核心机制:CPU 资源的“软隔离”与“硬抢占”
真正的混部治理,是在内核层面打仗,线上应用最怕的是 CPU 争抢导致的调度延迟,批处理任务最怕的是被饿死,这里面有几个具体可操作的动作。
内核参数调整:让 CPU 的“手”别乱伸
在基于 Kubernetes 的环境里,单纯用 requests 和 limits 控制 CPU 配额,在混部场景下就是摆设,因为 CFS 调度器默认是“按权重分时间片”,当批处理任务来临时,在线服务的延迟会被拉高。
操作路径是调整 cpu.cfs_quota_us 和 cpu.cfs_period_us,但更有效的是启用 group identity 功能(高版本内核特性)。它的核心逻辑是给 cgroup 设置优先级带宽,在线任务被唤醒时,可以抢占正在运行的批处理任务的 CPU 时间片
,这个抢占过程是微秒级的,对于批处理任务来说,只是多等了一个调度周期。
拓扑感知:把批处理赶去“偏远地区”
x86 架构下,NUMA(非统一内存访问)拓扑是最难的坎,在线服务通常跑在 Node 0 的物理核上,配合专属内存通道,混部的批处理任务,应该被绑定到 Node 1 或 Node 2 上。
具体操作是在 Pod 的 YAML 里设置:
cpuManagerPolicy: static- 配合
topologyManagerPolicy: single-numa-node
这样能让在线服务的 CPU 尽量固定在一个 NUMA 节点,批处理任务在另一个节点,虽然有 Lunderground 的 cache 争抢,但比全乱跑强好几个数量级。
内存与网络:被忽视的延迟杀手
很多人盯着 CPU 看,却忘了内存带宽和 Page Cache 的争抢,在线服务的性能突然劣化,往往是批处理任务在做大文件读取,把内存带宽打满了。
内存带宽控制
在 Intel 平台上,可以通过 pqos 工具(Intel CAT 技术)为不同的 cgroup 分配 L3 缓存和内存带宽。
操作如下:
- 查看支持情况:
pqos -s - 创建监控组:
pqos -e "llc:0=0x0000FF;llc:1=0xFFFF00"(给在线任务的 L3 分配 0-7 区,批处理分 8-15 区) - 绑核执行:让在线任务跑在 cpu 0-7,批处理任务跑在 cpu 8-15
这就相当于用硬件指令把 CPU 三级缓存“挖了条沟”,物理上隔离,谁也别想污染谁。
网络收发包的独立线程
在线服务最怕 softirq(软中断)堆积,如果批处理任务的网卡流量太大,软中断会占满整个 CPU 核,在线服务的响应时间会直接飙红,建议方案是给网卡开 RPS(Receive Packet Steering)并绑核,确保在线服务的收包队列锁死在自己的核上,批处理任务的包走另外的队列,如果条件允许,用两张物理网卡分开管理网络路径,一张跑在线流量,一张跑批处理数据管道。
生命周期治理:谁说了算?
混部治理不是把 Pod 调度下去就完了,真正的挑战在于 “突发流量来了,谁走?”
这是一个典型的动态优先级问题,效率优先的调度策略会让机器跑满,但金融支付、游戏对战等在线服务的 QPS 波动,是没法提前预测的,治理动作必须落到
“驱逐机制” 上:
- 指标采集:要采集至少 3 个月的历史数据作为基线,只看 24 小时数据没意义,特别是电商大促或月末结算那几天。
- 驱逐阈值:当在线服务的 P99 延迟上升 超过基线的 3 倍 时,立即触发批处理任务的原地暂停(freeze,而不是 kill)。
- 优雅缩容:批处理任务必须支持 checkpoint(检查点),Redis 有 RDB,Spark 有 shuffle 文件。没有 checkpoint 机制的任务,本质上不具备混部资格。
这里有一个关键动作:驱逐的粒度是 “调度单元” 而非“工作节点”,比如在 Kubernetes 中,驱逐一个 Deployment 扩容出来的 Pod,比删掉整个 Job 要安全得多。
混部实施路径:从一台机器开始的灰度观测
千万别做“全量切换”,混部治理的试错成本极高,一次 OOM(内存溢出)或 CPU 饥饿,就能把在线服务的可用性打穿。
第一阶段:同集群,但不同机器
先不物理混布,只在同一个 Kubernetes 集群里,将批处理任务调度到非核心应用所在的机器上,这是为了验证 “资源配额是否充足”,而不是验证“干扰控制”。
第二阶段:单机混部,只放非核心服务
选一台承载非核心在线服务的机器(比如内部 BI 系统、报表服务),在这台机器上,开启 20% 算力的批处理任务,观察指标:
- 在线服务的
sys态 CPU 是否下降。 - 系统整体的
runqueue latency(运行队列延迟) 是否超过 50ms。 - context switch(上下文切换)是否突然增多。
第三阶段:核心业务的流量染色
当测试机器稳定运行 2 周后,把核心在线服务的 1% 流量 拉过来测试,这里最关键的监控指标不是 CPU 使用率,而是跨 NUMA 访问的比例,如果跨 NUMA 内存访问占比超过 15%,说明拓扑亲和性没做好,需要回去调整绑核。
量化收益与成本:混都混了,到底图什么?
混部治理的 ROI 计算不是看毛利率,而是看 “资源冗余压缩”,假设一个集群有 100 台物理机,纯在线服务跑在 100 台机器上,峰值利用率 30%,混部后,将所有在线服务压缩到 70 台机器上,剩余 30 台专门跑批处理。
此时的数据对比是:
| 指标 | 纯在线集群 | 混部集群 | 变化 |
|---|---|---|---|
| 在线服务平均利用率 | 30% | 55% | 提升 83% |
| P99 延迟劣化 | 基准 | +5% 以内 | 可接受 |
| 批处理任务完成时间 | 无变化 | 拉长 40% | 无 SLA 痛点 |
| 每核成本 | 100% | 70% | 下降 30% |
另一个容易被忽略的成本是 “机器维护时间窗”,混部后,在线服务腾挪出来的机器,可以专门做内核升级、硬件维修,而不用再考虑业务迁移,运维复杂度反而降低了。
常见问答:百度搜混部治理,逃不开这几个问题
问:Kubernetes 原生调度器能直接做混部吗?
原生调度器不能,它只负责“调度”,不负责“运行时的动态驱逐和恢复”,需要配合 Descheduler(主动驱逐策略)以及云厂商的 VPA(垂直扩缩容)才能形成闭环,纯靠原生的 ResourceQuota 限制配额,会导致大量 CPU 碎片,批处理任务根本塞不进去。
问:混部后经常出现离线任务内存暴涨,怎么快速止血?
给批处理任务打 cgroup 的 memory.high 水印,不要只设置 memory.limit(硬限制),因为硬限制会触发 OOM Killer 杀任务,设置 memory.high 后,当批处理任务内存超过阈值,内核会回收其 Page Cache 和脏页,而不是直接杀死进程,如果是计算密集型,建议加 swappiness=0,避免 swap 换入换出影响在线应用。始终直接对问题现象采取操作,而不是反复调整 requests 和 limits 的空泛配置。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/641518.html





