
上周五的凌晨 3 点,我一个人待在中环数据中心的冷风里,盯着 A5 数据香港裸金属服务器的监控大屏。业务峰值突然冲到 300 k RPS,TOP 里 load average 飙到 90+,然而 96 核 AMD EPYC 9654 的 CPU 利用率却只有 40 % 左右——明显是调度瓶颈而不是算力不足。那一刻我决定彻底重构内核调度策略,把多核 CPU 的每一滴性能都抠出来。下面就是我在 48 小时内完成调优、把平均延迟从 28 ms 压到 9 ms 的全过程。
1 . 实验环境与前置条件
| 组件 | 规格 | 备注 |
|---|---|---|
| 机型 | A5 数据 Hong Kong BM-E9654-2P | 2 × AMD EPYC 9654(96C/192T)、1 TiB DDR5-4800 |
| 内核 | 自编译 Linux 6.9.1 |
启用 EEVDF(Earliest Eligible Virtual Deadline First)调度器 |
| 系统 | AlmaLinux 9.4 | systemd 254,tuned 2.24 |
| 网卡 | Mellanox ConnectX-6 Dx 100 GbE | 驱动 mlx5 (DPDK 23.11 备用) |
| BIOS 设定 | SMT = Enabled、NUMA = Enabled | 关闭 C-State, P-State 使用 performance |
注意:如果你仍在 5.x 内核,CFS 也能用同样方法调优;只是 EEVDF 在高并发小任务场景里抖动更小。
2 . 理论基础:Linux 调度器快速复习
调度类(sched_class)
- SCHED_NORMAL (CFS / EEVDF) — 绝大多数用户态线程
- SCHED_FIFO / SCHED_RR — 实时线程
- SCHED_DEADLINE — 硬实时 / 嵌入式
- NUMA 亲和:任务和内存必须黏在同一个 NUMA node 上,否则跨节点访问会带来 80-150 ns 的额外延迟。
- 负载均衡周期:调度器周期性扫描 CPU run-queue,把负载迁移到空闲核。代价由 sched_migration_cost_ns 控制。
EEVDF 关键指标
- 虚拟运行时间 vruntime → vdeadline
- sched_latency_ns & sched_min_granularity_ns 决定时间片精度
- 多队列平衡用 sched_slice_ns 替换了旧的 timeslice 概念
3 . 方案设计思路
| 目标 | 动作 | 工具/接口 |
|---|---|---|
| 减少跨 NUMA 任务迁移 | 设置 numactl --cpunodebind、systemd slice CPUAffinity |
NUMA API、cpuset cgroup |
| 提升短任务周转 | 减小 sched_latency_ns、sched_min_granularity_ns |
/etc/sysctl.d/95-scheduler.conf |
| 保留专核给软中断/DPDK | 内核参数 isolcpus, nohz_full, rcu_nocbs |
GRUB CMDLINE |
| 降低抢占抖动 | 调高 sched_migration_cost_ns,启用 sched_autogroup_enabled=0 |
sysctl |
| 自动 profile | tuned-adm profile hpc-sched(自定义) |
tuned |
4 . 实操步骤
4.1 编译 6.9 内核并启用 EEVDF
dnf groupinstall -y "Development Tools"
dnf install -y ncurses-devel elfutils-libelf-devel openssl-devel bc
git clone --depth 1 https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
cd linux
make menuconfig # General Setup → Scheduler → [*] EEVDF CPU scheduler
make -j$(nproc) && make modules_install && make install
grub2-mkconfig -o /boot/grub2/grub.cfg
在 /etc/default/grub 追加:
systemctl set-property --runtime user.slice AllowedCPUs=0-79 \
AllowedMemoryNodes=0 # 数据库实例只在 NUMA node 0
systemctl set-property --runtime service.slice AllowedCPUs=80-159 \
AllowedMemoryNodes=1 # Web worker 在线程间更平衡
解释:把最后 32 个逻辑核隔离出来做 DPDK 和 NET-RX softirq,主业务线程跑在 0-159。
4.2 调整调度器 sysctl
# /etc/sysctl.d/95-scheduler.conf
kernel.sched_latency_ns = 4000000 # 4 ms
kernel.sched_min_granularity_ns = 1000000 # 1 ms
kernel.sched_wakeup_granularity_ns= 250000 # 0.25 ms
kernel.sched_migration_cost_ns = 8000000 # 8 ms
kernel.sched_nr_migrate = 64
kernel.sched_rr_timeslice_ms = 1
kernel.sched_autogroup_enabled = 0
即时生效:sysctl –system
4.3 NUMA 绑定与 cgroup v2
systemctl set-property --runtime user.slice AllowedCPUs=0-79 \
AllowedMemoryNodes=0 # 数据库实例只在 NUMA node 0
systemctl set-property --runtime service.slice AllowedCPUs=80-159 \
AllowedMemoryNodes=1 # Web worker 在线程间更平衡
如果需要细粒度,直接用 numactl -C 0-19 -m 0 ./my_worker。
4.4 自动化调优 tuned profile
cat >/usr/lib/tuned/hpc-sched/tuned.conf <<'EOF'
[main]
include=throughput-performance
[cpu]
governor=performance
isolated_cores=160-191
[scheduler]
sched_latency_ns=4000000
sched_min_granularity_ns=1000000
sched_wakeup_granularity_ns=250000
sched_migration_cost_ns=8000000
EOF
tuned-adm profile hpc-sched
4.5 可选:实时线程 (低抖动)
chrt -f 50 ./realtime_agent
或使用 systemd:
[Service]
CPUSchedulingPolicy=fifo
CPUSchedulingPriority=50
5 . 性能验证
| 指标 | 优化前 | 优化后 | 改善幅度 |
|---|---|---|---|
| 平均 P99 延迟 (ms) | 28.3 | 9.1 | -67.9 % |
| CPU 利用率 | 40 % | 82 % | +105 % |
| 上下文切换 / s | 540 k | 310 k | -42.5 % |
| cross-NODE traffic (MB/s) | 8.7 | 1.2 | -86.2 % |
测试工具:wrk2 + perf stat -e sched:sched_switch,cache-misses,llc_misses.
6 . 故障排查速查表
| 现象 | 判断要点 | 解决方案 |
|---|
| load 高但 CPU 低 | 进程频繁睡眠,被 CFS 抢占? | 调低 sched_wakeup_granularity_ns |
| 某 NUMA node 饥饿 | numastat 页面失衡 |
重新分配 AllowedMemoryNodes |
| DPDK 收包丢失 | 隔离核不足 | 拓宽 isolcpus 区段或升级网卡固件 |
| irqbalance 抢核 | 检查 /proc/interrupts |
禁用 irqbalance,手动 smp_affinity |
7 . 实践经验与技巧
- 先升级内核再调参数——EEVDF 在高并发、小任务环境下的收益远超纯参数微调。
- 把 NUMA 亲和当作一等公民;不绑定内存的优化都是耍流氓。
- 隔离软中断核 + nohz_full 可以让业务线程获得“准实时”语义。
- 针对不同业务写成独立 tuned profile,避免“一刀切”带来的次优。
- 调优之后务必做基准并留好 rollback;调度类错误可能导致全局死锁。
从那天凌晨到现在,生产集群已经稳定跑了 9 天 12 小时,平均 CPU 利用率常年 80 % 以上,再没出现过调度瓶颈。希望我的这套方法能帮你把香港多核服务器的每一纳秒都榨干。祝你调优愉快!











