作为经常在服务器上跑批量任务的人,你一定遇到过这种场景:守着一台机器做数据清洗、定时备份、日志压缩,结果线上服务的响应突然就变慢了,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 | 约 9548 |
| 0 | 1024 |
| 10 | 约 110 |
| 19 | 约 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 值为 0,PR 显示 20;nice 值为 10,PR 显示 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% 的 CPU,nice 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 <PID>NI 列变成了 -5。如果用top -d 1观察,能看到这个进程的 CPU 占用率明显上升,特别是在系统有负载的情况下。
反过来,如果想让某个失控进程安静下来,就调成正数。执行sudo renice -n 15 -p <PID>,然后观察它的 CPU 占用率。我见过不少同事直接 kill 掉出问题的任务,其实如果只是临时占 CPU,renice 后让它继续跑反而是更稳妥的选择。
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]段里加:
Nice=10这样 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 值怎么影响权重之后,其实就一句话:让它排队,而不是插队。