1. 线上问题排查的核心思路与命令分类
线上出问题的时候,最怕的不是问题本身有多复杂,而是手忙脚乱不知道从哪里下手。我见过不少同行,一听到告警就立刻重启服务,结果现场没了,问题原因也查不到,下次照样复现。排查这件事,核心原则就一条:先保留现场,再定位根因,最后才动手恢复。命令只是工具,脑子里得先有一张排查地图。
1.1 为什么命令排查依然是第一手段
现在可观测性体系很成熟了,链路追踪、指标监控、日志聚合平台一大堆。但真到了紧急时刻,图形界面加载慢、查询有延迟、采样数据缺失,反而是直接登录机器敲命令来得最快最准。命令行的优势在于:实时性高、不受采集链路影响、能拿到最原始的数据。监控平台看到的是“过去一分钟的平均值”,而命令看到的是“此刻这一秒的真实状态”,这两者在排查抖动类问题时差别巨大。
另一个现实原因是,很多问题发生在监控覆盖不到的角落。比如某个进程的文件描述符悄悄泄漏、某块磁盘的inode被耗尽、某个连接处于CLOSE_WAIT状态堆积——这些细节往往要落到具体命令上才能看清。所以我的习惯是,监控负责“发现异常”,命令负责“确认和深挖”。
1.2 排查命令的四大分类框架
我把线上排查常用的命令按用途分成四类,这样遇到问题时能快速定位该用哪一类:
| 分类 | 核心目标 | 代表命令 |
|---|---|---|
| 系统资源类 | 看CPU、内存、磁盘、负载 | top、free、df、iostat、vmstat |
| 进程线程类 | 看进程状态、线程栈、句柄 | ps、pidstat、jstack、pstree |
| 网络连接类 | 看连接状态、端口、流量 | ss、netstat、tcpdump、lsof |
| 日志追踪类 | 看实时日志、关键字过滤 | tail、grep、journalctl、awk |
这个分类不是死的,实际排查中经常交叉使用。比如发现CPU飙高,先用top定位到进程,再用pidstat看是哪个线程,最后用jstack抓线程栈分析代码。整个过程就是在这四类之间来回跳。
提示:排查前先确认自己有没有权限。很多生产环境的命令需要特定用户或sudo权限,临时申请权限会耽误时间,平时就要把常用命令的权限准备好。
1.3 排查的黄金顺序:从宏观到微观
新手最容易犯的错是一上来就盯着某个细节猛查,结果方向错了白费功夫。我的经验是严格遵循“从宏观到微观”的顺序:
- 先看整体负载:uptime、top,判断是全局问题还是局部问题
- 再看资源瓶颈:CPU、内存、磁盘、网络,确定是哪类资源吃紧
- 然后定位到进程:找到消耗资源的罪魁祸首
- 最后深入进程内部:线程栈、句柄、连接,找到具体代码或配置问题
这个顺序能保证你不漏掉关键信息,也不会在错误的方向上浪费时间。下面几个章节,我就按这个思路把常用命令一个个拆开讲。
2. 系统资源类命令的实战用法
系统资源是排查的起点。很多时候服务变慢,不是代码问题,而是机器本身扛不住了。这一章把CPU、内存、磁盘、负载相关的命令讲透。
2.1 top命令:不只是看CPU
top大概是所有人第一个学会的排查命令,但真正用明白的人不多。默认输出里,第一行是负载,第二行是进程统计,第三行是CPU,第四行是内存,第五行是交换分区。很多人只看CPU那一行的us(用户态)和sy(内核态),其实信息量远不止这些。
几个容易被忽略的关键指标:
- load average:三个数字分别是1分钟、5分钟、15分钟的平均负载。判断标准是跟CPU核数比,比如8核机器负载持续超过8就要警惕。但要注意,负载高不一定是CPU忙,也可能是大量进程在等IO。
- wa(iowait):CPU等待IO的时间占比。这个值高说明磁盘是瓶颈,而不是CPU。
- si/so:交换分区的换入换出。这两个值非零且持续,说明物理内存不够了,系统在拿磁盘当内存用,性能会断崖式下跌。
- %Cpu(s)的st:被虚拟机偷走的时间。如果是云主机,这个值高说明宿主机超卖了。
top的交互操作也很实用:按P按CPU排序,按M按内存排序,按1展开每个核的详情,按H显示线程。排查时我习惯先按1看是不是单核打满,再按P找最耗CPU的进程。
注意:top默认3秒刷新一次,对于瞬时抖动可能抓不到。可以改成
top -d 1加快刷新,或者用top -b -n 1输出一次快照方便记录。
2.2 free命令:内存的真实账本
free的输出里,重点看available这一列,而不是free那一列。因为Linux会把空闲内存拿去做缓存(buff/cache),free看起来很少是正常的,available才是真正可用的内存。
free -h total used free shared buff/cache available Mem: 15Gi 8.2Gi 1.1Gi 320Mi 6.1Gi 6.8Gi Swap: 2.0Gi 512Mi 1.5Gi判断内存是否真的紧张,看两个信号:一是available持续低于总内存的10%,二是swap的used在持续增长。如果swap用得很多,说明系统已经在频繁换页,这时候任何服务都会变慢。
我踩过的一个坑:有次服务频繁GC,查了半天代码,最后发现是机器内存被另一个进程吃光了,导致JVM的堆被换到swap里。所以内存排查一定要看全局,不能只盯着自己的服务。
2.3 df和du:磁盘空间与inode
磁盘满了是线上最常见的故障之一。df看分区使用率,du看目录占用。但有个细节很多人不知道:df看的是块使用率,inode是另一套账。
df -h # 看块使用率 df -i # 看inode使用率 du -sh /var/log/* | sort -rh | head -20 # 找大目录inode耗尽的表现很迷惑:df -h显示还有空间,但就是写不进文件,报“No space left on device”。这时候用df -i一看,inode用满了。inode被什么占了呢?通常是大量小文件,比如日志切分产生的海量小文件、临时文件没清理、邮件队列堆积等。
找inode大户可以用:
find / -xdev -printf '%h\n' | sort | uniq -c | sort -rn | head -20这条命令统计每个目录下的文件数量,排在前面的就是小文件重灾区。
提示:删除大量小文件时,直接用
rm -rf可能很慢甚至卡住。可以用find 目录 -type f -delete,或者先rsync一个空目录过去覆盖,速度更快。
2.4 iostat和vmstat:IO瓶颈的照妖镜
CPU不高、内存也够,但服务就是慢,这时候要怀疑IO。iostat看磁盘,vmstat看整体。
iostat -x 1 5 # 每1秒采样,共5次,-x显示扩展信息重点看几个列:%util是磁盘繁忙度,接近100%说明磁盘跑满了;await是平均IO等待时间,单位毫秒,机械盘超过20ms、SSD超过5ms就要注意;r/s和w/s是每秒读写次数。
vmstat的输出里,r列是等待运行的进程数,持续大于CPU核数说明CPU不够;b列是阻塞的进程数,高说明IO有问题;si和so跟free里的swap对应。
我一般把iostat和vmstat配合用:vmstat发现b列高,再用iostat确认是哪块盘的问题。这样定位效率最高。
3. 进程与线程类命令的深挖技巧
定位到具体进程之后,工作才真正开始。这一章讲怎么把进程和线程看透。
3.1 ps命令的几种实用组合
ps的参数组合非常多,我常用的就几个:
ps -ef | grep 服务名 # 看进程是否存在 ps -aux --sort=-%cpu | head -10 # 按CPU排序看前10 ps -aux --sort=-%mem | head -10 # 按内存排序看前10 ps -eLf | grep 进程名 # 看进程下的所有线程ps -eLf这个组合特别有用,它能列出每个线程的LWP(轻量级进程ID),配合后面要讲的jstack或gdb就能定位到具体线程。
还有一个技巧是看进程的启动时间,判断是不是被意外重启过:
ps -eo pid,lstart,etime,cmd | grep 服务名lstart是启动时间,etime是运行时长。如果发现运行时长只有几分钟,而服务本该跑了好几天,那说明进程刚重启过,问题可能跟重启有关。
3.2 pidstat:进程级的细粒度监控
top只能看进程的整体情况,pidstat能拆到每个进程甚至每个线程。
pidstat -p 12345 1 5 # 监控指定进程,每秒一次共5次 pidstat -t -p 12345 1 5 # -t 显示线程级数据 pidstat -d -p 12345 1 5 # -d 显示IO数据排查CPU问题时,pidstat -t能直接告诉你哪个线程在烧CPU,拿到线程ID(TID)后就能去抓线程栈。这个链路是:top找进程 → pidstat -t找线程 → jstack抓栈 → 定位代码。
3.3 jstack抓线程栈的正确姿势
Java服务排查绕不开jstack。但直接jstack pid抓出来的栈可能不准,因为抓的瞬间线程状态可能已经变了。我的做法是连续抓三次,间隔几秒,对比看哪些线程一直处于RUNNABLE或BLOCKED。
jstack 12345 > stack1.txt sleep 3 jstack 12345 > stack2.txt sleep 3 jstack 12345 > stack3.txt然后重点看nid(native thread id)跟前面pidstat拿到的TID对得上的线程。注意jstack里的nid是十六进制,pidstat的TID是十进制,要转换一下:
printf '%x\n' 12345 # 十进制转十六进制死锁排查也是jstack的强项,输出里搜deadlock关键字,JVM会直接告诉你哪几个线程互相等锁。
注意:jstack会暂停JVM一小会儿(安全点),对高并发服务有轻微影响。生产环境抓栈要避开业务高峰,或者用
-F强制抓取(但可能不准)。
3.4 lsof:一切皆文件的哲学
Linux里一切皆文件,lsof就是查看“谁打开了什么文件”的利器。排查句柄泄漏、端口占用、文件被删但空间没释放,都靠它。
lsof -p 12345 # 看进程打开的所有文件 lsof -i :8080 # 看谁占用了8080端口 lsof | grep deleted # 看已删除但未释放的文件 lsof -p 12345 | wc -l # 统计进程打开的文件数“文件已删除但空间没释放”是经典问题:日志文件被rm了,但进程还持有句柄,磁盘空间不回收。用lsof | grep deleted找到后,重启进程或清空句柄才能释放空间。
句柄泄漏的判断:lsof -p pid | wc -l持续增长,接近ulimit -n的限制时,服务就会报“Too many open files”。这时候要结合代码看哪里没关流。
4. 网络连接类命令的排查套路
网络问题最隐蔽,因为涉及两端甚至多端。这一章讲怎么把连接状态看清楚。
4.1 ss替代netstat的理由
netstat是老牌命令,但在连接数多的时候性能很差,因为它要遍历/proc下的所有文件。ss直接从内核拿数据,快得多。现在新系统基本都用ss了。
ss -s # 汇总统计 ss -tunlp # 看TCP/UDP监听端口 ss -tan state established | wc -l # 统计已建立连接数 ss -tan state time-wait | wc -l # 统计TIME_WAIT数连接状态里,几个需要重点关注的:
- TIME_WAIT:主动关闭方会进入这个状态,持续2MSL(通常60秒)。大量TIME_WAIT会占用端口资源,可以通过调整内核参数优化。
- CLOSE_WAIT:被动关闭方如果没正确关闭连接,会一直停在这个状态。CLOSE_WAIT堆积基本等于代码有bug,是应用没调close()导致的。
- SYN_RECV:半连接状态,大量出现可能是SYN Flood攻击,或者后端处理太慢。
我排查连接问题的习惯是先ss -s看全局,再按状态过滤看具体是哪种连接异常。
4.2 tcpdump抓包的最小必要原则
tcpdump功能强大,但在生产环境抓包要极其克制,因为抓包本身消耗CPU和磁盘,抓多了可能把机器拖垮。我的原则是:明确目标、限制数量、及时停止。
tcpdump -i eth0 -nn -c 100 port 8080 -w /tmp/cap.pcap参数解释:-i指定网卡,-nn不解析域名和端口名(更快),-c 100只抓100个包就停,-w写入文件而不是打印到屏幕。
抓包前先想清楚要抓什么:是抓某个端口的握手过程,还是抓某个IP的流量,还是抓特定协议的包。目标越明确,抓包量越小,对生产影响越小。
分析抓包文件可以用tcpdump -r读回来,或者下载到本地用Wireshark看。重点看三次握手是否完成、有没有RST包、重传多不多。
注意:抓包文件可能包含敏感数据,分析完要及时删除,不要留在生产机器上。
4.3 网络连通性排查的递进顺序
网络不通的时候,按这个顺序排查最省时间:
- 本机网络:
ip addr看网卡是否up,ip route看路由表 - 网关连通:
ping 网关IP,不通说明本机网络配置有问题 - 目标连通:
ping 目标IP,不通可能是路由或防火墙 - 端口连通:
telnet 目标IP 端口或nc -zv 目标IP 端口,确认端口是否开放 - DNS解析:
nslookup 域名或dig 域名,确认域名解析正常 - 抓包确认:前面都通但业务还异常,就抓包看实际流量
这个顺序能快速缩小问题范围,避免盲目猜测。
5. 日志追踪类命令的高效用法
日志是排查的最后一公里,也是最直接的信息来源。但日志量大,怎么快速找到有用的那几行是关键。
5.1 tail和grep的黄金组合
实时看日志用tail -f,配合grep过滤关键字:
tail -f app.log | grep --line-buffered "ERROR"--line-buffered很重要,不加的话grep会缓冲输出,看起来像卡住了。如果要同时看多个关键字:
tail -f app.log | grep -E "ERROR|WARN|Exception"查历史日志用grep加时间范围:
grep "2024-01-15 14:3" app.log | grep "ERROR"5.2 awk和sed的日志分析技巧
日志里经常要统计某些指标,比如统计每个接口的调用次数、统计错误码分布。这时候awk就派上用场了。
# 统计每种错误码出现次数 awk '{print $9}' access.log | sort | uniq -c | sort -rn # 统计每分钟的请求量 awk '{print substr($4, 2, 17)}' access.log | uniq -c # 找出响应时间超过1秒的请求 awk '$NF > 1 {print}' access.logsed适合做日志的提取和替换,比如提取某个时间段的日志:
sed -n '/14:30:00/,/14:35:00/p' app.log这些命令组合起来,能在几秒内从GB级日志里捞出关键信息,比图形界面快得多。
5.3 journalctl:系统日志的统一入口
用systemd的系统,服务日志都进了journal,用journalctl查看:
journalctl -u 服务名 -f # 实时跟踪某服务 journalctl -u 服务名 --since "10 min ago" # 看最近10分钟 journalctl -p err -b # 看本次启动的所有错误 journalctl -u 服务名 -o json-pretty # JSON格式输出,方便解析journalctl的好处是自带时间过滤和优先级过滤,不用自己写grep。但要注意journal默认会限制日志大小,老日志可能被清理,重要日志还是要单独落盘。
6. 常见问题排查速查与避坑经验
前面讲了命令怎么用,这一章把常见问题跟对应命令串起来,形成速查表,再分享几个我踩过的坑。
6.1 典型故障场景速查表
| 故障现象 | 首选命令 | 关键指标 | 可能原因 |
|---|---|---|---|
| 服务响应慢 | top、iostat | load、%util、await | CPU/IO瓶颈 |
| 内存持续增长 | free、ps | available、RSS | 内存泄漏 |
| 磁盘写满 | df -h、df -i | Use%、IUse% | 空间或inode耗尽 |
| 连接超时 | ss、tcpdump | CLOSE_WAIT、重传 | 连接泄漏或网络抖动 |
| 端口占用 | lsof -i、ss | 监听状态 | 端口冲突 |
| 句柄耗尽 | lsof、ulimit | 打开文件数 | 流未关闭 |
| CPU飙高 | top、pidstat -t | %CPU、TID | 死循环或频繁GC |
| 线程卡死 | jstack | BLOCKED、deadlock | 死锁或锁竞争 |
这张表建议打印出来贴在工位上,出问题时对照着查,能省不少时间。
6.2 我踩过的三个坑
第一个坑:重启掩盖了真相。早期遇到服务异常,第一反应是重启,结果重启后问题暂时消失,过几天又出现,反复几次都没找到根因。后来养成习惯:重启前先抓一份现场,包括线程栈、堆转储、连接状态。哪怕最后还是要重启,至少留下了分析材料。
第二个坑:只看平均值。监控平台给的是分钟级平均值,有次服务每隔几分钟抖动一下,平均值看起来完全正常。后来用top -d 1和pidstat 1看秒级数据,才发现是定时任务在特定时刻抢资源。排查抖动类问题,一定要看细粒度数据。
第三个坑:忽略系统日志。有次应用日志一切正常,但服务就是起不来,查了半天代码。最后dmesg一看,是OOM Killer把进程杀了。系统日志(dmesg、journalctl)经常被忽略,但它记录了内核层面的关键事件,排查疑难问题必看。
6.3 排查效率提升的几个习惯
- 提前准备好命令清单:把常用命令写成脚本或别名,出问题时直接调用,不用现查参数。
- 保留排查记录:每次排查的过程和结论记下来,形成自己的知识库,下次遇到类似问题能快速复用。
- 定期演练:在测试环境模拟各种故障,练习排查流程,真出问题时才不会慌。
- 关注基线:平时记录服务的正常指标范围,异常时一对比就知道偏了多少。
排查这件事,命令是死的,思路是活的。同样一套命令,有经验的人能在几分钟内定位问题,没经验的人可能查半天还在外围打转。差别就在于脑子里有没有那张排查地图,以及平时有没有积累足够的实战经验。多练、多记、多复盘,慢慢就能形成自己的排查直觉。