很多人排查服务器问题时的第一反应是「服务挂了重启一下」,但真正难缠的故障往往不会挂——网站能打开,只是变慢了;CPU 看起来没满,但响应时间翻倍;磁盘没什么读写,可页面加载要等好几秒。这类「慢但不挂」的问题,靠 top 和 df 是看不出来的,必须深入到两个层面:系统的 性能计数器,以及进程实际在做什么系统调用。
这篇文章把 Linux 服务器指标观测分成两条线来讲。第一条线是「看数字」:怎么用 sar、iostat、vmstat、pidstat 这些工具读出「瓶颈在 CPU、内存、磁盘还是网络」这个关键判断。第二条线是「看行为」:当数字指向某个进程之后,怎么用 strace 和 perf 看清这个进程到底卡在哪一次系统调用、哪一帧调用栈上。两步走完,绝大多数「玄学卡顿」都能定位到具体原因。
一、先建立一个正确的观测框架
Linux 性能分析领域有一个被反复引用的框架,叫做 USE 方法——对每一项资源,分别检查使用率(Utilization)、饱和度(Saturation)、错误(Errors)三个维度:
- 使用率:资源在某个时间窗口内有多少比例的时间是在忙。比如磁盘 90% 的时间都在处理 IO 请求
- 饱和度:资源忙不过来时,有多少工作被排队等待。这是比使用率更重要的指标——CPU 使用率 70% 但运行队列长度为 8,说明已经在排队了;磁盘使用率不高但 IO 平均等待时间很长,也是饱和的表现
- 错误:有没有硬件或驱动层面的报错。网卡丢包、磁盘重试、内存 ECC 错误都属于这一类
要覆盖的资源无非这几项:CPU、内存、磁盘 IO、网络,再加一个「进程本身」。下面按资源逐一讲工具。
二、sar:让系统把历史指标都记下来
前面提到的工具里,如果只能装一个,我建议装 sysstat,因为它带来了 sar——它最大的价值不是实时观测,而是记录历史。故障往往是几分钟前发生的,等你连上服务器,现场已经没了。sar 默认每 10 分钟采样一次,把数据存到 /var/log/sysstat/(Debian/Ubuntu)或 /var/log/sa/(RHEL 系),保留最近 7~30 天,于是你可以「回到过去」看当时发生了什么。
# 安装并启用
apt-get install -y sysstat
sed -i 's/^ENABLED="false"/ENABLED="true"/' /etc/default/sysstat
systemctl enable --now sysstat
# 回看今天 CPU 的历史数据(每 10 分钟一行)
sar -u
# 看指定时间的 CPU 数据(比如昨天下午 15 点前后的趋势)
sar -u -s 14:30:00 -e 15:30:00 -f /var/log/sysstat/sa$(date -d yesterday +%d)
# 内存:注意 %commit 和 kbmemfree 的区别
sar -r
# 磁盘 IO:重点看 %util 和 await 两列
sar -d
# 网络:看丢包和错误计数
sar -n DEV,Edev
读 sar -r 的输出时要注意,kbmemfree 低不等于内存不足。Linux 会把空闲内存全部拿去做文件缓存(buff/cache),真正该看的是 %commit(已提交内存相对总量的比例,代表应用申请了多少)和 kbmemavailable(估算的可用内存,包含可回收的缓存)。只盯着 free 数字小就急着去清缓存,是新手最常见的误判。
读 sar -d 时,%util 接近 100% 表示设备几乎一直在处理请求,await 是单个 IO 请求的平均耗时(含排队时间)。如果 await 明显高于设备正常水平(机械盘个位数毫秒级、SSD 亚毫秒级),说明已经饱和。云服务器还要注意一个特殊情况:%util 在高并发下会「虚高」——因为底层存储是多块物理盘,虚拟机的队列一直在灌,%util 到了 100% 但实际吞吐还有余量,这时候更该看 await 和应用的响应时间。
三、iostat:定位磁盘瓶颈的第一把工具
iostat 也来自 sysstat 包,是实时观察磁盘最常用的工具。运维场景下最实用的用法是「带间隔连续采样」:
# 每 2 秒采样一次,共 10 次,同时显示扩展统计
iostat -xz 2 10
-x 输出扩展指标,几个必须认识的列:
- r/s、w/s:每秒读、写请求数(IOPS)
- rkB/s、wkB/s:每秒读、写的数据量(吞吐)
- r_await、w_await:读、写请求的平均响应时间,单位毫秒。这两个是最重要的指标
- aqu-sz(旧版叫 avgqu-sz):平均请求队列长度。大于 1 说明请求已经在排队
- %util:设备有 IO 请求在处理的时间比例
判断逻辑可以总结成一句话:如果 await 明显升高,而 IOPS 没有相应提高,说明设备已经饱和,再加压力只会让响应更慢。此时要做的是减少 IO 或换更快的存储,而不是继续优化程序。
排查「谁在读写磁盘」,iotop 比 iostat 更直观,它按进程列出实时读写速率:
apt-get install -y iotop
iotop -oPa # -o 只显示有 IO 的进程,-P 按进程聚合,-a 显示累计而非瞬时
这个命令几乎能立刻回答「是哪个进程把磁盘吃满了」——常见的元凶包括:MySQL 在做大批量写入或没有索引的查询导致全表扫描、日志文件疯狂刷盘、备份任务在跑、Docker 层在解压镜像。
四、CPU:分清用户态、内核态和等待
关于 CPU,先纠正一个常见误解:top 里的 load average 包含了一部分不可中断睡眠(D state)的进程,而 D 状态大多是在等磁盘 IO。所以 load 高不一定代表 CPU 忙——磁盘卡住也会把 load 拉高,这是一个非常重要的判断分叉点。
用 vmstat 区分这件事最方便:
vmstat 2 10
关键几列:
- r:运行队列长度。持续大于 CPU 核心数说明 CPU 真的不够用
- b:阻塞(等待 IO)的进程数。这个数字大,配合高 load,说明瓶颈在 IO 而不在 CPU
- us、sy:用户态、内核态 CPU 占比。sy 高通常意味着系统调用频繁、上下文切换多或网络包处理重
- wa:等待 IO 的 CPU 时间占比。wa 高是磁盘瓶颈的强信号
- si、so:换入换出内存量。这两个非零且持续,说明物理内存不足,已经开始用 swap,性能会断崖式下降
看单个进程的 CPU 细分,用 pidstat(同样来自 sysstat):
# 每 2 秒输出一次,按进程显示 CPU 使用,-w 显示上下文切换,-t 显示线程
pidstat -u -w -t 2 10
pidstat -w 输出的 cswch/s(自愿上下文切换)和 nvcswch/s(非自愿,被强制抢走 CPU)很有价值:自愿切换高说明进程在频繁等待资源(锁、IO),非自愿高说明 CPU 争抢严重。这两个数字能把「为什么这个进程慢」的问题往前推一大步。
五、strace:看清进程到底卡在哪
前面所有工具告诉你的都是「哪个进程有问题」,但不知道「这个进程在做什么」。这时就该 strace 出场了——它追踪进程发起的每一次系统调用,是 Linux 排障里最锋利的刀之一。
一个特别实用的组合是「找到卡住的进程,看它当前卡在什么调用上」:
# 1) 先找出可疑进程
ps aux --sort=-%cpu | head -10
# 2) 追踪它 10 秒内所有系统调用(-f 跟线程,-tt 带时间戳)
strace -f -tt -p 12345 -o /tmp/trace.log &
sleep 10
kill %1
# 3) 看耗时最长的调用
sort -k3 -rn /tmp/trace.log | head -20
更直接的用法是统计模式,它会汇总每类系统调用的次数和耗时,一眼看出瓶颈:
strace -f -c -p 12345
# 或者对一次性命令整体统计
strace -f -c php /www/wwwroot/blog/index.php
几种典型的「卡点」及其含义:
- 大量
poll/epoll_wait且耗时很长:进程在等网络 IO 或事件,通常是后端服务(数据库、Redis、上游 API)响应慢 futex调用多且阻塞时间长:多个线程在争抢锁,典型的多进程/多线程模型下的锁竞争open/stat调用数量异常多:程序在疯狂地找文件,通常是 include 路径配置不对、或者缓存失效导致重复探测文件read/write单次调用耗时极长:磁盘 IO 慢或者网络阻塞- 反复
connect到某个 IP 失败或超时:某个下游依赖不可用,注意 DNS 解析也可能是元凶(connect之前有大量sendto到 53 端口)
用 strace 有两个必须注意的代价:它会显著拖慢被追踪的进程(生产环境慎用 -f 长时间跟),以及它需要相应权限(root 或 ptrace 能力)。所以正确姿势是短时间、有目标地采样,抓几秒到几十秒就停,别挂着跑一整天。
六、tcpdump:网络问题的第一条证据
网络卡顿的排查不走 strace,走抓包。第一步通常是看连接状态分布,用 ss:
# -s 看各状态连接数量汇总,-i 看 TCP 内部指标(重传、RTT)
ss -s
ss -tlnp # 监听端口与对应进程
ss -tan state time-wait | wc -l # TIME_WAIT 堆积量
ss -i # 每个连接的 RTT、重传计数
ss -i 的输出里有 rtt(往返时间)和 retrans(重传次数)。重传率超过 1% 就值得深挖了,通常指向链路质量或对端问题。TIME_WAIT 数量如果达到几万,要考虑开启 net.ipv4.tcp_tw_reuse 并检查是不是短连接太频繁。
要进一步看具体报文,用 tcpdump 抓一小段。核心用法是限定过滤条件,避免抓到海量无关流量:
# 抓指定端口、指定主机的报文,写文件供 Wireshark 分析
tcpdump -i eth0 -nn -s 0 -w /tmp/cap.pcap 'host 10.0.0.5 and port 3306' -c 500
# 直接看 SYN/SYN-ACK 是否正常往来(判断连不上还是连上后慢)
tcpdump -i eth0 -nn 'tcp[tcpflags] & (tcp-syn|tcp-fin) != 0 and host 10.0.0.5'
# 看有无重传(同一 seq 重复出现)
tcpdump -i eth0 -nn 'tcp and host 10.0.0.5' -c 200
判断思路很简单:如果只有 SYN 没有 SYN-ACK,是对方不可达或被防火墙拦;如果三次握手正常但后续很慢,看是不是有大量重传;如果有大量 RST,看是不是对端连接池满了或者中间设备在重置连接。
七、把观测串成一条排查路径
工具太多容易晕,实际排障时按下面的顺序走一遍,基本不会漏:
- 第一步,确认现象的范围:是整台机器都慢,还是只有一个站点慢?是所有人都慢,还是特定地区/运营商慢?这一步决定后面查哪一层
- 第二步,看系统级指标:
vmstat 2 10看 r/b/wa/si/so,判断瓶颈是 CPU、IO 还是内存;sar -d看历史磁盘数据;sar -n DEV看网络有没有异常 - 第三步,定位到进程:
pidstat -u -w -t 2或iotop -oPa,找出消耗资源最多的进程 - 第四步,看进程行为:
strace -f -c -p PID统计系统调用,判断它卡在等网络、等锁、还是等磁盘 - 第五步,若是网络问题就抓包:
ss -i看重传,tcpdump抓具体报文 - 第六步,留证据再动手:把关键输出重定向到文件保存,再考虑重启或改配置。很多故障重启后就再也复现不了,现场证据就是唯一的线索
八、几个容易踩的坑
坑一:在生产机上跑重型工具。 perf top、长时间的 strace -f、不带过滤条件的 tcpdump,本身就会消耗可观的 CPU 和磁盘 IO,可能把小问题变成大事故。所有观测都应该限制时长、加过滤条件、必要时降采样。
坑二:只看瞬时值。 很多问题有周期性——备份任务每小时跑一次、日志切割每天一次、某个爬虫定时来扫。只用 top 看一眼是抓不到的,必须靠 sar 的历史数据或者长时间采样(比如 iostat -xz 2 100 覆盖几分钟)才能看出规律。
坑三:把「内存空闲少」当成内存不足。 如前所述,缓存占用的内存是可以回收的,看 MemAvailable 而不是 MemFree。真正要警惕的信号是 si/so 持续非零和 OOM Killer 的日志记录。
坑四:工具没装就慌。 这些工具在生产环境往往没预装,出事时发现 apt install 都连不上(DNS 或网络也出问题了)就很被动。建议在服务器初始化阶段就把 sysstat、iotop、strace、tcpdump、mtr 装好,并把 sysstat 的采样打开——这是投入产出比最高的一件事,平时不占资源,出事时有历史数据。
坑五:忽略容器视角的失真。 如果你在 Docker 容器里看 /proc 的指标,看到的是宿主机的数据而不是容器的。要知道容器自己用了多少 CPU、内存,得看 cgroup 里的 /sys/fs/cgroup/ 数据,或者从宿主机上按容器 ID 过滤。这一点在混合部署多站点时特别容易误判。
九、小结
Linux 服务器性能观测的本质,不是记住多少条命令,而是建立一条清晰的推理链:资源 → 使用率与饱和度 → 具体进程 → 进程的系统调用行为。sar/iostat/vmstat/pidstat 负责前两环,strace 和 perf 负责后两环,tcpdump 负责网络这个特例。
真正把这条链路练熟的标志是:看到 load 高时,你会先问「是 CPU 忙还是 IO 等」,而不是直接去重启服务;看到响应慢时,你会先抓一段执行现场,而不是凭感觉改配置。排障能力的差距,往往就体现在这几个「先问什么」的瞬间。