Meta 用开源内核调度器解决广告服务延迟退化

日期:2026-07-16
主题:Meta 用开源内核调度器解决广告服务延迟退化
原文Meta 用开源内核调度器解决广告服务延迟退化
来源:Engineering at Meta
领域:⚙️ 操作系统/内核


背景

在 Meta 的广告投放系统中,每次广告请求涉及数百个微服务在毫秒级窗口内完成匹配、竞价、排序和计费。这意味着即便 1 毫秒的延迟退化,乘以全球数十亿次日请求量,都可能带来显著的收入损失和用户体验下降。

2025 年底,一次常规的 Linux 内核升级在 Meta 广告服务集群中意外触发了延迟退化。按传统做法,Meta 要么回退内核版本(失去安全更新),要么等待上游修复(可能数周),要么自己打补丁嵌入调度逻辑。但这次他们走了一条完全不同的路——用 sched_ext,一个基于 BPF 的可扩展内核调度框架,在不修改内核源码的前提下,为广告投放工作负载定制了一套专属的调度策略。

核心内容

问题:为什么内核升级会"误伤"广告服务?

Meta 的广告服务集群承载着一种高度混合的负载模式:既有毫秒级延迟敏感的在线竞价请求(必须在前端实时完成),又有大规模离线的广告召回和特征工程管道。通用的 CFS(完全公平调度器)在设计上以"公平分配 CPU 时间"为原则,对这类混合负载没有感知——在线请求和离线管道在调度器眼中只是两个同等优先级的任务。

当新的内核版本调整了 CFS 的调度参数(如唤醒抢占策略、组调度时间片的分配比例)后,离线批处理任务开始更频繁地抢占在线广告请求的 CPU 时间片,导致 p99 延迟出现不可接受的退化。

方案:sched_ext——可编程的调度器

sched_ext 是 Linux 内核 6.12 合入上游的 BPF 可扩展调度框架,它的核心思想很简单:把调度策略的决定逻辑从内核固定算法搬到 BPF 程序里

传统上写一个自定义内核调度器意味着:

  1. 修改内核 kernel/sched/ 下的 C 代码
  2. 重新编译整个内核
  3. 部署新内核到所有服务器
  4. 出了问题,回退又是一个完整流程

而 sched_ext 的思路完全不一样。它在内核中保留了一个最小的"调度分派层"(dispatch layer),而真正的策略逻辑——"下一个该跑哪个任务"——通过 BPF 程序在用户空间编写,动态加载到内核。这意味着你可以像写一个 eBPF 观测工具一样写调度策略,然后 bpftool 加载,即刻生效,无需重启内核。

实现:Meta 的广告调度策略

Meta 团队基于 sched_ext 构建了一个名为 ads_sched 的 BPF 调度程序,核心设计包括:

负载感知分类。在调度器的 select_cpuenqueue 回调中,ads_sched 通过 BPF map 维护了一个实时分类器,将任务分为三类:在线广告请求(延迟敏感)、离线批处理(吞吐优先)、以及系统管理任务(后台稳定)。分类依据是 cgroup 层级和任务的调度行为特征——无需业务代码打标,纯内核视角自动识别。

优先级抢占控制。当在线类任务就绪时,ads_sched 会在同一个核心上立即抢占正在运行的离线任务。而当离线批处理队列中只有一个任务时,调度器会让它运行尽可能长的时间片以提升缓存亲和性。这种"弹性抢占"策略——根据负载类型自适应调整抢占边界——是 CFS 做不到的。

NUMA 感知的负载均衡。Meta 的广告服务器是双路/四路 NUMA 架构。ads_sched 的 balance 回调中实现了"在线任务优先本地分配、离线任务可跨 NUMA 迁移"的策略,避免了在线请求跨 NUMA 访问内存带来的额外延迟。

成果与开源

通过 ads_sched,Meta 成功规避了内核升级带来的延迟退化,在线广告请求的 p99 延迟恢复到升级前的基线水平,并且离线批处理的整体吞吐仅下降了不到百分之三,远低于预期损耗。

更重要的是,Meta 将 ads_sched 的策略代码以开源形式贡献给了 sched_ext 社区。这不是一个 Meta 内部的专有补丁——它是一段不到 300 行的 BPF C 代码,跑在主线内核的 sched_ext 框架之上,任何有类似混合负载场景的团队都可以参考或直接复用。

更深层的工程启示

这次实践的意义远超解决一个具体的延迟问题。它验证了一个新的范式:内核调度不再是操作系统的黑盒,而是可以通过 BPF 进行编程的应用层可观测扩展

在传统架构中,应用团队和内核团队之间隔着一堵墙——应用开发者不知道内核怎么调度自己的任务,内核开发者也不了解应用的工作负载特征。sched_ext 本质上提供了一座桥梁:应用团队可以用自己熟悉的 BPF 工具链(bpftracelibbpf、甚至 AI 辅助生成的 BPF 代码)来编写"懂业务"的内核调度策略。

这对大规模基础设施团队来说是一个重要的信号。当 Cloudflare 用 BPF 做包处理、Cisco 用 BPF 做运行时安全防护、Meta 用 BPF 做内核调度时,我们正在看到一个趋势:BPF 正在从一个观测工具演变为内核的可编程接口层

总结与展望

Meta 用 sched_ext 解决广告服务延迟退化,不是一个孤立的性能优化案例——它展示了"应用感知的内核调度"从实验室走向超大规模生产的可行性。当 BPF 让内核变得可编程,操作系统调度器不再需要是一个"一刀切"的通用策略。未来我们可能会看到更多团队为自己的工作负载编写专属调度器,就像今天大家为自己的服务写配置文件一样自然。