Linux 服务器 CPU 使用率低但系统很慢
在运维和性能优化的日常工作中,最容易让人产生困惑的现象之一就是:CPU 使用率明明很低(甚至不到 30%),但整个服务器响应极慢、命令敲下去要等好几秒、网站打开卡顿、数据库查询超时。很多新手看到 top 里 us+sy 只有 10~20%,就误以为“服务器资源很充裕”,然后开始各种盲目调参,结果问题越搞越严重。这篇文章就来系统拆解这类“CPU 低但系统慢”的真实原因、排查路径、诊断工具和常见解决办法。
一、为什么会出现“CPU 低但系统慢”?
核心原因一句话概括:
CPU 不是瓶颈,系统在“等”别的东西。
Linux 调度器只有在进程处于 可运行状态(R / Running) 时才会消耗 CPU。当进程大部分时间都在等待(D、S、T 等状态),CPU 自然空闲,但用户感知到的系统却是“慢”。
最常见的几种“等待”类型:
- 等待磁盘 I/O(最常见,占比 60%+)
- 等待网络 I/O(TCP 窗口满、对方慢、DNS 解析卡住等)
- 等待内存(内存回收、swap、页面错误)
- 等待锁(互斥锁、读写锁、futex 等)
- 等待内核资源(调度延迟、软中断、IRQ 竞争)
- 虚拟化/容器层被限流(cgroup cpu.shares / cpu.cfs_quota)
下面按概率从高到低逐一拆解。
二、第一大元凶:磁盘 I/O 等待(iowait / D 状态)
现象特征
- top / htop 中 wa(iowait) 持续偏高(>20%~40% 甚至更高)
- 大量进程处于 D(不可中断睡眠) 状态
- load average 明显高于 CPU 使用率(常见 load 20+,CPU 总使用率 <30%)
- iostat -x 中 %util 接近或达到 100%,await > 10~50ms(视 SSD/HDD)
快速诊断命令
Bash
# 1. 看整体 iowait 和负载
top # 按 1 查看每核,注意 wa%
iostat -x 1 5 # 重点看 %util、await、svctm、r/s+w/s
# 2. 找出哪些进程在 D 状态
ps -eo pid,ppid,state,tid,class,rtprio,time,pcpu,pmem,cmd | grep " D"
# 3. 实时查看哪个进程在狂读写磁盘
iotop -o # 只显示有 IO 的进程(需安装 iotop)常见场景举例
- MySQL / PostgreSQL 慢查询 + 缺少索引 → 大量随机 IO
- 日志级别为 DEBUG → 高并发场景每秒写几千行日志
- 宝塔 / 面板类软件开启了 access.log + error.log 无限制写入
- 数据库 binlog、redo log、data 文件放在同一块普通 SATA 盘
- 虚拟机宿主机磁盘性能差 + 多租户争抢
- NFS / Ceph / Gluster 等分布式存储网络抖动或延迟放大
快速缓解办法
- 立即:把日志级别改成 INFO/WARN,关闭 debug
- 中期:把高频写日志的文件单独挂载到高性能盘(NVMe 或单独 SSD)
- 长期:优化 SQL、使用缓存、分库分表、异步写日志
三、第二大类:内存压力导致的间接等待
现象特征
- free -h 显示 available 很低(< 10%)
- vmstat 中 si/so(swap in/out)不为 0,且持续存在
- dmesg | grep -i “out of memory” 有 OOM Killer 痕迹
- 即使没有 swap,kswapd0 进程 CPU 占用偏高(说明内核在疯狂回收页面)
诊断命令
Bash
# 查看真实可用内存(最重要)
free -h
# 查看 swap 使用和换页速率
vmstat 1 5
# 查看哪些进程占内存最多
ps -eo pid,rss,vsz,cmd --sort=-rss | head -20
# 查看 slab 缓存是否过大(常见于文件服务器)
slabtop
# 是否触发了 OOM
dmesg | grep -i 'out of memory'
journalctl -k | grep -i 'out of memory'常见原因
- Java 应用堆外内存泄漏(Netty、DirectByteBuffer)
- PHP-FPM / Nginx 开启过多 worker + 每个进程内存偏高
- Redis 内存超限但没设置 maxmemory-policy
- 大量小文件缓存导致 dentry/inode 缓存爆炸
- 程序大量 fork 但不释放内存(最典型:多进程模型的爬虫)
解决方向
- 调大物理内存(最直接)
- 调低 vm.swappiness(建议 10 或更低)
- 限制进程内存(ulimit -v、systemd 的 MemoryMax)
- Java 应用加 -XX:MaxDirectMemorySize
- 开启 zram / zswap 作为 swap 加速(适用于内存紧张但 CPU 富裕的场景)
四、网络等待导致的“假慢”
现象特征
- CPU 低、iowait 也不高,但请求响应时间明显变长
- ss -s 显示大量 ESTABLISHED 连接但吞吐量低
- tcpdump 抓包看到大量重传、零窗口、延迟 ACK
诊断命令
Bash
# 查看网络统计
sar -n DEV 1 5
sar -n TCP 1 5
# 查看连接状态分布
ss -s
ss -m # 显示内存使用情况
# 抓包看是否有大量重传
tcpdump -i eth0 -nn tcp port 80 or port 443 -c 200常见原因
- 上游服务慢(数据库、第三方 API、CDN 源站)
- 对端 TCP 窗口极小(接收缓冲区不足)
- MTU 不匹配导致分片和黑洞
- 网卡 offload 功能异常(尤其是虚拟机)
- 防火墙 / iptables 规则过多导致 conntrack 表爆炸
五、其他隐藏原因(概率较低但很坑)
- cgroup / 容器 CPU 限额
- docker stats 显示 CPU 被 throttle
- cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us
- 软中断过多(ksoftirqd 占用高)
- 网卡中断不均衡、DPDK、大量小包
- futex 竞争(多线程锁竞争)
- perf record -e syscalls:sys_enter_futex -a sleep 10
- numa 访问不均衡
- 大内存机器 + 进程没有绑定 numa 节点
六、系统性排查流程(建议做成 checklist)
- uptime / top → 看 load、wa%、进程状态
- iostat -x + iotop → 判断是否 IO bound
- free -h + vmstat → 判断是否内存压力
- ps -eo pid,state,cmd | grep D → 找卡住的进程
- strace -p <PID> 或 perf top → 看进程在等什么系统调用
- ss -s / sar -n → 判断网络是否异常
- dmesg / journalctl -k → 看内核是否有严重错误
结语
“CPU 使用率低但系统很慢”本质上都是资源等待导致的假象,而其中 70% 以上的情况最终都会指向 磁盘 IO 等待 和 内存压力 这两个方向。真正高效的排查不是盯着 CPU% 调参数,而是要快速定位“系统到底在等什么”。
熟练掌握 iostat、iotop、vmstat、ss、perf 这几个工具,并养成“先看 wa 和 D 状态”的习惯,大部分生产问题都能在 10~30 分钟内找到主要方向。
如果你的业务场景也经常遇到这类问题,欢迎在留言区分享你的负载特征、top 截图或 iostat 输出,我们可以一起分析具体原因。