《Linux系统常用命令》这个系列写到了第十四期,前面聊过文件操作、文本处理、权限管理、磁盘管理这些基础科目,不少朋友后台私信说,命令背了不少,但真到线上出问题时,一下乱了阵脚,不知道该先敲什么。所以这一期我不准备再按命令分类来讲,而是换个思路,做成一个“故障排查实战专场”:从系统负载异常、进程卡死、磁盘写满、网络变慢到日志定位,模拟几个运维日常里最常见的现场,告诉你那一刻最该掏出来的命令是什么,以及为什么是它。
这一期适合两类人:一类是刚把基础命令过了一遍、想在真实场景里串起来的初学者,另一类是天天面对告警、希望把排查思路再理顺的一线运维。命令本身都不复杂,难的从来不是命令,而是顺序和判断。所以这一期每一节都尽量带一个具体的现场节奏,你可以直接照着敲一遍。
1. 系统负载异常?先敲这几行命令再谈优化
1.1 uptime 和 top:全局状态先看这两样
服务器告警最常见的一句话就是“负载高了”。不少新手一上来就盯着 CPU 使用率看,其实第一步应该是敲uptime,先看系统整体状态。它的输出里有 load average,也就是过去 1 分钟、5 分钟、15 分钟的平均负载值:
$ uptime 10:42:13 up 81 days, 3:22, 2 users, load average: 30.12, 18.33, 12.50load average 到底代表什么?它不是 CPU 使用率,而是“处于可运行状态和不可中断睡眠状态的进程平均数量”。你可以把它想象成食堂打饭窗口前的排队人数:CPU 是打饭阿姨,进程是排队的人。一个窗口的理想情况是队伍长度不超过 1,如果长期超过 CPU 核心数的好几倍,说明大家一直在等,系统已经忙不过来了。注意这里的“队列长度”是平均值,三个数字分别代表过去 1、5、15 分钟,所以30.12, 18.33, 12.50表示最近 1 分钟突然涌入大量任务,趋势是上涨的,得赶紧处理。
紧接着敲top,看两样东西:第一行和uptime一样的负载信息,以及%Cpu(s)那一行的各项数值。us 是用户态 CPU、sy 是内核态 CPU、wa 是等待 IO、id 是空闲,这些概念后面要用到。top运行后按P键按 CPU 排序、按M键按内存排序,先快速定位到最耗资源的进程 PID。这一步通常能把“系统异常”缩小到“某个进程异常”。
1.2 vmstat 和 pidstat:把高负载拆到具体进程
top看到了整体,但要说清楚负载高的“类型”,必须请出vmstat。vmstat 的参数格式是vmstat 间隔 次数,比如vmstat 1 5每秒输出一次,共输出 5 次:
$ 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 31 1 0 2097152 345678 6123456 0 0 0 102400 0 0 3 1 10 86 0重点看两个 procs 列:r是正在排队等待 CPU 的进程数,b是处于不可中断睡眠状态的进程数。如果r很高,通常是 CPU 密集,进程在抢 CPU;如果b很高而 CPU 的wa(等待 IO)也高,那多半是磁盘或网络 IO 拖了后腿。比如上面这个输出,r=31说明 CPU 队列确实长,但wa=86说明 86% 的 CPU 时间都在等 IO,问题重心应该放到存储设备上。
要看到具体是哪个进程在读写磁盘,用pidstat -d 1,它属于 sysstat 工具包,没有的话先装一下。输出里会按进程展示每秒的磁盘写入速度,比如下面这样:
$ pidstat -d 1 09:23:41 UID PID kB_rd/s kB_wr/s kB_ccwr/s Command 09:23:42 0 1234 0.00 20480.00 0.00 mysqld看到某个进程的kB_wr/s一直很高,方向就明确了:这一步不是急着调优,而是确认“谁在制造负载”。我见过太多人在top里看到 CPU 高就杀掉进程,结果发现真正原因是磁盘快照备份把 IO 打满了,杀错对象还引发业务告警。
1.3 一个真实场景:load 30 但 CPU 很闲
有一次同事转给我一个线上告警:load 30,业务响应变慢。我登录上去先uptime,负载确实高;再top,发现 processes 里有大量 D 状态进程,CPU 的 idle 还剩不少;接着vmstat 1 5,b列常年不为 0,wa接近 90%。这时候基本可以断定瓶颈不在 CPU,而在 IO。
继续用iostat -x 1看具体磁盘:
$ iostat -x 1 Device r/s w/s rkB/s wkB/s await %util sda 3.00 356.00 24.00 122880.00 136.00 99.50%util接近 100%,await(平均 IO 请求耗时)已经上百毫秒,磁盘完全处于饱和状态。然后再回过去用pidstat -d 1找写入大户,发现是一个日志采集进程在反复写大文件。后来的处理是给它加缓存、调整写入频率,而不是简单 reload 业务服务。这个案例想说明的是:排查一定要按“负载 -> CPU/IO 分类 -> 具体进程”的路径走,每一步都用不同的命令交叉验证,而不是猜。
2. 进程状态异常,别急着 kill
2.1 ps 状态码:先看懂 D、S、R、Z
很多朋友一看到 CPU 高的进程就想着kill -9,其实进程状态本身就有大学问。用ps -eo pid,stat,comm,wchan:25,cmd可以看到每个进程的状态码和当前等待的内核函数位置:
$ ps -eo pid,stat,wchan:25,comm,cmd | head -20 PID STAT WCHAN COMMAND CMD 1 Ss ep_poll systemd /usr/lib/systemd/systemd 520 S do_select sshd sshd: /usr/sbin/sshd -D 1234 D blkdev_issue_flush mysqld /usr/sbin/mysqld 3456 Z 0 java [java] <defunct>常见的状态码含义:R表示正在运行或可运行;S是可中断睡眠,比如在等网络数据;D是不可中断睡眠,通常是在等磁盘 IO,这种状态很难被 kill;Z是僵尸进程,进程已经结束但父进程没有回收它的资源。
僵尸进程是面试高频考点。它的出现说明父进程没有正确调用wait()来回收子进程,所以子进程变成了“已经死了但还占着进程表项”的形态。你没法直接kill僵尸进程,因为它在进程表里已经没有可执行代码了,真正要做的是处理它的父进程:要么让父进程正常退出,由 init 或 systemd 进程接手回收,要么检查父进程代码里有没有忘记回收子进程。排查时用ps -eo ppid,pid,stat,cmd | grep 'defunct'找到僵尸的父进程,再决定怎么办。如果父进程是常驻服务,通常只能重启它,但要注意业务影响。
2.2 端口和文件句柄都被谁占了:lsof 的三种用法
排查进程相关问题,lsof是我使用频率非常高的命令,它比netstat更能说清楚“进程和资源之间的关系”。最常用的有三种用法。
第一种,查端口占用,lsof -i:8080,顺着端口找到进程 PID,解决了“端口起不来”的经典问题:
$ lsof -i:8080 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME java 5678 root 23u IPv4 123456 0t0 TCP *:8080 (LISTEN)第二种,查进程打开了哪些文件,lsof -p PID。如果程序报 “Too many open files”,先看这个进程打开了什么,常常会发现日志句柄、临时文件或者 socket 连接没有释放。这时候配合ulimit -n查看当前用户能打开的文件数上限,再判断是调上限还是修程序的资源泄漏问题。
第三种,查被删除但仍被进程占用的文件,lsof +L1。这个放到磁盘部分详细说,但排查进程异常时它也很关键:进程明明已经把日志文件删了,但还在持续写入旧的文件描述符,导致磁盘空间不释放,这种“幽灵文件”只有 lsof 能一眼看穿。
2.3 strace:程序卡住时,直接“偷看”它在等什么
程序卡死是最让人头疼的问题之一,表现可能是某个进程 CPU 不高但业务没响应,也可能是一个服务起来后又悄悄退出。这时候可以用strace跟踪进程的系统调用,看它到底卡在哪儿:
$ strace -p 1234 futex(0x7f3c0009d000, FUTEX_WAIT_PRIVATE, 2, NULL) = -1 EAGAIN read(17, 0x7fff2d3f1a50, 4096) = -1 EAGAIN (Resource temporarily unavailable)如果看到进程一直阻塞在read、futex、connect这类调用上,通常说明它在等外部资源:等数据到达、等锁释放、等网络连接。strace还可以配合-c做统计,把一段时间内的系统调用汇总,看看哪个调用耗时占比最高:
$ strace -c -p 1234 % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 75.23 2.345678 1234 1901 32 read 20.11 0.654321 456 1432 0 futex需要注意的是,生产环境用strace要谨慎,它会大幅拖慢目标进程。我自己的习惯是:先小范围试几秒确认问题,不要长时间挂着;如果怀疑是 Java 或者 Go 程序的业务层问题,优先用语言自带的诊断工具(比如 jstack)而不是直接上 strace。
2.4 父进程退出后谁来“接盘”:聊聊孤儿进程和托管
进程管理还有一个容易忽略的点:父子进程。当你用ps -ef看到一个进程的 PPID 是 1(systemd 或 init),说明它的父进程已经退出了,这个进程变成了“孤儿进程”,被系统顶层进程接管。这不是坏事,反而说明它还能继续存活;真正要警惕的是父进程退出后,子进程没人管,跟着变僵尸。
所以我会建议,凡是需要长期运行的进程,不要用裸的nohup一把梭,而是交给 systemd 或 supervisor 这类进程托管工具。这样进程退出后有重启策略、有日志收集、有状态查询,排查时直接用systemctl status就能看到完整信息,比满世界找 PID 高效得多。这个理念在后面的 systemd 部分还会用到。
3. 磁盘快满了?df -h 只是第一步
3.1 空间去哪儿了:du 的精确制导
“磁盘满了”是运维的日常,但很多人只会用df -h看到/分区 100%,然后陷入不知道删什么的尴尬。实际上df只看全局,du才能查目录占用。正确的姿势是从根往下逐层排查,先看一级目录谁占得多:
$ du -h -d 1 / | sort -h | tail -10-d 1表示只统计一层目录,sort -h按人类可读的大小排序,这样能快速锁定/var、/home、/opt里的“大胃王”。再进去继续用同样的命令往下一层查,精准定位到大目录下的具体子目录,比如 Docker 内容常在/var/lib/docker,应用日志常在/var/log。这里有个容易踩坑的点:du和df看到的大小可能不一致,因为可能有已删除但被进程占用的文件,也可能有文件系统保留块,别急着觉得命令出了问题,继续往下查。
3.2 分区没满但报“No space left”:inode 耗尽了
磁盘告警还有一种经典误判:df -h显示空间还剩 20%,但程序就是报 “No space left on device”。这时候要记得查 inode,命令是df -i:
$ df -i 文件系统 Inodes IUsed IFree IUse% 挂载点 /dev/sda1 655360 655360 0 100% /inode 可以理解为文件系统里的“档案柜格子”,每个文件都要占一个格子。如果某个目录下堆积了几十万个小文件(典型的如 Docker overlay 目录、邮件队列、临时缓存目录、rsyslog 的 omfile 队列),就算这些文件体积很小,也会先把 inode 耗尽。排查时可以用find / -xdev -type f | wc -l统计总文件数,再按目录逐层定位。清理的原则是:先识别是哪些服务生成的临时文件,配置清理策略,不要凭空乱删;如果一时无法清理,也可以把目录挂载到另一个空闲的分区上缓一缓。
3.3 删了文件空间却不释放:找找被进程占用的“幽灵文件”
这也是我见过很多团队栽过跟头的地方。半夜收到磁盘告警,管理员直接rm -rf /var/log/app.log,但df -h一看,空间根本没有降下来。原因是日志文件正在被某个进程使用,删除操作只是把目录里的条目删掉了,进程还握着这个文件的文件描述符,数据块自然不会被释放。
排查命令就是前面提过的lsof +L1:
$ lsof +L1 | grep deleted java 1234 root 1w REG 8,1 4096 0 12345 /var/log/app.log (deleted)看到输出的(deleted)标记后,处理方式有两种:如果进程可以重启,重启后句柄自然释放;如果进程不能重启,那就用清空而不是删除的方式,先把文件内容清掉再让进程继续写:
$ : > /var/log/app.log这里也顺便说一个长期建议:不要用rm直接删正在写入的日志文件,正确做法是配合 logrotate 的 copytruncate 模式,或者先通知应用重新打开日志。这种细节,手册里不会写,但线上真的会教做人。
3.4 文件系统变成只读:别慌,一步步查
“文件系统变只读”多数发生在异常断电或磁盘 IO 错误之后。现象是写文件报 “Read-only file system”,第一反应不要直接强制重启,先看内核日志里有没有磁盘错误:
$ dmesg -T | tail -50 [Mon Feb 24 10:21:03 2025] EXT4-fs error (device sda1): ext4_find_entry: ...如果是偶发错误,可以尝试重新挂载为读写:
$ mount -o remount,rw /但要注意,这只是应急手段。如果文件系统已经有结构性损坏,越写越糟糕。正确做法是先备份能拿到的数据,卸载分区后用fsck或xfs_repair修复,再挂载回来。下面这张表是我自己整理的快速排查路径:
| 现象 | 第一步命令 | 第二步判断 | 应急/修复 |
|---|---|---|---|
| 写文件报只读 | dmesg -T 查 IO 错误 | 是否有磁盘坏道/超时 | mount -o remount,rw |
| 文件系统损坏 | fsck / xfs_repair | 卸载后修复 | 备份数据再修复 |
| inode 耗尽 | df -i | 找小文件目录 | 清理临时文件 |
| 空间不释放 | lsof +L1 | 找 deleted 文件 | 重启进程或清空文件 |
4. 网络慢与不通:一条命令一条命令地逼近
4.1 端口与连接状态:ss 的日常用法
网络问题排查第一步是搞清楚“连接本身在不在”。现在主流发行版都推荐用ss替代netstat,它的输出更快,信息也更全。最常用的组合是:
$ ss -tunlp这个命令列出所有 TCP/UDP 监听端口和已建立连接,并带上进程信息。如果要看整体连接状态统计,用ss -s,可以快速看到当前有多少个 ESTAB、TIME_WAIT、LISTEN。如果看到 TIME_WAIT 数量特别多,说明短连接频繁创建销毁,结合业务就可以判断需不需要调整内核参数,比如启用net.ipv4.tcp_tw_reuse。注意这类参数调整要依据实际压测结果,不要照抄网上配置。
定位端口被占用也很简单:ss -tlnp | grep :8080,比以往netstat -tunlp | grep 8080更直接。
4.2 到底慢在哪:curl 耗时拆解
“网站打开慢”是个很泛的描述,可能是 DNS 解析慢、TCP 建连慢、后端处理慢,也可能是带宽小。这时候我有一个习惯:用curl的-w参数把各阶段耗时拆开看:
$ curl -o /dev/null -s -w "DNS: %{time_namelookup}s\nTCP: %{time_connect}s\nTLS: %{time_appconnect}s\n首字节: %{time_starttransfer}s\n总耗时: %{time_total}s\n" https://example.com DNS: 0.012s TCP: 0.043s TLS: 0.087s 首字节: 0.852s 总耗时: 0.865s如果time_starttransfer明显大于time_connect,说明连接建立很快,但服务端迟迟不返回数据,瓶颈在应用层;反过来,如果time_connect就很高,再配合traceroute看每一跳的延迟,判断是不是网络链路或防火墙的问题。这个拆解方法能帮你把“网络慢”和“服务慢”分开,避免在错误方向上折腾。
4.3 抓包定乾坤:tcpdump 入门三板斧
当常规网络命令都查不出问题时,就该抓包了。很多新手觉得tcpdump是高阶技能,其实你只需要三板斧。第一板斧:抓特定端口的包,存成 pcap 文件,丢给 Wireshark 分析:
$ tcpdump -i eth0 -nn 'tcp port 443' -c 100 -w /tmp/capture.pcap第二板斧:盯住某个主机的通信:
$ tcpdump -i eth0 -nn host 192.168.1.10第三板斧:看 TCP 握手是否正常,比如只抓 SYN 和 RST:
$ tcpdump -i eth0 -nn 'tcp[tcpflags] & (tcp-syn|tcp-rst) != 0'抓包的目的不是让你逐字节去读,而是把“可能”变成“确定”。有一回一个服务间歇性超时,ping 通、端口通、应用日志也正常,最后抓包发现客户端在重传 SYN,服务器却迟迟不应答,进一步查是负载均衡设备的连接表满导致丢包。这种问题不抓包几乎没法定位。
4.4 流量和带宽:nload、iftop、iperf3
想直观看到当前网卡带宽占用,可以用nload,它会用动态界面展示进/出流量;想看是哪些连接在占用带宽,用iftop -i eth0 -n -B,它按连接实时排序。这两个工具都小巧实用,大多数发行版可以直接用包管理器安装。
如果怀疑是带宽瓶颈,就需要主动压测。用iperf3是最靠谱的:一台机器起服务端iperf3 -s,另一台机器跑客户端iperf3 -c 目标IP -t 30,测试结果会直接给出带宽和重传率。重传率过高说明链路质量差或 MTU 配置有问题,带宽上不去则要考虑限速或链路规格。这样一轮下来,“网络慢”就能定位到具体是链路、设备还是应用。
5. 日志与 systemd:故障现场的第一手证据
5.1 journalctl:别只会翻 /var/log/messages
很多老运维习惯直接去/var/log/messages里 grep,但在 systemd 时代,更高效的是直接查 journal 日志。查看某个服务的日志:
$ journalctl -u nginx -f只看最近 30 分钟的错误级别日志:
$ journalctl -u nginx --since "30 minutes ago" -p err结合 systemd 服务名,这已经是排查服务崩溃的首选入口。有一个容易被忽略的点:默认情况下 journald 日志只存在内存里,重启后旧日志就没了。如果希望日志持久化,需要改/etc/systemd/journald.conf里的Storage=persistent,然后systemctl restart systemd-journald。这个问题我踩过不止一次:服务重启后想翻看崩溃前的日志,发现记录已经被清空,只能懊恼当初没配置持久化。
5.2 内核日志:OOM 和硬件错误都在 dmesg 里
应用突然被杀死,最经典的场景是内存不足触发了 OOM Killer。这时候journalctl -k或dmesg -T是必查项:
$ dmesg -T | grep -i oom [Mon Feb 24 10:30:02 2025] oom-kill:constraint=CONSTRAINT_NONE,threads_total=1024 [Mon Feb 24 10:30:02 2025] Out of memory: Killed process 5678 (java) total-vm:2097152kB看到哪条日志被 OOM Killer 杀掉后,再结合top看当时内存占用,判断是内存泄漏还是业务峰值导致。同样,硬件层面的磁盘 IO 错误、网卡故障,也会在内核日志里留下痕迹。所以排查顺序应该是:先看内核日志有没有“异常事故报告”,再去看应用自己的日志。
5.3 日志三件套:tail、grep、awk 的组合拳
经典日志文件虽然不如 journald 直接,但依然重要,比如/var/log/secure记录登录认证信息,/var/log/messages记录系统级消息,应用日志则常按业务存放在各自目录。排查时我习惯用一组组合拳:先用tail -F实时跟随最新的日志输出,再用grep -E 'ERROR|Exception|FATAL'过滤关键行,最后用awk按字段做统计。
举个例子,分析 Nginx 访问日志里哪些 URL 响应最慢:
$ awk '{print $7, $NF}' /var/log/nginx/access.log | sort -k2 -rn | head -20如果想知道某个时间点有多少 5xx 错误:
$ grep '25/Feb/2025:10:' /var/log/nginx/access.log | grep -c ' 5[0-9][0-9] '这些命令组合看起来朴素,但实际排查时比很多监控平台都快。
5.4 快速梳理日志的排查清单表
面对一堆日志文件,新手常有的困惑是“我该先看哪个”。下面是我自己的参考顺序:
| 场景 | 优先查看 | 关键线索 |
|---|---|---|
| 服务崩溃/启动失败 | journalctl -u 服务名 -p err | Failed、exit code |
| 内存被杀 | dmesg -T 或 journalctl -k | oom-kill、Out of memory |
| 登录异常 | /var/log/secure | Failed password、session opened |
| 业务错误 | 应用自己的日志目录 | Exception、ERROR |
| 定时任务异常 | /var/log/cron | 任务执行状态、错误输出 |
| 网络异常 | journalctl -k 或网卡日志 | link down、watchdog |
6. 命令记不住?用“场景驱动”的方式重来一遍
6.1 一套可复制的排查思路:从症状到根因
很多人感觉命令白学了,是因为按字母顺序背参数,而不是按场景记。我的经验是反过来:把每个故障场景当作一条链路,在链路上挂命令。比如“用户反馈网页打不开”,我脑子里的链路是这样的:先curl -I看 HTTP 响应码,再ss -tlnp看服务端口是否在监听,然后journalctl -u nginx看服务状态和报错,如果服务没问题就去/var/log/nginx/error.log找上游超时,再用ss -tn看后端连接池是否占满,最后dmesg看有没有被 OOM 或 IO 错误影响。
这套思路的价值在于:它不依赖你记住所有命令,而是依赖你理解“用户请求经过的路径”。把路径上的每个节点各备一两招,排查自然有章法。有心的话,可以自己在虚拟机里故意制造故障,比如把磁盘写满、把端口占用、把系统负载拉高,再按这条链路亲手练一遍,比背十遍命令有用得多。
6.2 高频命令速查表:按场景查命令
我整理了一张按场景记忆的命令速查表,建议打印出来贴在工位旁边:
| 场景 | 命令 | 核心参数 |
|---|---|---|
| 系统负载 | uptime / top / vmstat / mpstat | top 按 P 排序,vmstat 1 5 |
| 进程状态 | ps / pidstat / lsof / strace | ps -eo stat,wchan,strace -p |
| 磁盘空间 | df -h / du -h -d 1 / df -i | du 加 --max-depth |
| 幽灵文件 | lsof +L1 | 配合 grep deleted |
| 端口连接 | ss -tunlp / ss -s | 查监听用 -lnp |
| 耗时拆解 | curl -w | time_starttransfer 关键 |
| 抓包定位 | tcpdump | -c 限制包数,-w 保存结果 |
| 日志查询 | journalctl / dmesg / tail / grep / awk | --since、-p err、-k |
这张表不是让你背下来,而是让你拿着它直接去查,用熟了自然就记住了。
6.3 面试常问的 5 个 Linux 排查题
整理几个高频面试题,其实也是日常排查的缩影。
第一个:load 高但 CPU 使用率不高,怎么排查?参考路径是vmstat看b和wa,再iostat看磁盘等待,锁定 IO 瓶颈。
第二个:端口被占用,怎么找到是哪个进程?ss -tlnp | grep :8080,直接看 PID,再ps -fp PID看进程详细情况。
第三个:文件删不掉怎么办?先看是不是权限问题lsattr查看隐藏属性,再看是否文件系统只读mount确认挂载状态。
第四个:僵尸进程太多怎么清理?ps -A -o stat,ppid,pid,cmd | grep -w Z找到僵尸,处理父进程,而不是直接 kill 僵尸本身。
第五个:服务启动失败从哪里入手?systemctl status 服务名看状态,journalctl -u 服务名 -p err看日志,按需再看/etc/systemd/system/下的启动参数。
这几个题目考的不是参数背得熟不熟,而是你有没有一套清晰的排查路径,回答时把前面的链路串出来即可。
6.4 一个建议:每个星期故意“搞坏”一次系统
写命令系列的这些期,我一直坚持一个观点:命令不是背会的,是用会的。与其对着命令大全从头读到尾,不如在自己的虚拟机里每个星期做一次故障演练。今天把磁盘 fio 跑满看负载变化,明天写个死循环进程练 strace,后天手动清空日志文件再看 lsof 的表现。出错没什么,出错后的排查过程才是真正的记忆锚点。
我自己带新人也一直是这个套路:先给一台测试机,让他把服务跑起来,然后我悄悄往系统里放一些“事故”,比如占满 inode、锁掉关键文件、后台开一堆 IO 进程,让他通过命令一层层定位。几个月下来,他不需要背任何命令表,遇到问题也能从容应对,因为那些命令已经在各种失败经历里刻进脑子里了。
这个系列聊到第十四期,从最基础的ls、cd走到今天面向故障现场的排查,希望传达的从来不只是命令参数,而是面对一台未知服务器时,如何有条理地读懂它告诉你的信息。下次再遇到告警,别急着拍脑袋,先按这六节里的路径走一遍,多半能把问题缩小到很小一块,剩下的就是交给日志和工具去确认了。