上周线上服务器突然变慢,用户反馈接口响应从200ms飙到2s。
登上服务器一看,load average飙到20多(4核机器),但具体是什么原因导致的?CPU?内存?磁盘IO?网络?
花了2小时才定位到问题:一个定时任务写日志把磁盘写满了,导致数据库疯狂刷盘。
这篇文章把性能排查的完整流程和工具总结一下,下次再遇到类似问题能快速定位。
遇到性能问题,不要慌,按这个顺序排查:
先看整体负载(load average)
**核心原则:从宏观到微观,从现象到本质。**
---
## 二、第一步:看整体负载
### 2.1 uptime - 快速查看负载
```bash
$ uptime
14:30:25 up 45 days, 3:21, 2 users, load average: 8.52, 6.31, 4.12
load average三个数字含义:
第一个:最近1分钟平均负载第二个:最近5分钟平均负载第三个:最近15分钟平均负载怎么判断负载高不高?
对比CPU核心数:
# 查看CPU核心数
$ nproc
4
load average < CPU核心数 → 正常load average ≈ CPU核心数 → 较忙load average > CPU核心数 × 2 → 过载,需要排查
上面的例子,4核机器load到8.52,已经过载了。
$ top
关注几个关键指标:
top - 14:30:25 up 45 days, 3:21, 2 users, load average: 8.52, 6.31, 4.12
Tasks: 203 total, 3 running, 200 sleeping, 0 stopped, 0 zombie
%Cpu(s): 75.2 us, 12.3 sy, 0.0 ni, 8.5 id, 3.2 wa, 0.0 hi, 0.8 si, 0.0 st
MiB Mem : 7821.3 total, 234.5 free, 6543.2 used, 1043.6 buff/cache
MiB Swap: 2048.0 total, 1024.0 free, 1024.0 used. 876.4 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
12345 mysql 20 0 4523412 3.2g 8532 S 156.3 42.1 1234:56 mysqld
23456 www-data 20 0 892340 256m 12340 S 45.2 3.3 567:89 php-fpm
重点看这几行:
| 指标 | 含义 | 异常判断 |
|---|---|---|
us | 用户态CPU | >70% 可能有CPU密集任务 |
sy | 内核态CPU | >30% 可能有大量系统调用 |
wa | IO等待 | >20% 说明磁盘IO有瓶颈 |
id | 空闲 | 越低越忙 |
Mem used | 内存使用 | 接近total需要关注 |
Swap used | 交换分区 | 有使用说明内存不够 |
# 安装
apt install htop # Ubuntu
yum install htop # CentOS
# 运行
htop
htop相比top的优势:
彩色显示,更直观可以鼠标操作可以直接kill进程支持树状显示进程父子关系
$ top
# 看 %Cpu(s) 行的 us + sy 是否接近100%
# 看 id(空闲)是否接近0%
如果
us + sy > 90% 且
id < 10%,基本可以确定是CPU瓶颈。
# 按CPU排序(top默认就是)
$ top -o %CPU
# 或者用ps
$ ps aux --sort=-%cpu | head -20
找到高CPU进程后,分析它在做什么:
方法1:strace 跟踪系统调用
# 跟踪进程的系统调用
$ strace -p <PID> -c
^C
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
85.23 1.234567 123 10000 write
8.45 0.123456 12 10000 read
4.32 0.054321 5 10000 open
方法2:perf 分析CPU热点
# 安装
apt install linux-tools-generic
# 采样30秒
$ perf record -p <PID> -g -- sleep 30
# 查看结果
$ perf report
方法3:看进程的堆栈
# Java进程
$ jstack <PID>
# 通用方法
$ cat /proc/<PID>/stack
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 单个进程CPU 100% | 死循环、算法问题 | 代码review、优化算法 |
| 多个进程CPU都高 | 并发请求过多 | 限流、扩容 |
| sy(系统态)高 | 大量系统调用 | 减少IO操作、优化代码 |
| 短时间CPU飙高 | 定时任务、GC | 错峰执行、调优GC |
$ free -h
total used free shared buff/cache available
Mem: 7.6Gi 5.8Gi 234Mi 123Mi 1.6Gi 1.4Gi
Swap: 2.0Gi 1.0Gi 1.0Gi
重点指标:
available:真正可用的内存(比free更准确)
Swap used:如果不为0,说明物理内存不够了
# 按内存排序
$ ps aux --sort=-%mem | head -20
# 或者用top,按M键切换为内存排序
$ top
# 然后按 M
系统层面:
# 查看内存详细分布
$ cat /proc/meminfo
# 重点看这几项
MemTotal: 8000000 kB # 总内存
MemFree: 200000 kB # 空闲内存
MemAvailable: 1500000 kB # 可用内存
Buffers: 100000 kB # 缓冲区
Cached: 1200000 kB # 页面缓存
SwapTotal: 2000000 kB # 交换分区总量
SwapFree: 1000000 kB # 交换分区空闲
进程层面:
# 查看进程内存详情
$ cat /proc/<PID>/status | grep -E "VmRSS|VmSize"
VmSize: 4523412 kB # 虚拟内存(不重要)
VmRSS: 3321456 kB # 实际物理内存(重要)
# 查看进程内存映射
$ cat /proc/<PID>/smaps | head -50
如果发现某个进程内存持续增长:
# 定时记录内存使用
$ while true; do
echo "$(date) $(ps -p <PID> -o rss=)" >> /tmp/mem.log
sleep 60
done
# 一段时间后分析增长曲线
Java进程:
# 查看堆内存
$ jmap -heap <PID>
# dump堆内存分析
$ jmap -dump:format=b,file=heap.bin <PID>
# 用MAT或VisualVM分析
如果内存真的不够,Linux会启动OOM Killer杀进程:
# 查看被OOM杀掉的进程
$ dmesg | grep -i "out of memory"
$ dmesg | grep -i "killed process"
$ top
# 看 wa(IO wait)是否 > 20%
如果wa很高,基本就是磁盘IO瓶颈。
# 安装
apt install sysstat
# 每2秒刷新一次
$ iostat -xz 2
输出解读:
Device r/s w/s rkB/s wkB/s %util
sda 15.00 250.00 120.00 51200.00 98.50
| 指标 | 含义 | 异常判断 |
|---|---|---|
| r/s | 每秒读次数 | - |
| w/s | 每秒写次数 | - |
| rkB/s | 每秒读取KB | - |
| wkB/s | 每秒写入KB | - |
| %util | 磁盘繁忙度 | >80% 说明磁盘繁忙 |
# iotop需要安装
$ apt install iotop
# 查看IO最高的进程
$ iotop -o
输出:
PID PRIO USER DISK READ DISK WRITE COMMAND
12345 be/4 mysql 0.00 B/s 50.23 M/s mysqld
23456 be/4 root 0.00 B/s 10.45 M/s rsync
# 查看磁盘使用率
$ df -h
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 100G 95G 5G 95% /
# 查看哪个目录占用大
$ du -sh /* | sort -rh | head -20
# 更细的分析
$ du -sh /var/* | sort -rh | head -10
磁盘满了会导致:
数据库无法写入日志无法记录应用崩溃| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| %util接近100% | 磁盘瓶颈 | 换SSD、优化查询 |
| 磁盘空间满 | 日志太多、大文件 | 清理日志、扩容 |
| 随机IO高 | 数据库查询慢 | 加索引、优化SQL |
| 顺序IO高 | 大文件读写 | 错峰执行 |
# 查看所有连接
$ ss -tunap
# 统计各状态连接数
$ ss -s
Total: 1234
TCP: 890 (estab 567, closed 123, orphaned 0, timewait 200)
# 查看TIME_WAIT连接数
$ ss -tan | grep TIME-WAIT | wc -l
TIME_WAIT过多会导致端口耗尽:
# 查看可用端口范围
$ cat /proc/sys/net/ipv4/ip_local_port_range
32768 60999
# 如果TIME_WAIT接近这个范围,需要优化
# 安装iftop
$ apt install iftop
# 查看实时流量
$ iftop -i eth0
或者用更简单的:
# 每秒刷新
$ watch -n 1 "cat /proc/net/dev | grep eth0"
# 抓取特定端口的包
$ tcpdump -i eth0 port 3306 -w mysql.pcap
# 抓取特定IP的包
$ tcpdump -i eth0 host 192.168.1.100
# 用Wireshark分析
# ping测试
$ ping -c 10 目标IP
# 路由追踪
$ traceroute 目标IP
# 更详细的mtr
$ mtr 目标IP
现象:
接口响应从200ms变成2stop看CPU的wa(IO等待)达到40%load average很高排查过程:
# 1. 确认是IO问题
$ iostat -xz 2
# 发现 %util 接近100%
# 2. 找到IO高的进程
$ iotop -o
# 发现是mysql写入很高
# 3. 查看磁盘空间
$ df -h
# 发现根分区使用率99%!
# 4. 找到大文件
$ du -sh /var/* | sort -rh
# /var/log 占了80G
# 5. 具体是哪个日志
$ ls -lh /var/log/*.log | sort -k5 -h
# 发现某个应用日志有60G
解决:
清理日志:
> /var/log/xxx.log配置日志轮转:logrotate监控磁盘使用率告警
现象:
应用每隔几天就会被OOM Killer杀掉重启后又正常,过几天又OOM排查过程:
# 1. 确认是哪个进程被杀
$ dmesg | grep -i "killed process"
# 发现是java进程
# 2. 监控内存增长
$ while true; do
ps -p <PID> -o rss= >> /tmp/mem.log
sleep 300
done
# 发现内存每天增长几百MB
# 3. dump堆内存
$ jmap -dump:format=b,file=heap.bin <PID>
# 用MAT分析,发现某个Map一直在增长
# 4. 代码review
# 发现是缓存没有设置过期时间,一直往里塞数据
解决:
缓存设置过期时间和最大容量定期重启(临时方案)增加内存监控告警现象:
每天固定时间(凌晨2点)系统变慢其他时间正常排查:
# 查看定时任务
$ crontab -l
0 2 * * * /opt/backup.sh
# 查看备份脚本
$ cat /opt/backup.sh
# 发现是全量数据库备份,占用大量IO
解决:
备份任务加nice降低优先级使用增量备份错峰到业务低谷期| 类别 | 工具 | 用途 |
|---|---|---|
| 综合 | top/htop | 实时监控CPU、内存 |
| vmstat | 系统整体状态 | |
| dstat | 综合资源监控 | |
| CPU | top | 查看CPU使用 |
| perf | CPU热点分析 | |
| strace | 系统调用跟踪 | |
| 内存 | free | 内存使用概览 |
| ps aux --sort=-%mem | 内存大户 | |
| pmap | 进程内存映射 | |
| 磁盘 | df | 磁盘空间 |
| du | 目录大小 | |
| iostat | 磁盘IO | |
| iotop | IO进程排序 | |
| 网络 | ss/netstat | 连接状态 |
| iftop | 流量监控 | |
| tcpdump | 抓包分析 |
分享一个我常用的快速排查脚本:
#!/bin/bash
# quick-check.sh - 快速性能检查
echo "========== 系统负载 =========="
uptime
echo -e "
========== CPU使用 =========="
top -bn1 | head -5
echo -e "
========== 内存使用 =========="
free -h
echo -e "
========== 磁盘空间 =========="
df -h | grep -v tmpfs
echo -e "
========== 磁盘IO =========="
iostat -x 1 2 | tail -10
echo -e "
========== CPU占用TOP10 =========="
ps aux --sort=-%cpu | head -11
echo -e "
========== 内存占用TOP10 =========="
ps aux --sort=-%mem | head -11
echo -e "
========== 网络连接统计 =========="
ss -s
echo -e "
========== 最近的系统日志 =========="
dmesg | tail -20
chmod +x quick-check.sh
./quick-check.sh
如果你管理的服务器分布在不同地方(公司机房、云服务器、家里的服务器),远程排查会遇到一些额外的问题。
服务器负载高时,SSH可能连不上或者很卡。
解决方案:
用mosh替代SSH(更抗网络波动):
# 服务器安装
apt install mosh
# 客户端连接
mosh user@server
开启SSH保活:
# ~/.ssh/config
Host *
ServerAliveInterval 30
ServerAliveCountMax 5
服务器多了,一台台登录很麻烦。
方案1:使用tmux/screen保持会话
# 服务器上启动tmux
tmux new -s debug
# 断开后重连
tmux attach -t debug
方案2:批量执行命令
# 使用pdsh并行执行
pdsh -w server[1-10] "uptime"
# 或者用ansible
ansible all -m shell -a "uptime"
有时候服务器在内网,没有公网IP,远程排查就很麻烦。
比如:
家里的开发服务器,在公司想连上去排查问题客户的服务器在内网,需要远程协助排查传统方案:
VPN:配置复杂,维护成本高跳板机:多一跳,延迟高frp内网穿透:需要自己维护服务端我的方案:用星空组网
原理是把多台服务器组成一个虚拟局域网,不管在哪都能直接SSH连接。
# 1. 服务器和本地电脑都装星空组网客户端
# 2. 登录同一账号,加入同一网络
# 3. 服务器获得虚拟IP,比如 10.26.1.10
# 直接用虚拟IP SSH连接
ssh user@10.26.1.10
# 或者配置~/.ssh/config
Host homeserver
HostName 10.26.1.10
User root
优势:
P2P直连,延迟低(比跳板机快)不需要公网IP免费版3设备够用配置5分钟搞定我现在管理的几台服务器(家里、公司、云上)都组在一个网络里,在哪都能直接连,排查问题方便多了。
cp config config.bak
性能排查的核心思路:
先看整体:uptime看负载,top看资源分布定位瓶颈类型:是CPU、内存、磁盘还是网络?找到问题进程:用对应工具排序分析根因:看日志、抓包、分析堆栈解决问题:优化代码、调整配置、扩容记住几个关键阈值:
load average > CPU核心数×2 → 过载CPU wa > 20% → IO瓶颈内存available接近0 → 内存不足磁盘%util > 80% → 磁盘繁忙磁盘使用率 > 90% → 需要清理有问题评论区讨论,这些都是踩坑总结出来的~
我的环境:
Ubuntu 22.04 / CentOS 7常用工具版本:sysstat 12.x、iotop 0.6