拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Linux性能排查实战手册:CPU、内存、IO、网络四维故障定位指南

Linux性能排查实战手册:CPU、内存、IO、网络四维故障定位指南

Linux 服务器出问题的时候,最怕的不是问题本身,是不知道怎么下手。CPU 飙升、内存吃紧、磁盘 IO 卡成狗、网络连接一堆超时,每一件都够运维喝一壶的。这些年我排查过不少生产环境的问题,最大的体会是:性能排查不是靠某一个神仙命令,而是靠一套从现象到根因的思维方法。这篇东西就是围绕 CPU、内存、IO、网络四个维度,把我平时实际在用的命令、判断逻辑和踩坑经验整理成一份能直接照着操作的手册。

文章里不会讲太多纸上谈兵的理论,重点放在“现场怎么查、数据怎么读、问题怎么定位”上。适合刚接手服务器运维的初级工程师,也适合那些已经会敲几个命令但遇到复杂故障还是发怵的朋友。每一段我都会配上实际输出的解读和判断依据,你完全可以把它当成工作台旁边的速查参考。

1. 排查前的准备:先理顺思路再敲命令

很多人一上来就敲top,看到 CPU 100% 就开始找进程,找到进程就开始 kill,结果问题反复出现。这不是命令的问题,是思路的问题。性能排查首先要搞清楚三件事:现象是什么、影响范围多大、期望的恢复手段是什么。

1.1 四步走的排查流程:现象、量化、定位、验证

我习惯把整个排查过程拆成四个阶段,顺序基本不能乱。

第一步是观察现象。这一步只做记录,不做任何变更。比如“应用响应变慢”“页面加载超时”“数据库连接池被打满”,这些是现象描述,注意不要在这个过程中加入主观猜测,比如“可能被攻击了”“可能是慢查询”,猜测会干扰后面的判断。

第二步是量化问题。把模糊的“卡”“慢”“打不开”转成具体数字。CPU 使用率是多少、内存还剩多少、磁盘 util 是否跑满、网络重传率是多少。没有数字支撑的排障等于盲人摸象,这也是我一直强调必须先做量化的原因。

第三步是定位根因。根据量化的结果,把排查范围一层层缩小。比如 CPU 高,缩小到是用户态高还是内核态高;用户态高,再用pidstat或perf缩小到具体的进程和函数;进程找到了,再看它的调用栈和日志。这是一个漏斗结构,每一层都有相应的命令和判断标准。

第四步是验证恢复。做了调整之后,重新观察第一步里的现象是否消失,同时确认没有引入新问题。比如你清了 cache,内存是释放了,但如果应用的读写性能因此下降,这就不是一个合格的变更。

1.2 工具选型的两个原则:全局优先,先看系统再看进程

Linux 下的排查工具非常多,top、free、iostat、netstat、ss、tcpdump、perf、strace,每一类都能讲一大篇。但真到现场,我会遵循两个原则来选工具。

原则一,先看全局再看局部。不要一上来就盯进程,先看整个系统的水位。top看整体负载,vmstat看 CPU/内存/IO 的综合状况,iostat看磁盘整体利用率,sar看历史趋势。全局数据能帮你建立坐标系,知道瓶颈到底在哪一层。

原则二,能看内核态别只看用户态。很多问题表面上是进程行为,内核里可能完全是另一回事。比如进程卡住,用户态毫无输出,但/proc/pid/status里的状态是 D(不可中断睡眠),说明它在等 IO 完成;这时候你去看进程日志永远查不出来。

1.3 建立基线:没有基线的排查没有意义

基线这个词听着像大厂方法论,其实特别实际。你得知道你系统正常情况下 CPU 负载是 1 还是 10,内存占用是 30% 还是 80%,磁盘 util 是 5% 还是 60%。同一台机器,同样的指标,在不同的业务场景下含义完全不同。

我有一个习惯:新机器上线后跑一周sar,把各项核心指标记录下来存成文件。不用专门搭监控系统,sar -o加 cron 就够了。后面排查时打开历史数据一看,就能快速判断现在的异常是“突然升高”还是“长期缓慢爬升”,这两个问题的方向完全不同。没有基线,就永远只能凭感觉做判断,这是排障中最容易翻车的一环。

2. CPU 维度:从 load average 到上下文切换

CPU 排查是所有性能问题的重灾区。一方面 CPU 指标直观,谁都会看;另一方面,CPU 的隐藏信息非常多,同一个“CPU 高”背后可能有完全不同的故事。

2.1 先分清 load average 和 CPU 使用率

很多人把 load average 当成 CPU 使用率,这是新手最常见的一个误解。load average 是运行队列中处于可运行状态和不可中断状态的进程平均数量,它包含等待 CPU 的进程,也包含等待 IO 的进程。而 CPU 使用率是 CPU 处于非空闲状态的时间比例,两者并不完全对应。

看一个实际例子,uptime输出:

$ uptime 14:32:11 up 23 days, 4:21, 2 users, load average: 8.12, 6.54, 3.20

load average 的三个数字分别对应 1 分钟、5 分钟、15 分钟的平均负载。如果 1 分钟的值显著高于 15 分钟,说明是最近才突然升高的;如果三个值差不多,说明系统已经持续高负载一段时间了。但注意,不能只看这个数字本身,还要结合 CPU 核数来判断。8 核的机器 load 8 和 2 核的机器 load 8 完全是两个概念。

判断 CPU 是否真的是瓶颈,用vmstat看r列(运行队列)和b列(阻塞进程数):

$ vmstat 1 5 procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 6 0 0 812456 2048 3289476 0 0 0 26 2567 48900 34 12 54 0 0

r持续大于 CPU 核数,意味着 CPU 确实不够用了;如果r不大但wa(等待 IO)很高,那 CPU 空闲但进程在等磁盘,问题在 IO 层,加 CPU 核心数根本没有用。

2.2 top、mpstat、pidstat 组合定位 CPU 飙升

定位 CPU 飙升的进程,top是最快的入口:

$ top -bn1 | head -20 top - 14:35:22 up 23 days, 4:24, 2 users, load average: 8.12, 6.54, 3.20 Tasks: 356 total, 2 running, 354 sleeping, 0 stopped, 0 zombie %Cpu(s): 34.2 us, 12.3 sy, 0.0 ni, 52.8 id, 0.7 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem : 16314344 total, 812456 free, 1234568 used, 14265320 buff/cache PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 8301 work 20 0 12.456g 2.156g 14856 S 156.3 13.2 234:42.12 java 1024 root 20 0 321456 78564 2236 S 12.6 0.5 12:02.33 dockerd

这里有一个判断要点:%CPU超过 100 说明进程用了多核,TIME+是累计 CPU 时间。如果累计时间很高但当前 %CPU 不高,说明它历史上消耗了大量 CPU;如果当前 %CPU 高且 TIME+ 也在快速增长,那就是现场问题。

top是整机视角,要确认某进程具体在哪些 CPU 核上运行、是不是存在 CPU 绑核失衡,用mpstat -P ALL 1看每个核的独立使用率。生产环境经常会出现“整机 CPU 不高,但某个核跑满”的情况,比如网卡多队列中断绑定不均、某些线程密集调度到同一个核上,这种情况只看整机数据根本发现不了。

定位到具体进程后,还有一层需要继续深挖:这个进程到底在干什么。pidstat -p PID 1按进程输出 CPU 占用和时间片分布,pidstat -p PID -t 1进一步细化到线程级。Java 应用线程打满了,就要用jstack导线程栈;C/C++ 应用可以直接用perf top -p PID看热点函数。

2.3 上下文切换高:一个容易被忽略的 CPU 杀手

CPU 使用率不高但系统响应慢,这种情况十次里有八次是上下文切换惹的祸。上下文切换包括进程切换、线程切换和中断处理,每一次切换都有成本,切换频率过高会把 CPU 时间消耗在“换人”而不是“干活”上。

用vmstat的cs列(context switch)和in列(interrupt)来观察。我处理过一个实际案例,某服务每隔几秒就出现一次明显卡顿,top看 CPU 使用率不到 20%,但vmstat里cs高达 30 万以上。进一步用pidstat -w 1查看到底是哪个进程在触发切换,发现是一个使用 epoll 的事件驱动框架因为超时参数设置不合理,导致大量短连接反复唤醒,上下文切换被拉爆。修改超时策略后,卡顿立即消失。

查看每个进程的上下文切换量:

$ pidstat -w -p 8301 1 Linux 5.4.0-26-generic (hostname) 07/12/2025 _x86_64_ (8 CPU) 14:36:20 PID Cswch/s nvcswch/s Command 14:36:21 8301 125.00 1890.00 java

Cswch/s是自愿切换(比如等待 IO、锁),nvcswch/s是非自愿切换(时间片耗尽被抢占)。非自愿切换占比高,往往是线程数太多或线程频繁争抢 CPU;自愿切换高,可能是锁竞争或 IO 等待。两类问题处理方式完全不同,锁竞争要看代码,线程太多就要考虑协程化或者调线程池大小。

2.4 高负载低 CPU 的一个排障实录

有一年我们线上某个 Redis 集群节点出现诡异现象:客户端超时频发,但top显示 CPU 使用率只有 15%,load average 却到了 20 多。我的第一反应不是 CPU 不够,而是有进程卡在 IO 上了。

用vmstat 1一看,wa列确实不高,但是b列也就是阻塞进程数持续有 5-8 个。再逐个排查进程状态,发现有个备份脚本对 Redis 的数据目录做了全量tar,把磁盘带宽占满了,Redis 的持久化子进程在等磁盘写入,导致主进程阻塞。CPU 完全闲着,load average 却降不下来,因为 load 统计了不可中断睡眠状态(D 状态)的进程。

这个案例里有两条经验值得记住:一是 load 高不一定是 CPU 问题,二是看到 load 高先看一眼vmstat的b列和/proc/loadavg里的 running 与 blocked 状态。我在生产环境设过一个约定:load 高时先跑vmstat 1 3,r列高才是 CPU 问题,b列高优先查磁盘和网络。

3. 内存维度:不是只有 free 那一行数字

内存问题比 CPU 隐蔽得多。CPU 问题一般是“看得见”的,跑满就是跑满;内存问题经常是“温水煮青蛙”,今天涨一点明天涨一点,等告警响了,进程已经被 OOM Killer 杀掉了。内存排查的关键是把每一块内存的用途搞清楚。

3.1 free 输出全解读:buff/cache 需要紧张吗

看内存第一眼当然是free -h:

$ free -h total used free shared buff/cache available Mem: 15Gi 1.1Gi 12Gi 21Mi 1.9Gi 13Gi Swap: 2.0Gi 0B 2.0Gi

这里最容易引起恐慌的就是 buff/cache 偏高,很多新手看到内存被 cache 占了一大半就急着清。我在这里明确说一个原则:cache 不是坏事,而是 Linux 内核在用空闲内存做磁盘缓存,目的是提升读写性能。判断内存是否真的吃紧,要看available这一列,它才是“在不触发 swap 的情况下还能分配给新进程的内存估算值”。

如果available持续很低,或者内存耗尽,swap 被大量写入,那就需要排查了。先看一眼 swap 的使用情况:si、so两列持续有值,说明系统正在频繁换页,性能通常已经受损。Linux 下的 swap 和 Windows 下的虚拟内存不完全一样,不等于废物机制,但当它频繁活动时,说明物理内存已经不够用了。

3.2 连接状态超时在虚拟机里的实际表现:

我补充一个常见细节:云服务器或者虚拟机里执行free时,total通常比买来的规格少一些,这是内核保留了一部分内存用于启动参数和硬件映射。不要拿这个“损失”去开工单,属于正常现象。

3.3 定位内存消耗:top、smem、/proc 不能只看 RES

top里的RES(常驻内存)是最常看的内存指标,但它有一个陷阱:它不包含共享内存里属于该进程的部分,也不反映进程实际申请了多少内存。一个进程的 RES 可能只有 500M,但加上共享库和共享内存后,实际占用可能超过 1G。

排查内存使用比较靠谱的两条路:

第一条是smem,它会按 USS、PSS、RSS 三个维度输出内存占用。USS 是进程独占的内存,PSS 是按进程数量均摊共享内存后的值。看谁是真正的内存大户,用 PSS 排序比用 RES 更公平:

$ smem -t -k -s pss | head -20 PID User Command Swap USS PSS RSS 8301 work java 0B 1.456g 1.512g 2.156g

第二条是直接查/proc。进程泄漏不泄漏,盯smaps_rollup里的 Rss 变化趋势最直观,它把整个进程内存映射汇总了:

$ cat /proc/8301/smaps_rollup Rss: 2211840 kB Pss: 1589248 kB Shared_Clean: 15234 kB Shared_Dirty: 652718 kB Private_Clean: 10248 kB Private_Dirty: 1545640 kB

连续采样几次,如果Private_Dirty和Rss只涨不降,基本可以确认内存泄漏。再往细了挖,可以看cat /proc/8301/smaps | grep -A 20 "heap"确认是否堆区增长,或者pmap -x 8301看具体映射段。Java 应用还要结合jstat -gcutil PID 1000看 GC 后的堆占用,确认是堆内泄漏还是堆外直接内存泄漏。

3.4 内存回收、OOM 与 cgroup 限制的坑

很多开发对 Linux 内存回收机制不了解,一看到内存用得多就想赶紧清 cache:

$ sync && echo 3 > /proc/sys/vm/drop_caches

这条命令在生产环境我基本不用。drop_caches 清的是 page cache,如果系统正在跑大量文件读写,清了 cache 不仅不能解决问题,还会让后续读写直接落到磁盘上,IO 性能反而恶化。更重要的是,它是临时手段,不解决任何根因。

内存真正耗尽时,内核会启动 OOM Killer。这时候第一件事不是骂内核,而是看日志定位为什么耗尽了:

$ dmesg -T | grep -i oom

日志里会记录被杀的进程、当时的进程列表、各进程占用的内存。如果你有特别重要的进程不想被杀,可以调它的oom_score_adj,比如 MySQL:

$ echo -500 > /proc/$(pidof mysqld)/oom_score_adj

但这里有个更隐蔽的坑:现在的容器环境大多有 cgroup 内存限制,进程看整机内存还有富余,但 cgroup 内内存已经达到上限,容器会被直接杀掉。排查这种问题,不能只看整机的 free,还要查 cgroup 的统计:

$ cat /sys/fs/cgroup/memory/memory.usage_in_bytes $ cat /sys/fs/cgroup/memory/memory.limit_in_bytes

如果是 cgroup v2 路径,对应文件是/sys/fs/cgroup/memory.current和/sys/fs/cgroup/memory.max。在容器里做内存排查,第一步永远是先确认限制在哪里,否则你的排查方向大概率是错的。

3.5 内存排查经验:swap 的罪与罚

swap 配置这个事,我在生产环境踩过很深的一次坑。当时一台机器物理内存 64G,swap 配了 8G,某天应用突然开始剧烈抖动,响应时间从 10ms 飙升到 2 秒。查下来发现 swappiness 被设成了 60,内核在内存还剩 30% 的时候就开始积极换页。频繁的页换入换出把磁盘 IO 打满,整机性能被拖垮。

后续我把核心业务服务器的 swappiness 调低到 10,同时把 swap 移到 SSD 上,但这个场景本身说明了一点:swap 不是越大越好,也不是不用最好,它承担的是“内存溢出时避免进程被杀”的兜底角色。在内存充裕且对性能敏感的应用上,宁可让 swappiness 尽量低,也不要让内核积极换页。如果你用的是云服务器,一些厂商的默认镜像会把 swappiness 调成偏高,建议部署后第一时间检查/proc/sys/vm/swappiness。

4. IO 维度:iowait 高不代表磁盘坏了

IO 排查是四个维度里最容易被误判的。原因很简单:IO 链路太长,从应用程序到磁盘读写的完整路径里,任何一环都能变成瓶颈。你以为的“磁盘慢”,可能是文件系统锁问题,可能是硬件故障,也可能是磁盘其实很快但队列太长。

4.1 学会看 iostat:%util 的真正含义

iostat是 IO 排查的核心工具:

$ iostat -x 1 5 Linux 5.4.0-26-generic (hostname) 07/12/2025 _x86_64_ (8 CPU) avg-cpu: %user %nice %system %iowait %steal %idle 5.23 0.00 1.02 42.34 0.00 51.41 Device r/s w/s rkB/s wkB/s rrqm/s wrqm/s %rrqm %wrqm r_await w_await aqu-sz rareq-sz wareq-sz %util nvme0n1 112.4 185.2 3812.4 51234.5 0.0 18.2 0.00 8.94 1.25 2.48 0.56 33.9 276.8 61.80

这里有几个指标非常容易误读:

%util被很多人当成“磁盘利用率”,其实它的准确定义是采样周期内磁盘设备有请求未完成的时间百分比,这个值高不代表磁盘“满”了。举个生活化的例子:一条高速公路在某个时段源源不断有车通过,通行能力并没有达到上限,但基于“时间百分比”的算法会认为这条路一直忙。对于 SSD 和 NVMe,只要队列不太深,100% util 也未必是瓶颈;机械硬盘反而要重视这个值。

真正判断磁盘是否到瓶颈,要看aqu-sz(平均队列长度)和r_await/w_await(读写响应时间)。如果 await 很低(毫秒级),util 高说明并发处理良好;如果 await 高且队列深,说明磁盘确实在排队。

还有个细节经常被忽略:rrqm/s和wrqm/s是磁盘调度器合并的请求数量。有时候你会发现r/s不高,但wrqm/s很高,说明 IO 已经被合并了,这通常是好事,但极端情况下合并频率太高会拉高 CPU 中断开销,需要结合vmstat的in列判断。

4.2 定位到具体进程:iotop 和 pidstat

iostat看到的是磁盘视角,只知道整个磁盘的 IO 情况,但不知道是哪个进程消耗的。这时候用iotop:

$ iotop -bk -d 1 -n 5 Total DISK READ : 0.00 B/s | Total DISK WRITE: 2.48 M/s Current DISK READ: 0.00 B/s | Current DISK WRITE: 1.23 M/s TID PRIO USER DISK READ DISK WRITE SWAPIN IO> COMMAND 5201 be/4 mysql 0.00 B/s 1.82 M/s 0.00 % 12.30 % mysqld

IO>列是进程在等待 IO 完成的时间百分比,这个值越高说明进程越在等磁盘。注意iotop需要 root 权限,而且在 IO 压力极大的机器上,iotop本身的开销会比较大,不建议常驻运行。

如果iotop没有定位到明显的 IO 大户,但磁盘 util 依然很高,要怀疑是不是 writeback 内核线程在刷脏页。用pidstat -d 1看一下:

$ pidstat -d 1 Linux 5.4.0-26-generic (hostname) 07/12/2025 _x86_64_ (8 CPU) 14:40:32 PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command 14:40:33 245 0.00 36842.00 0.00 112 kworker/u8:3-flush-253:0

kB_ccwr/s是取消写入的量,iodelay是 IO 延迟,这两个值一起看,能判断内核 writeback 是不是在大量刷盘。如果出现这种情况,通常是脏页比例阈值到了,可以临时调高/proc/sys/vm/dirty_background_ratio,但根因往往还是业务写入量太大。

4.3 IO 慢的隐藏原因:文件系统、锁、双写与硬件

磁盘硬件本身没问题、iostat 数值也不高,但应用还是慢,这种情况有不少。我的经验里,以下几个原因出现的频率最高。

第一个原因是文件系统的元数据操作。比如 ext4 的小文件大量创建删除,目录项(dentry)和 inode 的分配会成为瓶颈。用strace -c -p PID统计系统调用,如果看到大量的openat、unlinkat、creat调用,基本就是小文件场景的文件系统元数据开销。

第二个原因是应用层的锁竞争。数据库场景里,行锁、表锁、间隙锁在并发高时会表现成 IO 等待。看到 iostat 的 await 不高,但iosdiag或者应用日志里充满了锁等待超时,这不属于磁盘问题,去查慢日志和锁监控。

第三个原因是主从/lvm 层级的双写放大。比如一块盘通过 LVM2 mirror 做镜像,或者某些云盘写策略导致一次写入在底层变成多次物理写,iostat 里 r/s w/s 明明不高,但物理盘已经热到烫手。这种情况下要对比业务层的 IO 量和设备层的 IO 量,差距大就是有写放大。

第四个原因是硬件层面的校验。很多企业用的磁盘阵列卡带有写缓存,配置里如果关闭了 write-back 只开 write-through,写入性能会腰斩。这种问题用iostat看不出端倪,只能核对阵列卡配置和历史性能基线。

4.4 一个 iowait 不高但卡死的实际案例

之前帮朋友排查过一套 Kafka 集群的写入延迟问题。现象是生产者端持续报超时,但iostat -x出来磁盘 util 只有 30%,iowait 也不高。第一反应是网络问题,但网络指标也正常。

后来用blktrace去抓块设备层的事件:

$ blktrace -d /dev/nvme0n1 -w 10 $ blkparse -i nvme0n1.blktrace.0 | tail -100

过程中发现大量的D型事件(即请求被延迟处理),解释出来是上层文件系统在等待日志提交。再往上层看,Kafka 的 log segment 每次 fsync 都触发了底层小文件的大量元数据操作。进一步定位到是页缓存淘汰过快,导致每次 fsync 都得等脏页刷完才能返回。把 dirty_ratio 和 dirty_expire_centisecs 调回合理范围,并把 Kafka 的 flush 参数从 1 改成 100ms,问题才彻底解决。

这个案例教育我一件事:磁盘性能下降了,不能只盯着块设备的指标,文件系统层和应用 fsync 策略对 IO 延迟的影响往往更大。

4.5 inode 耗尽等其他“假 IO 故障”

IO 故障里有一种相对少见但一旦遇到就很棘手的:磁盘空间还有,inode 却用完了。特别是大量小文件的场景,比如消息队列积压、Java 应用日志未轮转,都很容易把 inode 刷光。用df -i查看:

$ df -i Filesystem Inodes IUsed IFree IUse% Mounted on /dev/vda1 6553600 6543289 10311 99% /

IUse% 达到 100% 时,即使磁盘有空余,任何创建新文件的操作都会报错,日志也可能只显示 “No space left on device”。排查时第一反应往往去查磁盘空间,结果空间明明够用,容易绕弯路。顺手练成习惯:排查文件写入类问题,先df -h和df -i一起跑。

5. 网络维度:抓包永远比看监控接近真相

网络排查是四个维度里最“虚”的一个。CPU、内存、IO 都能在单机上看到,网络问题往往牵扯两端,中间还隔着一堆路由器和交换机。但也不是完全没有方法,只要按照“容量 → 异常 -> 数据包”的路径走,大多数网络问题一两轮就能找到线索。

5.1 先看流量水位:sar、ss、iftop

网络排查第一步看吞吐和连接数。sar -n DEV 1 5每秒输出每个网卡的收发包数和吞吐量:

$ sar -n DEV 1 5 12:00:01 IFACE rxpck/s txpck/s rxkB/s txkB/s rxcmp/s txcmp/s rxmcst/s %ifutil 12:00:02 eth0 12520.00 13450.00 15240.00 16892.00 0.00 0.00 0.00 1.80

关注带宽利用率和 PPS(每秒包数)。小包场景特别容易让 CPU 的中断处理成为瓶颈,这时候网卡吞吐看着只有几百兆,但 PPS 已经几十万了。如果单队列网卡没有开启 RPS(Receive Packet Steering),可以开启/proc/sys/net/core/rps_sock_flow_entries和多队列的rps_cpus来分散到多核,但这是另一个话题。

连接数用ss比netstat更高效,特别是连接数多的时候,netstat会明显卡顿:

$ ss -s Total: 1832 (kernel 2843) TCP: 542 (estab 189, closed 321, orphaned 0, synrecv 0, timewait 32), ports 0

$ ss -ant | awk '{print $6}' | sort | uniq -c

统计各状态的连接数,重点关注 SYN_SENT、SYN_RECV、TIME_WAIT。如果 `TIME_WAIT` 特别多,可以通过调 `net.ipv4.tcp_tw_reuse` 减少影响,但不建议在生产环境盲目开 `tcp_tw_recycle`,它在 NAT 环境下会造成大量连接被误杀。 `iftop` 适合看实时流量来源: ```bash $ iftop -i eth0 -n -B

它能显示每个 IP 对之间的流量大小,排查“谁在占带宽”非常直观。跟 iotop 一样,iftop需要 root 且本身占用有一点,不是必须常驻的工具。

5.2 连接建立慢:TCP 重传、握手超时、连接队列溢出

连接建立慢的排查,第一步看 TCP 握手状态。ss -tnlp | grep :8080看当前端口的连接状态,如果出现大量 SYN_RECV,通常是服务端 accept 队列满了,也就是半连接队列溢出。可以用netstat -s | grep -i drop看是不是有 SYN cookie 或者丢包计数在涨。

第二步查重传。TCP 重传意味着链路不稳或者接收端处理不过来。ss -ti能看到当前连接的重传次数:

$ ss -ti | head -5 State Recv-Q Send-Q Local Address:Port Peer Address:Port ESTAB 0 0 10.0.0.5:8080 10.0.0.8:39420 skmem:(r0,rb131070,t0,tb87040,f0,w0,o0,bl0,d0) cubic rto:204 rtt:1.229/0.718 ato:40 mss:1448 cwnd:20 ssthresh:14
  1. 找到客户端口和服务端口。2. 清空宿主机的流量统计,找个干净的统计起点。3. 执行正常请求,如果观察统计中segments retransmited每秒都在涨,说明链路确实存在丢包。4. 再配合两端主机的sar -n EDEV看是否有网卡错误计数。

rtt是当前往返时延,cwnd是拥塞窗口。如果cwnd很小而且持续小,说明 TCP 拥塞控制一直在保守状态,有可能经历了严重的重传或丢包后还没有恢复。rto是重传超时,正常在小网络里应该是个位数毫秒级别,如果看到几百甚至上千,链路状况就不会太好。

第三步,如果觉得某个连接确实有问题,直接tcpdump抓包看握手过程:

$ tcpdump -i eth0 tcp port 8080 -nn -c 10

抓包重点看 SYN、SYN-ACK、ACK 三个包的往返时间。如果 SYN 发出去了,ACK 始终不回来,那就是中间链路或对端的问题;如果 SYN 一直重传但无人应答,检查防火墙或者安全组是不是把源 IP 拉黑了。

5.3 TCP 重传与乱序的排查

重传是网络问题里出现频率最高的关键词,也是最容易被误诊的。碰到重传,先把“丢包”和“乱序”分开。乱序会导致接收方认为丢包然后请求重传,但其实数据都在路上,只是到达顺序不对。

判断标准:用tcpdump抓包,如果发现相同 TCP 序号出现两次且第二次到达时距离第一次非常短,大概率是乱序而不是真实丢包。真实丢包的特征是等重传超时(RTO)到了才重传。翻开一根链路,重传率高的时候,我习惯性看两端 CPU 有没有软中断跑满、缓冲区有没有溢出。网络问题不一定真是网络问题,有时候是接收端网卡队列溢出导致丢包。ethtool -S eth0 | grep drop能看到 rx_dropped、rx_missed,这些指标是网卡层丢包的直接证据。

5.4 网络排查手记:一次连接超时排查的完整路径

最后分享一个我印象深刻的现场。客户反馈我们的服务接口偶发超时,每次持续几秒钟就恢复。整机 CPU、内存、IO 都没问题,网络流量也不大,但就是偶发超时。

先用ping -f大包打对端,没有明显丢包。再抓包,发现有个明显规律:超时的请求总是发生在 TCP 连接的初始握手阶段。用netstat -s对比各状态计数后发现listen queue overflow的次数在持续上涨。

再查应用层,服务端用的是 Java 的阻塞 IO 模型,默认 back log 设置得比较低,而在某个时间段因为下游数据库慢,请求处理速度跟不上,accept 队列瞬间被打满,新连接只能放在半连接队列里排队,排队超过超时时间就直接握手超时。

这个案例的根因其实有两层:一是应用处理线程池在必要时刻被数据库拖慢,二是 TCP backlog 太小。处理方式是给应用层连接池和数据库连接池设置了合理的超时隔离,同时把 listen backlog 从 50 调到 512。一套组合拳下来,超时消失。

经验就是:网络问题的表象往往在 TCP 层面,根因却在更上层的应用。抓包只能帮你定位到“握手超时”,但为什么握手会超时,要回到应用层找答案。

6. 四维排障速查表与常用组合命令

到这儿四个维度都过了一遍,我整理一个速查表方便你现场用。表里面的工具和命令不是按“全不全”列的,是按“现场够不够用”列的。每一条对应一个症状,看完直接抄作业。

维度典型症状第一梯队命令第二梯队命令关键判断指标
CPU负载高、响应慢top、vmstatmpstat、pidstat、perf、jstackr列 vs 核数、cs列、%CPU超核数
内存缓慢变慢、被 OOM 杀free、topsmem、smaps_rollup、jstat、dmesgavailable、si/so、cgroup 限额
IO异步任务慢、数据库卡iostat -x、iotoppidstat -d、strace、blktrace、df -ir_await/w_await、aqu-sz、inode 使用率
网络连接超时、传输慢ss、sar -n DEViftop、tcpdump、ethtool、netstat -sSYN_RECV 堆积、重传次数、rx_dropped 计数

一个比较省事的组合排查流程,我在应急时经常用:

# 一分钟内快速摸底四维状态 uptime free -h vmstat 1 3 iostat -x 1 3 ss -s dmesg -T | tail -50

这一串命令跑下来,CPU 负载、内存水位、磁盘吞吐、网络连接状态、内核报错全都有了,足够支持下一轮的深入排查。

如果你要临时持续监控,用sar -o /tmp/sar_$(date +%F).log 1 10把采样数据落盘,后面可以用sar -f打开复盘,这一招在事后分析“故障到底从什么时候开始”的时候非常好用。

结尾

我在实际排查中还有一个习惯,也当作最后的小技巧分享给你:处理任何性能问题都先把dmesg -T | tail -100过一遍,再开始用各种工具。内核在发生 OOM、IO 错误、软锁死、CPU 异常时会往这里写日志,很多时候答案其实就摆在第一屏,只不过大家习惯往复杂的方向想。

性能排查说到底就是一个不断缩小范围的过程,从“整机水位高”到“某个进程异常”,再到“某次调用超时”,每一层都需要一个准确的判断标准。希望这份手册能让你在下次面对告警时,少一点慌张,多一点思路。毕竟系统不会骗人,骗人的永远是没读懂的指标。

返回列表