VOL. 01 · PERFORMANCE & RESOURCES

Linux 性能与
资源故障

THE OPS FIELD MANUAL
CPU · Memory · Disk · IO
root@ops:~#
top vmstat free df / du iostat dmesg pidstat

Linux 性能问题的本质是资源争抢:CPU、内存、磁盘 IO,三个核心资源中任何一个达到瓶颈,系统都会"卡"。但卡的表现千差万别:负载高未必 CPU 忙,内存少未必真不够,磁盘满未必 du 算得出。诊断的关键是分清"使用率"和"饱和度"

性能分析三板斧 · USE Method

Netflix 性能大师 Brendan Gregg 提出的诊断框架。每遇到性能问题,对每个资源(CPU/内存/磁盘/网络)依次问这三个问题——能覆盖 80% 以上的性能故障定位。

U
UTILIZATION
使用率 — 资源在工作的时间占比。如 CPU 80% 在跑任务。
S
SATURATION
饱和度 — 资源不够用,有任务在排队等待。如 Load Average 高、Swap 频繁。
E
ERRORS
错误数 — 资源访问失败的次数。如磁盘 IO 错误、网卡丢包。
01

CPU 使用率飙高,定位元凶

USER / SYSTEM / IOWAIT / SOFTIRQ DIAGNOSIS
高频 必会
  • top 看到 CPU 使用率 90%+,系统响应变慢
  • 有时是某个进程独占一个核,有时是所有核都满
  • SSH 登录卡、命令执行延迟
# top 看 %Cpu(s) 那一行,这几个字段含义不同,根因完全不同
top
# 或更精确
mpstat -P ALL 1
字段含义对应根因
us (user)用户态 CPU应用进程跑得猛(Java/Python 死循环、计算密集)
sy (system)内核态 CPU系统调用频繁(IO/上下文切换/锁竞争)
wa (iowait)等 IO 完成磁盘慢,CPU 在等数据(不是 CPU 的锅)
si (softirq)软中断网卡大流量、中断风暴
hi (hardirq)硬中断硬件中断频繁
st (steal)被 hypervisor 偷走云主机超卖(虚拟机才有)
# 1. 找进程
top          # 按 P 按 CPU 排序
ps aux --sort=-%cpu | head

# 2. 找到具体哪个线程(Java/多线程应用必看)
top -Hp <pid>          # 按 P 排序,看 PID 列(其实是 TID)

# 3. Java 应用:把 TID 转 16 进制,在 jstack 里搜
printf "%x\n" <tid>      # 比如得到 1a2b
jstack <pid> | grep -A 20 "nid=0x1a2b"
# 直接看到死循环或热点代码栈

# 4. 通用方法:perf 抓函数热点
perf top -p <pid>
# 上下文切换次数(看 cs 列)
vmstat 1
# cs > 几万/s 通常是问题,看是不是线程太多了

# 按进程看上下文切换
pidstat -w 1
# cswch/s 自愿切换(锁等待) nvcswch/s 被动切换(CPU 抢占)

# 系统调用追踪
strace -c -p <pid>      # 几秒后 Ctrl+C,看哪个系统调用占比高
# wa 高了直接跳去看磁盘 IO,见 Case 08
iostat -x 1
iotop           # 看哪个进程在猛读写
# 看中断分布
cat /proc/softirqs    # 大流量服务器看 NET_RX 是否暴涨
cat /proc/interrupts

# 网络中断集中在 CPU0,负载不均
cat /proc/irq/<irq-num>/smp_affinity
# 改 affinity 把中断分散到多核(RPS/RSS)
陷阱 · wa 高 ≠ CPU 不够。iowait 是"CPU 闲着但任务在等 IO 完成"的状态,加 CPU 没用,要加磁盘性能或优化 IO 模式。
02

Load Average 很高,但 CPU 空闲

D-STATE PROCESSES BLOCKING ON I/O
高频 进阶
  • uptime 显示 load average 高(比如 8 核机器 load 30+)
  • 但 top 里 %us、%sy 都不高,%wa 可能高
  • 系统响应慢,但没看到哪个进程在猛跑 CPU

Linux 的 Load Average 不是"CPU 使用率",而是 正在运行 + 不可中断睡眠 (D 状态) 的进程数。D 状态进程不消耗 CPU,但会拉高 load。

  • 磁盘 IO 阻塞:大量进程等磁盘读写(最常见)
  • NFS / 网络存储不响应:挂载点卡死,访问的进程全 D 住
  • 内核 bug:某个驱动卡住进程(罕见)
# 1. 找出所有 D 状态进程
ps -eo state,pid,user,cmd | grep "^D"
# 或更直观
ps aux | awk '$8 ~ /^D/ {print}'

# 2. 实时看 D 状态进程数
vmstat 1
# 关注 procs 段的 b 列(blocked on I/O)

# 3. 看具体卡在哪个系统调用
cat /proc/<pid>/stack       # 内核栈,看堵在哪
cat /proc/<pid>/wchan       # 进程在等什么

# 4. 检查所有 mount 点是否健康
df -h
# 卡住的 NFS 挂载点会让 df 也 hang 住!

# 5. 测试每个挂载点能不能访问
timeout 3 ls /mnt/nfs1 && echo OK || echo HANG
D 状态杀不掉 · D 状态的进程不响应任何信号,kill -9 都没用。必须等它等的资源回来(IO 完成、NFS 恢复),或者重启机器。
预防 NFS 卡死 · 挂载时加 soft,intr,timeo=30 选项,服务端不响应时客户端会超时报错而不是死等。hard 模式下 NFS 服务挂了能让整台机器跟着挂。
03

内存被吃光,但找不到大户

CACHE / BUFFER / RSS / SLAB MEMORY ACCOUNTING
高频 必会
free -h
              total        used        free      shared  buff/cache   available
Mem:           15Gi        12Gi       300Mi        50Mi       2.7Gi       2.5Gi
Swap:         2.0Gi       500Mi       1.5Gi
含义正确解读
used已用内存包含 cache,看起来吓人但不全是真用
free完全空闲低不代表危险,Linux 喜欢用满 cache
buff/cache缓存可被回收,不算"真正占用"
available实际可用这才是判断的关键!低于 10% 才警惕
# 按 RSS(实际物理内存)排序找进程
ps aux --sort=-rss | head

# top 里按 M 排序也行
top           # 按 M

# 单个进程详细内存使用
pmap -x <pid> | tail -1
指标含义关注度
VSZ虚拟内存(包括没用到的)低 — 虚的,JVM 会很大但不一定真占
RSS常驻物理内存(实际占用)高 — 这才是真正吃内存的量
SHR共享内存中 — 多进程共享的库

所有进程 RSS 加起来远小于 used 内存——这种情况看 内核内存:

# slab 内核对象缓存(dentry/inode 等)
cat /proc/meminfo | grep -E "Slab|SReclaim"

# slab 详细分类
slabtop      # 看哪个 slab 占用大

# 大量小文件操作会让 dentry cache 暴涨,手动清
echo 2 > /proc/sys/vm/drop_caches  # 清 dentry 和 inode cache
echo 3 > /proc/sys/vm/drop_caches  # 清所有 cache
drop_caches 不是常规操作 · 清完 cache 后,文件读取性能会暂时下降。生产环境只在诊断或确实有问题时用,不要写进定时任务。正常的 cache 占用是好事,不要看到 used 高就慌。
04

OOM Killer 杀了我的进程

OUT-OF-MEMORY KILLER MECHANICS
高频 必会
  • 应用进程突然消失,没有日志、没有 core dump
  • dmesg 里有 "Out of memory: Killed process xxx"
  • /var/log/messages 出现 oom-killer 相关记录
# 1. 看 OOM 历史
dmesg -T | grep -i "killed process"
grep -i "oom" /var/log/messages

# 2. 详细的 OOM 现场(很重要,有内存快照)
dmesg -T | grep -B 2 -A 20 "oom-killer"
# 输出会列出当时所有进程的内存占用,排序后的 oom_score

内核根据 oom_score 选受害者,分数越高越先被杀。计算因素:

  • 进程占用内存大小(主要因素)
  • oom_score_adj(可手动调整,-1000 到 1000)
  • root 进程会有一些豁免
# 查看进程的 oom_score
cat /proc/<pid>/oom_score
cat /proc/<pid>/oom_score_adj

# 保护重要进程不被 OOM(设为 -1000 永不杀)
echo -1000 > /proc/<pid>/oom_score_adj

# 反过来,让某进程优先被杀
echo 1000 > /proc/<pid>/oom_score_adj
# 1. 加内存(最直接)

# 2. 关闭过度内存提交
sysctl vm.overcommit_memory=2
# 0=启发式(默认), 1=允许超量, 2=严格控制

# 3. JVM 应用必须限制堆大小
# -Xmx 不要超过物理内存的 70%,留出空间给堆外

# 4. 找出内存泄漏(应用层问题)
pmap -x <pid>
# 观察一段时间 RSS 是否持续增长
云服务器特别注意 · 默认很多镜像没有 swap,一旦内存不够就直接 OOM。生产环境建议配置少量 swap(物理内存的 10-25%)作为缓冲,设置 swappiness=10 减少不必要的 swap。
05

Swap 使用异常,系统变慢

SWAPPINESS AND SWAP THRASHING
中频 入门
# 1. 看 swap 总体使用
free -h
swapon -s

# 2. 看哪些进程用了 swap
for file in /proc/*/status; do
  awk '/VmSwap|Name/{printf $2 " " $3}END{print ""}' $file
done | sort -k 2 -n -r | head

# 3. 实时监控 swap in/out
vmstat 1
# 看 si(swap in)和 so(swap out)列,持续不为 0 就是 thrashing
# 当前值(默认 60,值越高越倾向于使用 swap)
cat /proc/sys/vm/swappiness

# 数据库/Redis 等内存敏感服务,建议调到 1-10
sysctl -w vm.swappiness=10

# 永久生效,写入 /etc/sysctl.conf
echo "vm.swappiness = 10" >> /etc/sysctl.conf

# 临时禁用 swap(谨慎,可能触发 OOM)
swapoff -a
swapon -a    # 重新启用
Swap Thrashing 信号 · vmstat 看到 si/so 持续大于 0,top 里 wa% 高,系统卡顿到无法登录——这是已经在频繁换页,内存严重不足。要么紧急加内存,要么找出泄漏进程 kill 掉。
06

磁盘满了,但 du 找不到大文件

DELETED-BUT-OPEN FILES HOLDING SPACE
高频 进阶
  • df -h 显示某个分区 95%+,几乎满了
  • du -sh / 加起来远小于 df 显示的占用
  • 删除日志文件后,df 显示空间不变

Linux 删除文件的机制:rm 只是从目录里删除引用,如果还有进程持有这个文件的 fd,文件实际不会被释放,但 du 看不到它(目录里没了),df 却显示空间被占。

# 1. 确认是不是这个问题
df -h          # 看分区使用
du -sh /*       # 加起来对比,差距大就是删除未关闭

# 2. 找出"已删除但被占用"的文件(关键命令!)
lsof | grep "deleted"
# 或更精确
lsof +L1      # 列出链接数为 0 的文件(已删除但被打开)

# 3. 输出示例
# java  12345 root  3w  REG  8,1  10737418240 /var/log/app.log (deleted)
#                                  ↑ 10GB 占着空间
# 方案 A:重启占用文件的进程(最干净)
systemctl restart <service>

# 方案 B:不能重启服务,用 /proc 清空文件内容
# 比如 lsof 显示 java 12345 持有 fd 3
: > /proc/12345/fd/3
# 文件被截断为 0,空间立即释放
# 但应用还在写,新写入会从 0 开始

# 方案 C:重启大法(最后手段)
reboot
常见场景 · 应用日志被运维直接 rm 删除,但应用没有重新打开文件(没用 logrotate 的 copytruncate)。正确做法:不直接删日志,而是 truncate -s 0 app.log 清空,或者发 SIGUSR1 让应用重新打开日志文件。
预防 · 用 logrotate 管理日志,配置 copytruncate 选项,自动处理这个问题。
07

磁盘没满,但写文件报"空间不足"

INODE EXHAUSTION
中频 进阶
  • 报错 "No space left on device",但 df -h 显示空间充足
  • 无法创建新文件,删除文件后又能创建一个

每个文件占用一个 inode,文件系统创建时 inode 数量就固定了。海量小文件场景(邮件、缓存、session)会先把 inode 用完,空间还很多但写不下。

# 1. 看 inode 使用
df -i
# 看 IUse% 列,接近 100% 就是这个问题

# 2. 找哪个目录小文件最多
for i in /*; do echo -n "$i: "; find $i 2>/dev/null | wc -l; done | sort -t: -k2 -n

# 或者一层一层往下找
find /var -xdev -type f | cut -d/ -f 1-4 | sort | uniq -c | sort -rn
# 1. 直接删除小文件大户(找到后)
find /var/spool/postfix -type f -delete

# 2. 文件太多,rm * 会爆 argument list too long
find /path/to/dir -type f -delete
# 或
ls | xargs -n 100 rm
无法增加 inode · ext4 文件系统的 inode 数量在 mkfs 时就定了,不能动态加。inode 满了只能删文件,或者把数据迁走重新格式化(指定更高的 inode 密度 -N)。XFS 默认动态分配,不容易遇到这问题。
08

磁盘 IO 瓶颈,iostat 怎么读

DISK PERFORMANCE METRICS
中频 进阶
# 看磁盘性能(关键命令)
iostat -x 1
# -x 显示扩展信息,1 = 每秒刷新
字段含义警戒值
%util磁盘繁忙度> 80% 接近饱和
awaitIO 平均等待 ms> 20ms 慢,> 100ms 严重
r/s, w/s每秒读写次数对比硬件标称 IOPS
rkB/s, wkB/s每秒读写量对比带宽上限
avgqu-sz平均队列长度> 1 有排队,> 10 严重
svctm服务时间已废弃,别看
iotop            # 类似 top,但看 IO
iotop -o         # 只显示有 IO 的进程

# 或者 pidstat
pidstat -d 1
# kB_rd/s 读速率, kB_wr/s 写速率
存储类型4K 随机 IOPS顺序带宽典型延迟
机械硬盘 7200rpm~150~150 MB/s10-15 ms
SATA SSD~10K~500 MB/s0.1 ms
NVMe SSD~100K+~3500 MB/s< 0.1 ms
NFS(千兆网)~5K~100 MB/s1-5 ms
实测磁盘性能 · 用 fio 测,别用 dd(不准):
fio -name=test -ioengine=libaio -direct=1 -rw=randread -bs=4k -size=1G -iodepth=32
09

Load Average 该怎么看

UNDERSTANDING THE 1/5/15 MINUTE METRICS
高频 入门
uptime
10:23:01 up 30 days,  3:21,  2 users,  load average: 2.05, 4.50, 8.10
  • 第一个:过去 1 分钟平均负载
  • 第二个:过去 5 分钟平均负载
  • 第三个:过去 15 分钟平均负载
1m / 5m / 15m含义处理
2.0 / 4.5 / 8.1负载在下降(峰值过了)观察即可
8.1 / 4.5 / 2.0负载在上升(故障中!)立即排查
5.0 / 5.0 / 5.0稳定高负载正常业务,看是否符合预期

load 的合理上限 ≈ CPU 核数。8 核机器 load 持续 > 8 才算饱和。

nproc                  # 看核数
cat /proc/cpuinfo | grep processor | wc -l
误区 · load 高 ≠ CPU 忙。Linux 的 load 包含 D 状态进程(等 IO 的),磁盘慢也会让 load 飙高但 CPU 闲。一定要结合 top 看 CPU 各分量,见 Case 02。

load 高排查决策树

  1. 看 1m/5m/15m 趋势,确定是上升还是稳定
  2. top 看 %CPU 各分量:us/sy 高 → 跑 Case 01
  3. top 看 %wa 高 → 跑 Case 08 看 IO
  4. %CPU 都不高但 load 高 → 跑 Case 02 找 D 状态进程
  5. vmstat 1 看 r 列(运行队列)和 b 列(阻塞队列)
CONTINUE TO NEXT VOLUME
Vol.2 网络与进程故障