CPU调优实战:进程优先级调整(nice/renice)、CPU亲和性(taskset)、内核调度策略调优

CPU调优这块,说实话,很多运维兄弟容易忽略。总觉得CPU不够用就加核,或者干脆上云扩容。但我在生产环境摸爬滚打这么多年,发现很多时候根本不是硬件不够,而是调度出了问题。你想想看,一个后台批处理任务把CPU吃满了,结果前端Web服务跟着遭殃——这种场景我见过太多次了。

今天咱们就聊聊CPU调优的三个核心手段:进程优先级、CPU亲和性、还有内核调度策略。这三板斧用好了,能让你在不花一分钱的情况下,把系统吞吐量提上去不少。

一、进程优先级调整:nice与renice

先说说优先级。Linux里每个进程都有个“nice值”,范围是-20到19。数值越小,优先级越高,越受CPU“偏爱”。默认值是0,普通用户只能调高(变“谦让”),root才能调低(变“霸道”)。

核心原则:交互式服务(如Nginx、MySQL)给高优先级,后台批处理(如日志压缩、数据同步)给低优先级。

1. 启动时设置优先级:nice

启动一个低优先级的压缩任务:

# 以nice值10启动(优先级较低)
nice -n 10 tar -czf backup.tar.gz /data

启动一个高优先级的Web服务:

# root用户设置nice值为-5(优先级较高)
nice -n -5 /usr/local/nginx/sbin/nginx

我的习惯:线上数据库进程我一般设成nice -5,但不会低于-10。太激进了反而可能影响系统关键进程,比如sshd。有一次我把MySQL设成-20,结果SSH连上去都卡顿,教训深刻。

2. 运行时调整优先级:renice

找到进程PID后动态调整:

# 将PID为1234的进程优先级调低
renice -n 10 -p 1234

# 将某个用户的所有进程优先级调高(root操作)
renice -n -5 -u www-data

查看当前nice值:

ps -eo pid,ni,comm | grep nginx

我曾经踩过的坑:有一次线上做日志归档,我直接renice -n 19把压缩进程降到底。结果磁盘I/O没降下来,CPU倒是空转了——因为压缩是I/O密集型任务,调nice对I/O几乎没影响。记住,nice只影响CPU调度,不影响磁盘I/O优先级。

二、CPU亲和性绑定:taskset

CPU亲和性,说白了就是把进程“钉”在某个或某几个CPU核心上跑。为什么要这么做?因为CPU缓存有局部性。进程在不同核心间来回切换,缓存频繁失效,性能损耗很大。尤其是对延迟敏感的服务,绑定核心能显著降低抖动。

1. 查看进程当前CPU亲和性

# 查看PID为1234的进程运行在哪些CPU上
taskset -p 1234

# 输出示例:pid 1234's current affinity mask: 3
# 这个3是二进制掩码,表示CPU0和CPU1

2. 启动时绑定CPU

# 将进程绑定到CPU0和CPU2(核心0和核心2)
taskset -c 0,2 /usr/local/nginx/sbin/nginx

# 绑定到CPU0-3(前4个核心)
taskset -c 0-3 /usr/local/mysql/bin/mysqld

3. 运行时修改亲和性

# 将PID为1234的进程绑定到CPU1
taskset -p -c 1 1234

实战建议:NUMA架构下,尽量让进程绑定在同一颗CPU的物理核心上,避免跨NUMA节点访问内存。可以用numactl --hardware查看NUMA拓扑。

我记得有一次帮客户优化Redis集群。他们发现某些节点响应时间忽高忽低,排查半天发现是内核把Redis进程在不同核心间来回迁移。用taskset绑定到固定核心后,P99延迟直接降了40%。

三、内核调度策略调优

Linux内核支持多种调度策略。咱们最常用的是CFS(完全公平调度器),但针对不同场景,还有实时调度策略可用。

1. 调度策略分类

策略 调度类 适用场景
SCHED_OTHER (SCHED_NORMAL) CFS 普通进程,默认策略
SCHED_FIFO 实时 先入先出,高优先级任务不被打断
SCHED_RR 实时 时间片轮转,同优先级轮流执行
SCHED_BATCH CFS 批处理任务,减少唤醒次数
SCHED_IDLE 空闲 极低优先级,只在CPU空闲时运行

2. 查看和修改调度策略

# 查看进程调度策略和优先级
chrt -p 1234

# 将进程设为FIFO实时策略,优先级50
chrt -f -p 50 1234

# 将进程设为RR轮转策略,优先级80
chrt -r -p 80 1234

特别提醒:实时策略(FIFO/RR)优先级范围是1-99,数值越大优先级越高。但千万别把普通进程设成实时策略!我曾经见过有人把sshd设成SCHED_FIFO,结果一个死循环进程把CPU占死,SSH都连不上去,只能硬重启。实时策略只适合那些确定性的、短时间执行的任务,比如音频处理、工业控制。

3. 内核参数调优

通过/proc/sys/kernel/下的参数可以调整全局调度行为:

# 查看CFS调度器的调度周期(单位:纳秒)
cat /proc/sys/kernel/sched_latency_ns

# 查看最小调度粒度
cat /proc/sys/kernel/sched_min_granularity_ns

# 调整(临时生效)
echo 6000000 > /proc/sys/kernel/sched_latency_ns

我的经验:对于Web服务器这类交互式应用,可以适当调小sched_min_granularity_ns,让进程切换更频繁,响应更及时。对于批处理服务器,调大这个值可以减少上下文切换开销。具体数值需要压测来验证,没有银弹。

四、综合实战案例

假设我们有一台16核服务器,上面跑着Nginx(前端)、MySQL(数据库)、和一个日志分析脚本(后台)。

我的调优思路是这样的:

  1. Nginx:绑定到CPU0-3,nice设为-5,调度策略保持SCHED_OTHER
  2. MySQL:绑定到CPU4-11,nice设为-10,避免跨NUMA节点
  3. 日志脚本:绑定到CPU12-15,nice设为10,调度策略改为SCHED_BATCH

具体命令:

# Nginx
taskset -c 0-3 nice -n -5 /usr/local/nginx/sbin/nginx

# MySQL
taskset -c 4-11 nice -n -10 /usr/local/mysql/bin/mysqld

# 日志脚本(启动后调整)
chrt -b -p 0 $(pgrep -f log_analyzer.sh)
renice -n 10 -p $(pgrep -f log_analyzer.sh)
taskset -p -c 12-15 $(pgrep -f log_analyzer.sh)

这样配置之后,即使日志分析脚本把CPU吃满,也只影响它绑定的那4个核心,Nginx和MySQL几乎不受影响。这就是CPU亲和性的威力。

最后说一句:调优不是一锤子买卖。每次调整后,用perf toppidstat这些工具观察一下效果。我习惯先做一次基准测试,记录调整前后的吞吐量和延迟,用数据说话。别凭感觉调,容易翻车。

嗯,CPU调优这块就先聊到这儿。下一章咱们聊聊内存调优,那又是另一番天地了。