用 sched_ext 为广告服务定制 Linux 调度策略:从内核升级风险到毫秒级延迟治理

2026-07-14 44 预计阅读时间: 1 分钟
来源: engineering.fb.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:9 分钟

在大规模广告投放系统中,几毫秒并不是可以忽略的噪声。请求往往要经过召回、排序、预算控制和竞价等多个阶段,调度延迟一旦出现在高分位,就可能直接影响广告服务的吞吐、超时率和业务效果。Meta 面临 Linux 内核升级可能引入延迟回退的问题时,选择了上游 Linux 的 sched_ext:一个基于 BPF 的可扩展调度框架,用它构建适合广告投放工作负载的调度策略。

问题不只是平均延迟

广告服务通常同时运行大量短生命周期、CPU 密集且带有明确截止时间的任务。对这类系统而言,平均延迟可能仍然稳定,但 P99 或 P99.9 已经恶化。

一次内核升级可能改变许多调度细节,例如:

  • 可运行线程被分配到 CPU 的顺序;
  • 交互式短任务与长期计算任务之间的竞争关系;
  • CPU 空闲后的任务唤醒与迁移行为;
  • NUMA、缓存局部性和负载均衡带来的额外成本;
  • 高并发下运行队列增长时的尾部等待时间。

通用调度器需要兼顾桌面、数据库、批处理和容器平台等多种场景,不可能天然理解广告请求的服务等级目标。sched_ext 的价值在于,它允许工程团队通过 BPF 实现调度策略,同时继续使用 Linux 内核提供的运行环境和安全边界,而不必长期维护一套侵入式内核补丁。

这里需要注意一个边界:来源摘要说明 Meta 为广告投放构建了定制策略,但没有披露完整算法、队列结构或生产参数。因此,下面的命令是可以这样实践的验证流程,并不代表 Meta 的具体实现。

sched_ext 改变了调度策略的交付方式

传统的调度器实验通常意味着修改内核、重新编译、部署并重启机器。这样的反馈周期较长,生产回滚也更重。sched_ext 将策略的一部分放进 BPF 调度器中,使团队能够围绕特定工作负载迭代任务选择、CPU 分配和队列管理逻辑。

对延迟敏感服务来说,这带来几个实际变化:

  1. 策略可以针对工作负载设计。 调度器可以围绕短任务、延迟敏感线程或服务分组设计队列规则。
  2. 实验范围更容易控制。 可以先在测试机、影子流量或小规模主机池中加载策略,再逐步扩大覆盖面。
  3. 失败处理更加重要。 BPF 验证器能拦截一部分不安全程序,但策略错误仍可能造成吞吐下降、CPU 饥饿或尾延迟升高。
  4. 内核升级与策略升级可以分开评估。 团队可以分别观察新内核机制和定制调度策略对服务指标的影响。

sched_ext 不是让应用完全接管 CPU,也不意味着任何 BPF 程序都适合进入生产。内核版本、BPF 能力、调度器接口和示例工具仍可能变化,部署前必须以目标内核的文档和源码为准。

搭一套可重复的验证流程

可以先确认目标内核是否具备 sched_ext 支持。以下命令适用于暴露了 /proc/config.gz 的 Linux 系统;如果发行版把配置放在 /boot,脚本也会尝试读取对应文件。

#!/usr/bin/env bash
set -euo pipefail

if [[ -r /proc/config.gz ]]; then
  zgrep '^CONFIG_SCHED_CLASS_EXT=' /proc/config.gz || true
elif [[ -r "/boot/config-$(uname -r)" ]]; then
  grep '^CONFIG_SCHED_CLASS_EXT=' "/boot/config-$(uname -r)" || true
else
  echo "Kernel configuration was not found"
fi

if [[ -r /sys/kernel/sched_ext/state ]]; then
  printf 'sched_ext state: '
  cat /sys/kernel/sched_ext/state
else
  echo "sched_ext sysfs interface is unavailable"
fi

将脚本保存为 check-sched-ext.sh 后运行:

chmod +x check-sched-ext.sh
./check-sched-ext.sh

看到 CONFIG_SCHED_CLASS_EXT=y 只说明内核编译了相关能力,并不证明某个调度器已经加载。不同内核版本的控制接口和工具名称可能不同,应继续检查当前内核源码中的 tools/sched_ext 示例和发行版文档。

如果目标环境包含 Linux 内核源码,可以这样查看和构建随该版本提供的示例。具体依赖包名称因发行版而异,运行前需要安装 clanglibbpfbpftoolmake 以及对应内核头文件。

cd /path/to/linux-source
ls tools/sched_ext
make -C tools/sched_ext

不要直接把示例调度器加载到生产广告节点。更稳妥的做法是在隔离主机上建立基线,并使用相同流量模型比较默认调度器和实验策略。下面给出一个可复制的延迟采样框架,假设系统已安装 rt-tests 中的 cyclictest

#!/usr/bin/env bash
set -euo pipefail

label="${1:-baseline}"
out="cyclictest-${label}-$(date +%Y%m%d-%H%M%S).txt"

sudo cyclictest \
  --threads "$(nproc)" \
  --duration 60s \
  --interval 1000 \
  --histogram 10000 \
  --quiet | tee "$out"

echo "Wrote $out"

分别在默认策略和实验策略下执行:

./measure-latency.sh default
# 按目标内核的方式加载实验性 sched_ext 调度器
./measure-latency.sh sched-ext

cyclictest 不能替代广告服务的真实基准测试,它只提供调度抖动的辅助信号。生产决策还应比较请求 P50、P95、P99、P99.9、超时率、CPU 利用率、上下文切换、任务迁移、吞吐量和单位请求 CPU 时间。

从实验调度器走向生产

调度策略上线前,应把回退机制当作功能本身,而不是运维附录。建议至少完成以下检查:

  • 固定内核、BPF 工具链和调度器程序版本,避免不可追踪的组合变化;
  • 使用可重放流量或稳定压测模型,对比默认调度器与实验策略;
  • 单独观察高分位延迟,避免平均值掩盖运行队列中的长尾等待;
  • 覆盖 CPU 满载、突发流量、线程阻塞、NUMA 跨节点和依赖变慢等场景;
  • 为调度器退出、加载失败和指标越界准备自动恢复路径;
  • 采用小规模主机池逐步放量,并同时保留未启用策略的对照组;
  • 将业务指标与内核指标关联,确认延迟改善没有以吞吐、公平性或稳定性为代价。

Meta 的案例说明,内核升级不只是基础设施维护动作。对于毫秒级延迟会改变业务结果的系统,CPU 调度本身就是服务架构的一部分。sched_ext 提供了新的工程路径:保留上游 Linux 内核,通过 BPF 定制策略,并用可观测、可灰度、可回退的方式治理调度延迟。它的真正门槛不在于写出一个能加载的调度器,而在于证明该策略在真实负载、异常场景和后续内核版本中都能稳定工作。


相关推荐