十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Linux nice命令详解:从CPU调度原理到实战调优

Linux nice命令详解:从CPU调度原理到实战调优 作为经常在服务器上跑批量任务的人你一定遇到过这种场景守着一台机器做数据清洗、定时备份、日志压缩结果线上服务的响应突然就变慢了top 一看某个进程把 CPU 吃得干干净净其他进程全在排队等着。第一反应多半是 kill 掉这个“捣乱”的进程但任务还没跑完舍不得不 kill 又怕影响核心业务。Linux 早就给了标准答案就是标题里这个看着不起眼的命令——nice。这篇东西我不打算写成一页命令手册而是想把 nice 命令从概念到实操完整拆开讲清楚它到底改了什么、什么时候有效、什么时候白改以及我踩过的那些坑。适合刚接触 Linux 的运维新手也适合那些用了好几年 nice 但从没真正理解过它的人。1. 先搞清楚nice 到底是干什么的1.1 从一次压测事故说起早些年在公司做性能压测机器上同时跑着 Nginx 和一套内部的数据处理脚本。脚本是凌晨定时任务正常情况下 CPU 占用不高结果那天数据量翻倍脚本直接把整个 8 核机器吃满了。Nginx 的响应延迟从 5ms 一路涨到 200ms监控告警响成一片。当时最让我懊恼的是这个问题本来完全可以在脚本启动命令里加一个词就能避免——就是nice -n 10。nice命令的用途简单说就是在启动一个进程时给它设置一个“谦让值”让内核在分配 CPU 时间时少分它一点把更多执行时间留给优先级更高的进程。这个“谦让值”就是 nice value中文社区里一般叫 nice 值或者友好值取值范围在 Linux 上是 -20 到 19默认是 0。值越小优先级越高进程越“霸道”值越大优先级越低进程越“谦让”。所以“对系统更 nice”实际上是让进程自己往后退一步把 CPU 让给别人。这就是为什么它叫 nice而不是叫 fast 或者 priority。1.2 nice 值到底在调度器里怎么工作我刚开始接触这玩意儿的时候以为 nice 值就是设置一个 1 到 100 的百分比后来翻了内核资料才发现完全不是。Linux 的默认调度器是 CFS完全公平调度器从 2.6.23 开始用到现在。CFS 的理念不是给每个进程分一个固定时间片而是维护一个虚拟运行时间vruntime哪个进程实际占用 CPU 的时间越少、vruntime 越小调度器就越倾向于让它上 CPU。nice值的作用就是通过改变进程的权重来影响 vruntime 的增长速度。权重越大虚拟时间增长越慢进程就越容易获得 CPU权重越小虚拟时间增长越快进程就越容易被调度器冷落。内核里有一个权重表大致对应关系是每提高一个 nice 值权重大约降低 1.25 倍。具体数字可以感受一下nice 值权重简化理解-20约 88761-10约 95480102410约 11019约 15所以 nice 0 和 nice 10 的两个进程抢同一个 CPU 核时CPU 分配比例大约是 1024 比 110接近 9:1。这意味着 nice 值为 10 的进程在竞争激烈时理论上只能拿到大约 10% 的 CPU 时间。如果你对那个“罪魁祸首”脚本启动时加了nice -n 10它依然能跑只是跑得慢很多但不会把整个系统拖垮。1.3 “优先级”这个词太容易让人误会了很多老哥会把 nice 值和任务优先级画等号这会有误解。nice 值影响的是CPU 调度的权重它不保证进程一定在某个时间片内执行完也不保证实时性。它管的是“在 CPU 资源不够分的时候大家按什么比例排队”。另外要注意一个继承机制子进程会继承父进程的 nice 值。你用nice -n 10 ./script.sh启动脚本脚本里再起的子进程、孙进程全都继承了 nice 10 这个设定不需要逐个设置。这也是为什么在启动脚本时加一条 nice 命令就能影响一整个进程树特别适合批量任务场景。2. 核心用法nice 命令的参数与周边工具2.1 nice 命令的常见写法GNU 的nice命令语法是nice [选项] [命令 [参数...]]最常用的就是-n选项指定 nice 值调整量。假设我要启动一个备份脚本并把它的 nice 值设置为 10可以写nice -n 10 /opt/scripts/backup.sh这个写法是 POSIX 标准推荐的明确、可读性好。实际字节数上还有个简写形式比如nice -10 /opt/scripts/backup.sh在一些发行版上也支持表示调整量是 10。但我不推荐这么写原因后面会讲。如果你想启动一个提高优先级的进程比如某个交互式服务可以用负数sudo nice -n -5 /usr/local/bin/api-server注意这里必须用sudo因为普通用户没有权限把 nice 值往负数方向调内核会直接拒绝。还有两个冷门选项nice --help看帮助nice --version看版本。日常用不到但排查环境差异时偶尔会用到。2.2 启动之后怎么查看一个进程的 nice 值查 nice 值有几个常用入口。最简单的是ps命令ps -l在输出里找到NI那一列就是进程当前的 nice 值。如果想精确查某个进程ps -o pid,ni,cmd -p 1234这里pid是进程号ni是 nice 值cmd是启动命令行。在top里也能看到默认界面上半部分有一个NI列数字越大表示越谦让。如果你平时用的是htop它会直接显示在 NI 列里用 F7、F8 还可以交互式调整。还有个细节top里往往还能看到PR列即进程优先级priority。在常见的 Linux 环境中对于普通调度策略SCHED_NORMAL的进程PR大致等于20 NI。比如 nice 值为 0PR 显示 20nice 值为 10PR 显示 30。看到 PR 高不一定是坏事只是说明你调低了优先级让出了 CPU。2.3 renice进程启动之后想改怎么办nice只能管启动那一刻进程已经跑起来了你就得用renice。renice的语法在不同 Linux 发行版上有点差别我用的是 util-linux 版本最稳的写法是renice -n 10 -p 1234第二个-n 10表示要把进程 1234 的 nice 值设置为10注意是“设置成”而不是“增加 10”。这是跟nice最大的区别。有朋友刚开始用的时候想当然以为renice -n 10是在原来基础上加 10结果把进程优先级改成了 20直接被系统限制得几乎跑不动。renice还支持按用户名和进程组调整例如sudo renice -n -5 -u www-data把www-data用户的所有进程 nice 值设为 -5。这套操作在应急调优时非常实用不用一个一个 PID 去改。2.4 权限边界普通用户和 root 的差异这是新手最常踩的坑之一。普通用户使用nice和renice时只能把 nice 值变大也就是让进程更谦让不能把它变小。你运行nice -n -5 ./some-task大概率会得到一个Permission denied或者Operation not permitted。内核不让普通用户随便提升进程优先级是怕一个用户把系统资源全占死搞挂其他用户的服务。root 没有这个限制可以在整个 -20 到 19 范围内随意调整。如果某个进程已经运行并且你确信它是被误删了优先级可以这样救急sudo renice -n -5 -p 1234在容器环境里还有一层约束即使容器内是 root如果缺少CAP_SYS_NICE权限设置负数的操作也可能会失败。这时候需要从宿主机入手或者检查容器的 capabilities 配置。3. 实操记录在单核环境下验证 nice 的效果3.1 准备一个能稳定占 CPU 的实验任务理论讲再多不如亲手跑一次。我这次实验的环境是 16 核的云主机系统是 Ubuntu 22.04。为了直观看到效果我决定用taskset把任务限制在同一个 CPU 核上模拟两台进程抢一个核的场景。先用一个 C 程序制造固定的 CPU 计算负载循环次数自己调保证每跑一次需要几秒钟#include stdio.h int main(void) { volatile unsigned long long x 0; for (unsigned long long i 0; i 300000000ULL; i) { x i; } printf(%llu\n, x); return 0; }编译gcc -O2 -o /tmp/burn /tmp/burn.c基本思路是同时启动两个完全相同的/tmp/burn进程绑定到同一个 CPU 核心一个用nice -n 0另一个用nice -n 10然后看谁先跑完。之所以绑定同一个核是因为如果机器上有多余空闲核两个任务各跑各的谁也碍不着谁nice 的差异根本体现不出来。很多朋友说“我试了 nice 怎么没效果”大概率就是栽在这上面。3.2 用 taskset 锁定单核对比 nice 0 和 nice 10启动命令可以这样写把每个进程的 time 输出重定向到不同文件方便对比( time taskset -c 0 nice -n 0 /tmp/burn ) 2 /tmp/time0 ( time taskset -c 0 nice -n 10 /tmp/burn ) 2 /tmp/time10 wait cat /tmp/time0 /tmp/time10因为两个进程几乎同时启动同一个 CPU 核的算力需要按权重分配。理论上 nice 0 的进程能占到大约 90% 的 CPUnice 10 的进程只有 10% 左右。我在实际环境中得到的结果是nice 0 的进程real 约 3.2 秒nice 10 的进程real 约 9.8 秒虽然没有严格 9 倍那么夸张但差距已经非常明显。同一个程序只是启动命令里多了一个-n 10执行时间就慢了三倍。这效果比任何理论解释都直观。如果你机器上 bash 的运算性能不同可以修改 C 程序里的循环次数。我的经验是先跑一次taskset -c 0 /tmp/burn看单任务时间控制在 2 到 5 秒最优太短不好观察太长等得难受。3.3 用 renice 动态修改正在运行的进程启动时设置 nice 值只能管未来已经运行的进程要改就得实操一把renice。先启动一个普通任务taskset -c 0 /tmp/burn 然后立刻查看它的 PID 和当前 nice 值ps -o pid,ni,cmd -p $(pgrep -f /tmp/burn | head -1)输出里 NI 那列应该是 0。现在从另一个终端把它调成更“霸道”的负数优先级sudo renice -n -5 -p PID再查一次ps -o pid,ni,cmd -p PIDNI 列变成了 -5。如果用top -d 1观察能看到这个进程的 CPU 占用率明显上升特别是在系统有负载的情况下。反过来如果想让某个失控进程安静下来就调成正数。执行sudo renice -n 15 -p PID然后观察它的 CPU 占用率。我见过不少同事直接 kill 掉出问题的任务其实如果只是临时占 CPUrenice 后让它继续跑反而是更稳妥的选择。3.4 实验结论nice 值影响权重但不等于硬性限速通过上面这个实验我建议你记住三个结论。第一nice改的是分配权重不是设置一个“最多能用多少 CPU”的硬上限。如果机器上只有你这一个进程在跑nice 值再高也能用满所有 CPU只是当别人抢资源时你会排在后面。所以它不适合用来做严格的资源隔离。第二nice 值的影响和 CPU 核数、系统负载强相关。机器越闲效果越不明显机器越忙效果越狠。用taskset绑核之后效果才最接近理论值。第三对于 IO 密集型的进程单纯调 nice 值基本没用因为瓶颈不在 CPU 调度。比如一个进程在疯狂读数据库、写日志它大部分时间在等待 IO而不是占用 CPU这时候 CPU 调度器根本无暇去管它。想限制这类进程应该用ionice或者 cgroup 的 IO 权重后面单独聊。4. 常见问题与排查技巧实录4.1 为什么我改了 nice 值进程速度好像没变化这个问题我至少被问过五次每次排查到最后基本都是下面三个原因之一。第一个原因是机器多核且负载不高。你有 16 核只是跑了几个小任务每个任务都有独立的核心可用谁也不抢谁的那 nice 值当然不痛不痒。CFS 调度器只有在 CPU 资源不足的时候才会频繁计算权重做调整。仿佛马路上车少的时候公交车和跑车速度都能开到限速上限谁也不用给谁让道。第二个原因是进程本身不是 CPU 密集型的。top里看到某个进程 CPU 占用很低但你给它改了 nice 值也没见变快因为它瓶颈在磁盘 IO 或者网络等待上。判断方法很简单top里看%CPU和%WA如果%WA很高说明在等待 IO这时候调 CPU 优先级自然没用。第三个原因是你看错了列。top里NI列是 nice 值PR列是动态优先级有人看到 PR 变了以为没改成功其实改的是 NI 列。或者你用ps -ef看这个输出默认根本不包含 NI 列自然看不到变化。建议统一用ps -o pid,ni,cmd -p PID查看。4.2 修改 nice 值报 Operation not permitted这个错误多半是权限问题。普通用户想把 nice 值往负数调或者把某个已经是很高的 nice 值继续往下调内核都会拒绝。解决方法是使用sudo或者把操作交给有权限的运维管理员。还有一种容易忽略的情况如果你在容器里运行即便容器内是 root也可能因为缺少CAP_SYS_NICE而失败。这时候可以检查容器启动参数或者干脆从宿主机通过nsenter进入容器进行调试。平时写自动化脚本时建议先判断当前用户权限再决定是否使用负数不然半夜被告警吵醒很痛苦。4.3 在容器和 systemd 环境里nice 失效了这个问题问的人越来越多因为现在服务基本都在容器里跑。先说 systemd。用 systemd 管理服务时如果直接在 ExecStart 里写nice -n 10 xxx大多数情况下是能生效的但更规范的做法是在 unit 文件的[Service]段里加Nice10这样 systemd 会在启动服务时统一设置 nice 值比包一层命令更干净日志和状态管理也更清晰。注意负数同样需要权限普通 systemd service 默认不一定有CAP_SYS_NICE。再说容器。Docker 对 CPU 资源的控制主要走的是 cgroup 体系比如--cpu-shares、--cpus、--cpu-quota这些和宿主机上的 nice 是两个维度的东西。在容器里往自己的进程加上 nice 值对宿主机上其他容器的抢占行为影响非常有限。换句话说如果想限制某个容器的 CPU 使用不要只靠renice应该合理设置 Docker 的 CPU 配额参数。4.4 常见问题速查表问题可能原因解决思路调了 nice 值CPU 占用没变化多核空闲或进程非 CPU 密集用 taskset 绑核或用 top 确认瓶颈Nice 值无法设置为负数普通用户权限不够使用 sudo检查容器 capabilities子进程没有继承 nice 值继承机制失效很少见检查是否通过监听 socket 或 systemd 间接拉起进程在容器里 renice 报错缺少 CAP_SYS_NICE从宿主机调整或修改容器配置改了 PR 列但 NI 列没变PR 是动态优先级NI 才是 nice以 NI 列为准不要只看 PR进程速度反而更慢nice 值被调得过高改成 0 或适当变小这个表如果你能保存在本地下次排查会省不少事。5. 结合实战聊聊我的一点使用建议5.1 哪些场景值得用 nice我的经验是所有不要求实时响应、但很吃 CPU 的批量任务都应该在启动时带上nice。比如凌晨跑的数据清洗脚本、数据库备份压缩、日志归档、代码仓库的离线打包、测试集群里的压测客户端。具体命令大概长这样nice -n 10 /data/scripts/clean.sh nice -n 15 tar czf /backup/logs.tar.gz /var/log/nginx/如果任务里面还涉及大量磁盘读写建议和ionice搭配使用nice -n 10 ionice -c 2 -n 7 /data/scripts/clean.shionice管 IO 调度优先级nice管 CPU 调度优先级两个一起用才是真正的“后台干活不打扰人”。这也是很多运维老手会忽略的组合拳。对于已经运行但抢占资源严重的进程不一定要 kill先试试sudo renice -n 15 -p PID让进程继续把活干完只是不再嚣张往往比直接杀掉更符合业务预期。5.2 哪些场景别指望 nice实时性要求高的场景别用 nice。比如语音通话、视频推流、游戏服务器这类进程需要的不是排队权重而是严格的调度策略Linux 下应该用chrt设置 SCHED_FIFO 或者 SCHED_RR根本不属于 nice 的职责范围。做严格的资源隔离也别用 nice。想限制某个容器最多用多少核、多少 CPU 时间用 Docker 的--cpus、--cpu-quota或者 Kubernetes 的 CPU limit。nice 只影响相对顺序不能保证一个进程的 CPU 占用被限制在多少以内。这是很多人误解最深的地方nice不会给进程踩刹车它只是改变排队顺序。5.3 我个人的一个习惯我这几年写脚本凡是计划任务、批量脚本、压缩备份这一类不需要实时响应的活启动命令前面一律习惯性加一个nice -n 10。不管当前机器负载怎么样都加反正对任务本身影响不大但对整机其他服务而言多了一道保险。等到真正出事了你会庆幸当初多了这么一行。真的调度器这东西不懂的时候很神秘理解了 nice 值怎么影响权重之后其实就一句话让它排队而不是插队。
返回列表