Linux 服务器 CPU 使用率低但系统很慢

Linux 服务器 CPU 使用率低但系统很慢

在运维和性能优化的日常工作中,最容易让人产生困惑的现象之一就是:CPU 使用率明明很低(甚至不到 30%),但整个服务器响应极慢、命令敲下去要等好几秒、网站打开卡顿、数据库查询超时。很多新手看到 top 里 us+sy 只有 10~20%,就误以为“服务器资源很充裕”,然后开始各种盲目调参,结果问题越搞越严重。这篇文章就来系统拆解这类“CPU 低但系统慢”的真实原因、排查路径、诊断工具和常见解决办法。

一、为什么会出现“CPU 低但系统慢”?

核心原因一句话概括:

CPU 不是瓶颈,系统在“等”别的东西

Linux 调度器只有在进程处于 可运行状态(R / Running) 时才会消耗 CPU。当进程大部分时间都在等待(D、S、T 等状态),CPU 自然空闲,但用户感知到的系统却是“慢”。

最常见的几种“等待”类型:

  1. 等待磁盘 I/O(最常见,占比 60%+)
  2. 等待网络 I/O(TCP 窗口满、对方慢、DNS 解析卡住等)
  3. 等待内存(内存回收、swap、页面错误)
  4. 等待锁(互斥锁、读写锁、futex 等)
  5. 等待内核资源(调度延迟、软中断、IRQ 竞争)
  6. 虚拟化/容器层被限流(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)

常见场景举例

  1. MySQL / PostgreSQL 慢查询 + 缺少索引 → 大量随机 IO
  2. 日志级别为 DEBUG → 高并发场景每秒写几千行日志
  3. 宝塔 / 面板类软件开启了 access.log + error.log 无限制写入
  4. 数据库 binlog、redo log、data 文件放在同一块普通 SATA 盘
  5. 虚拟机宿主机磁盘性能差 + 多租户争抢
  6. 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 表爆炸

五、其他隐藏原因(概率较低但很坑)

  1. cgroup / 容器 CPU 限额
    • docker stats 显示 CPU 被 throttle
    • cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us
  2. 软中断过多(ksoftirqd 占用高)
    • 网卡中断不均衡、DPDK、大量小包
  3. futex 竞争(多线程锁竞争)
    • perf record -e syscalls:sys_enter_futex -a sleep 10
  4. numa 访问不均衡
    • 大内存机器 + 进程没有绑定 numa 节点

六、系统性排查流程(建议做成 checklist)

  1. uptime / top → 看 load、wa%、进程状态
  2. iostat -x + iotop → 判断是否 IO bound
  3. free -h + vmstat → 判断是否内存压力
  4. ps -eo pid,state,cmd | grep D → 找卡住的进程
  5. strace -p <PID> 或 perf top → 看进程在等什么系统调用
  6. ss -s / sar -n → 判断网络是否异常
  7. dmesg / journalctl -k → 看内核是否有严重错误

结语

CPU 使用率低但系统很慢”本质上都是资源等待导致的假象,而其中 70% 以上的情况最终都会指向 磁盘 IO 等待内存压力 这两个方向。真正高效的排查不是盯着 CPU% 调参数,而是要快速定位“系统到底在等什么”。

熟练掌握 iostat、iotop、vmstat、ss、perf 这几个工具,并养成“先看 wa 和 D 状态”的习惯,大部分生产问题都能在 10~30 分钟内找到主要方向。

如果你的业务场景也经常遇到这类问题,欢迎在留言区分享你的负载特征、top 截图或 iostat 输出,我们可以一起分析具体原因。

Telegram
Telegram@IDCSELL