2. Linux 进程调度器概述:CFS、实时调度类(FIFO/RR)、空闲调度类

Linux 内核的进程调度器负责在多个可运行进程之间分配 CPU 时间,其设计直接影响系统的响应性和吞吐量。对于 Redis 这类延迟敏感型应用,理解调度器的工作机制是性能调优的前提。本章将深入剖析三种核心调度类:完全公平调度类(CFS)、实时调度类(FIFO/RR)以及空闲调度类。

2.1 调度类层次结构与核心概念

Linux 调度器采用模块化设计,通过 调度类(Scheduling Class) 实现不同调度策略。每个进程在创建时被赋予一个调度策略,内核根据优先级顺序遍历调度类,选择最高优先级的可运行进程。

核心原则: 调度类优先级从高到低依次为:停止调度类(Stop) > 实时调度类(RT) > 完全公平调度类(CFS) > 空闲调度类(Idle)。Redis 通常运行在 CFS 或实时调度类下。
Linux 调度类层次结构 停止调度类 (Stop) 实时调度类 (RT) 完全公平调度类 (CFS) 空闲调度类 (Idle) 优先级递减:Stop > RT > CFS > Idle。高优先级调度类中的进程会抢占低优先级调度类。

2.2 完全公平调度类(CFS)

CFS 是 Linux 默认的调度类,适用于大多数普通进程(包括 Redis 的默认运行模式)。其设计目标是让每个进程获得公平的 CPU 时间份额。

2.2.1 核心工作原理

  • 虚拟运行时间(vruntime): CFS 为每个进程维护一个 vruntime,表示进程已消耗的 CPU 时间(经过优先级加权)。调度器总是选择 vruntime 最小的进程运行。
  • 红黑树(Red-Black Tree): 所有可运行进程按 vruntime 组织在一棵红黑树中,最左侧节点(vruntime 最小)即为下一个被调度的进程。
  • 调度周期(sched_latency): 默认值通常为 6ms 或 12ms,CFS 保证在一个调度周期内,所有可运行进程至少被调度一次。
  • 最小粒度(min_granularity): 进程每次运行的最小时间片,默认值通常为 0.75ms 或 1ms,防止上下文切换过于频繁。
Redis 调优提示: 在 CFS 下,Redis 可能因其他进程(如日志、监控、备份)的 CPU 消耗而经历调度延迟。通过调整 sched_latency 或使用 taskset 绑定 CPU 核心,可以降低延迟抖动。
CFS 红黑树调度示意图 vruntime=100 vruntime=80 vruntime=150 vruntime=60 vruntime=95 调度选择逻辑 选择红黑树最左侧节点 vruntime=60 的进程将优先运行

2.2.2 CFS 关键参数

参数名 默认值(常见) 对 Redis 的影响
sched_latency_ns 6,000,000 (6ms) 调度周期越小,Redis 被重新调度的等待时间越短,但上下文切换开销增加
sched_min_granularity_ns 750,000 (0.75ms) 最小时间片越小,延迟越短,但切换频率升高
sched_wakeup_granularity_ns 1,000,000 (1ms) 控制唤醒抢占的阈值,影响 Redis 从 I/O 等待恢复后的响应速度
sched_nr_migrate 32 控制负载均衡时迁移的进程数,影响多核 Redis 实例的缓存亲和性

2.3 实时调度类(FIFO / RR)

实时调度类为需要确定性响应时间的进程设计,优先级范围为 0~99(数值越大优先级越高)。Redis 在追求极致低延迟时,常被配置为使用实时调度策略。

2.3.1 SCHED_FIFO(先进先出)

  • 无时间片: FIFO 进程一旦获得 CPU,会一直运行直到主动让出(如 sleep、I/O 等待)或被更高优先级的实时进程抢占。
  • 严格优先级: 只有优先级更高的 FIFO/RR 进程才能抢占当前运行的 FIFO 进程。
  • 适用场景: 需要极低且可预测的调度延迟,且进程能在短时间内完成关键计算。

2.3.2 SCHED_RR(轮转)

  • 固定时间片: RR 是 FIFO 的增强版,为每个同优先级进程分配固定时间片(默认 100ms)。时间片耗尽后,进程被放到同优先级队列的尾部。
  • 公平轮转: 在同优先级进程之间实现时间片轮转,防止单个进程长时间独占 CPU。
  • 适用场景: 多个实时进程需要共享 CPU,且每个进程都需要定期获得执行机会。
风险提示: 将 Redis 设置为实时调度策略(FIFO/RR)时,必须谨慎。如果 Redis 进程陷入死循环或长时间占用 CPU,可能导致系统关键进程(如 sshd、watchdog)无法运行,最终系统挂死。建议配合 ulimit -r 限制实时优先级,并设置 CPU 亲和性。
SCHED_FIFO vs SCHED_RR 调度对比 SCHED_FIFO 进程 A(优先级 50)运行 进程 B(优先级 40)等待 A 主动让出前,B 无法运行 SCHED_RR 进程 A(优先级 50)运行 100ms 时间片耗尽 → A 移至队尾 进程 B(优先级 50)获得 CPU 关键区别 FIFO:无时间片,进程运行直到主动让出或被更高优先级抢占 RR:固定时间片(默认 100ms),同优先级进程轮转执行 两者均支持优先级 0~99,数值越大优先级越高

2.4 空闲调度类(Idle)

空闲调度类用于运行 空闲进程(idle task),其优先级最低,仅在系统中没有其他可运行进程时被调度。空闲进程通常执行 CPU 休眠指令(如 HLT、WFI),以降低功耗。

  • 每个 CPU 核心一个空闲进程: 当某个 CPU 核心上没有其他进程可运行时,该核心执行空闲进程。
  • 对 Redis 的影响: 空闲调度类本身不直接影响 Redis 延迟,但空闲进程的唤醒机制(如 tickless 内核、dynticks)会影响 CPU 进入和退出节能状态的延迟,间接影响 Redis 的响应时间。
  • 调优关联: 在 Redis 生产环境中,建议禁用 CPU 深度节能状态(如 intel_idle.max_cstate=0),以减少从空闲状态唤醒的延迟。

2.5 调度类选择对 Redis 延迟的影响总结

调度类 延迟特性 吞吐量影响 Redis 推荐场景
CFS(默认) 中等,受系统负载影响较大 高,公平分配 CPU 通用部署、多租户环境
SCHED_FIFO 极低且可预测 可能降低其他进程吞吐 低延迟交易、缓存服务
SCHED_RR 低,同优先级轮转 中等,时间片开销 多实时进程共存场景
Idle 不适用 不适用 仅系统空闲时运行