2. Linux 进程调度基础:CFS 调度器原理、nice 值与权重、调度延迟与最小粒度
2.1 CFS 调度器核心原理
完全公平调度器(Completely Fair Scheduler, CFS)是 Linux 内核自 2.6.23 版本起默认采用的进程调度算法。其设计哲学并非追求绝对的“公平”,而是在多任务环境下为每个进程提供虚拟运行时间(vruntime)上的公平,从而在宏观上实现 CPU 时间的均衡分配。
CFS 摒弃了传统的时间片轮转模型,转而使用红黑树(Red-Black Tree)来管理可运行进程。红黑树以进程的 vruntime 为键值进行排序,最左侧的节点(即 vruntime 最小的进程)是下一个被调度执行的进程。这种设计使得调度器能够以 O(log n) 的时间复杂度快速找到最需要 CPU 的进程。
vruntime += 实际运行时间 * (NICE_0_LOAD / 进程权重)
其中 NICE_0_LOAD 是 nice 值为 0 时的标准权重(1024)。该公式表明:权重越高的进程,其 vruntime 增长越慢,从而获得更多的 CPU 执行机会。
2.2 nice 值与权重映射
在 CFS 中,nice 值不再是传统意义上时间片的加减,而是通过一个全局的权重数组 prio_to_weight[] 映射为进程的调度权重。nice 值的范围是 -20 到 +19,默认值为 0。权重与 nice 值呈指数级关系,每相差 1 个 nice 值,权重变化约 10%。
| nice 值 | 权重(weight) | 相对 nice=0 的 CPU 占比 | 典型场景 |
|---|---|---|---|
| -20 | 88761 | 约 86.7 倍 | 实时性要求极高的系统进程 |
| -10 | 9548 | 约 9.3 倍 | 音频/视频处理 |
| 0 | 1024 | 1.0 倍(基准) | 普通用户进程(默认) |
| 10 | 110 | 约 0.11 倍 | 后台批处理任务 |
| 19 | 15 | 约 0.015 倍 | 极低优先级空闲任务 |
taskset 进行更精细的 CPU 隔离。
2.3 调度延迟与最小粒度
CFS 引入了两个关键参数来控制调度行为的精细度:调度延迟(sched_latency)和最小粒度(min_granularity)。这两个参数直接决定了进程在被抢占前能连续运行的最长时间,对 MySQL 这类延迟敏感型应用影响显著。
2.3.1 调度延迟(sched_latency)
调度延迟是 CFS 保证所有可运行进程在一个周期内至少被调度一次的目标时间间隔。默认值为 6ms(可通过 /proc/sys/kernel/sched_latency_ns 查看和修改)。当可运行进程数量较少时,每个进程在一个调度周期内获得的时间片为 sched_latency / 进程数。
2.3.2 最小粒度(min_granularity)
最小粒度是进程被调度后至少运行的时间下限,防止进程切换过于频繁导致上下文切换开销过大。默认值为 0.75ms(可通过 /proc/sys/kernel/sched_min_granularity_ns 查看)。当进程数量较多时,每个进程的时间片会被截断至最小粒度,以保证调度延迟不超过 sched_latency 的约束。
if (进程数 * min_granularity <= sched_latency)
时间片 = sched_latency / 进程数
else
时间片 = min_granularity
当进程数超过 sched_latency / min_granularity(默认 8 个)时,调度延迟将不再保证,每个进程仅获得最小粒度的时间片。
2.4 调度器参数对 MySQL 的影响路径
理解 CFS 调度器参数后,我们可以梳理出其对 MySQL 查询延迟的影响路径:
- nice 值 → 权重 → vruntime 增长率:高权重(低 nice 值)的 MySQL 线程 vruntime 增长慢,在红黑树中更靠左,被调度更频繁。
- sched_latency → 时间片大小:较大的调度延迟意味着每个 MySQL 线程获得更长的时间片,减少上下文切换次数,降低缓存污染。
- min_granularity → 切换频率:过小的最小粒度会导致 MySQL 线程频繁被抢占,增加查询尾延迟(tail latency)。
- 进程数 → 调度延迟保证:当 MySQL 服务器上可运行进程数过多时,调度延迟保证失效,每个线程仅获得最小粒度时间片,查询延迟急剧上升。
sched_latency_ns 调大至 12ms-24ms,并将 sched_min_granularity_ns 调大至 1.5ms-3ms,以减少上下文切换。同时通过 taskset 将 MySQL 线程绑定到专用 CPU 核心,减少可运行进程数,确保每个查询线程获得充足的时间片。