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(数据库)、和一个日志分析脚本(后台)。
我的调优思路是这样的:
- Nginx:绑定到CPU0-3,nice设为-5,调度策略保持SCHED_OTHER
- MySQL:绑定到CPU4-11,nice设为-10,避免跨NUMA节点
- 日志脚本:绑定到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 top、pidstat这些工具观察一下效果。我习惯先做一次基准测试,记录调整前后的吞吐量和延迟,用数据说话。别凭感觉调,容易翻车。
嗯,CPU调优这块就先聊到这儿。下一章咱们聊聊内存调优,那又是另一番天地了。