跳转到内容

第 31 章 · 排障工具箱

一句话:按症状反查:手里只有一条报错或一个现象时,从这里找到先跑哪条命令、多半是什么原因、去哪一章看处理办法。 用它还是用别的:已经知道是哪个工具出的问题(Nginx、PostgreSQL、Docker),直接翻那一章的“注意事项”;只知道“端口被占”“连不上”“起不来”“满了”“慢”,先在这一章按层定位到具体工具,再跳过去。 核对版本:Ubuntu 24.04.3(WSL 2.6)、PowerShell 7.6,2026-10

不管什么症状,按同一个顺序走,能省掉大半时间:

  1. 先复现。确认问题能稳定重现,记下时间、哪台机器、做了什么操作。只出现过一次、再也复现不了的,先去翻那个时间点的日志。
  2. 看报错原文。完整复制第一条报错,不要只看最后一行;Python 的调用栈(traceback)看最后一行,Java 看第一行和 Caused by: 那几行,再往里找第一行自己写的代码。拿报错原文去搜,比用自己的话描述准得多。
  3. 看日志。服务端日志里的信息往往比客户端报错多一倍:systemd 管的服务用 journalctl -u <服务>(第 30 章),容器用 docker logs <容器>(第 12 章),Nginx 看 error.log(第 16 章),Windows 上的程序和服务看事件查看器(eventvwr.msc,命令行是 Get-WinEvent)。
  4. 缩小范围。按“本机 → 网络 → 服务”一层层排除:先在服务所在的机器上用 curl localhost:<端口> 试,通了再从外面试;不通就不用查网络。
  5. 一次只改一处。改完重新复现一次,没好就改回去再试下一处。同时改三处,好了也不知道是哪一处起的作用,坏了更不知道。

“连不上服务”是最常见、也最容易乱查的一类。下图把它拆成六层,从上一层通了才往下一层查,每层旁边是该跑的命令(Windows 和 Linux 各一条)和这一层不通时看到的现象。前三层在你自己的电脑上就能查,后三层要登上服务器。第二层的 ping 和不带端口的 Test-NetConnection 用的是 ICMP,云服务器常禁 ping,ping 不通不等于网络不通,直接试第三层。

连不上服务的六层排查:在你的电脑上依次查名字解析、网络通不通、端口有没有人听,端口不通就登上服务器查听的地址对不对、防火墙放行了吗、认证过了吗,每层旁边是对应命令和不通时的现象

同一个地址和端口同一时间只能有一个程序在听。报这类错时只需回答一个问题:谁占着它。先查出进程号,再看它的完整启动命令,判断是自己忘了关的程序、另一个容器,还是系统服务,三种情况的处理完全不同。

尖括号里的内容换成自己的。Linux 上 ss -ltnp 不加 sudo 时,别人进程的那一列是空的;lsof -i 不加 sudo 干脆不列出别人的进程,查不到不代表端口空闲,要用 sudo lsof -i :8080。

症状或报错 Windows(PowerShell) Linux 说明
启动时报 address already in use、EADDRINUSE、[WinError 10048] 通常每个套接字地址(协议/网络地址/端口)只允许使用一次 Get-NetTCPConnection -LocalPort 8080 -State Listen ss -ltnp 'sport = :8080' Windows 看 OwningProcess 列,Linux 看最后一列的 pid=
同上,换个命令看 netstat -ano | findstr :8080 sudo lsof -i :8080 netstat 最后一列是 PID;lsof 第二列是 PID,不加 sudo 看不到别人的进程
这个 PID 是什么程序 Get-Process -Id <PID> ps -fp <PID> 看进程名和启动时间,判断是不是自己上次没关的
看它完整的启动命令 (Get-CimInstance Win32_Process -Filter "ProcessId=<PID>").CommandLine ps -o cmd= -p <PID> 同样是 python,命令行能看出是哪个项目
确认是自己的程序后结束它 Stop-Process -Id <PID> kill <PID> 必须先跑上面两行看清楚;系统服务用服务管理器停,不要直接结束
端口明明没人占,仍报 [WinError 10013](访问权限不允许) netsh interface ipv4 show excludedportrange protocol=tcp 无 列出的范围被 Hyper-V、WSL 等系统组件保留,换一个范围外的端口

常见原因 → 处理:

  • 上次启动的同一个程序还在后台(终端关了但进程没退、IDE 停止按钮没停干净、nohup 跑着)→ 按上表找到 PID,看命令行确认是自己的再结束。Linux 上先用 kill,不要一上来 kill -9,详见第 29 章 29.6。
  • 另一个容器或本机装的服务占着:Docker 报 Bind for 0.0.0.0:8080 failed: port is already allocated → 详见第 12 章 12.6;Compose 映射 3306 时报同样的错 → 详见第 13 章 13.6。
  • 80、443 被另一个 Web 服务器占着:bind() to 0.0.0.0:80 failed (98: Address already in use) → 详见第 16 章 16.6;Caddy 报 listen tcp :80: bind: address already in use → 详见第 17 章 17.6。

按 31.1 的图从上往下查。<主机> 可以是域名或 IP。

症状或报错 Windows(PowerShell) Linux 说明
curl: (6) Could not resolve host、DNS 名称不存在 Resolve-DnsName <域名> getent hosts <域名>;dig +short <域名> 解析不出 IP 就不用查后面几层;dig 要先装 dnsutils
一直等,最后超时 Test-NetConnection <主机> -Port <端口> nc -zv -w 3 <主机> <端口> 超时多半是防火墙或云安全组把包丢了
Connection refused、[WinError 10061] 由于目标计算机积极拒绝,无法连接、curl: (7) Failed to connect 同上,TcpTestSucceeded : False 同上,nc: connect to … failed: Connection refused 包到了那台机器,但那个端口没人在听
在服务器上看它听在哪个地址 Get-NetTCPConnection -State Listen -LocalPort <端口> ss -ltnp 地址是 127.0.0.1 只收本机,0.0.0.0 或 * 才收外面
防火墙有没有放行 Get-NetFirewallRule -Enabled True -Direction Inbound -Action Allow sudo ufw status 云服务器还要看控制台里的安全组
端口通了,网页返回 502、504 curl.exe -I <url> curl -I <url> 反向代理连不上或等不到后端,去看代理的错误日志
端口通了,报认证失败 Get-WinEvent -LogName Application -MaxEvents 20 journalctl -u <服务> -n 50 --no-pager 用户名、口令、来源地址三样核对

常见原因 → 处理:

  • 服务只监听 127.0.0.1:服务器上 curl localhost 正常,从外面连报 Connection refused → 详见第 29 章 29.6;本机装的 PostgreSQL 的 listen_addresses → 详见第 21 章 21.6。
  • 容器里写了 localhost:应用日志报 connect ECONNREFUSED 127.0.0.1:5432,数据库明明在跑 → 把地址改成 Compose 里的服务名,详见第 13 章 13.6。
  • 反向代理后面的程序没起或端口不对:502 Bad Gateway,错误日志有 connect() failed (111: Connection refused) → 详见第 16 章 16.6;本机用 curl 测出 curl: (7) → 详见第 18 章 18.6。
  • 网络通了,认证没过:password authentication failed → 详见第 21 章 21.6;Access denied for user → 详见第 22 章 22.6;NOAUTH Authentication required. → 详见第 25 章 25.6;SSH 的 Permission denied (publickey). → 详见第 6 章 6.6。

进程起不来,原因几乎都写在它退出前的最后几行输出里,难的是找到这几行去了哪:前台运行的程序在终端里,systemd 服务进了 journald,容器在 docker logs 里,Windows 服务进了事件日志。先找到这几行,再看退出码。

症状或报错 Windows(PowerShell) Linux 说明
命令行程序一闪就退,没看清输出 $LASTEXITCODE echo $? 紧接着运行;0 是正常结束,非 0 是出错退出
服务现在是什么状态 Get-Service <服务> systemctl status <服务> Linux 这条还会带出最近十行日志
看服务或程序的报错 Get-WinEvent -FilterHashtable @{LogName='Application';Level=2} -MaxEvents 20 journalctl -u <服务> -n 50 --no-pager Windows 的 Level=2 是“错误”;图形界面在事件查看器的“Windows 日志”
哪些服务启动失败了 事件查看器 → Windows 日志 → 系统 systemctl --failed 没有失败的服务时显示 0 loaded units listed.
被系统因内存不足杀掉 无 journalctl -k -g 'killed process' --case-sensitive=false;sudo dmesg -T | grep -i 'killed process' 有 Out of memory: Killed process <PID> (<名字>) 就是被 OOM 杀了;Ubuntu 默认 kernel.dmesg_restrict=1,dmesg 要加 sudo(WSL 里不用)
容器起来又停了 docker ps -a 后 docker logs <容器> 同左 状态列 Exited (1) 里的数字就是退出码

日志里什么都没有、程序就是一启动就退时,Linux 上可以 strace -f -o trace.txt ./app 记下它的系统调用,打开 trace.txt 从后往前找最后几个返回 -1 ENOENT(文件不存在)或 -1 EACCES(没权限)的调用,它找的那个路径往往就是原因。

常见原因 → 处理:

  • 环境变量没带进去:终端里能跑,做成服务后报 KeyError: 'DATABASE_URL' → 详见第 30 章 30.6;改了变量程序读到的还是旧值 → 详见第 15 章 15.6。
  • 一启动就崩,被反复拉起:服务停在 failed,日志有 Start request repeated too quickly. → 先看再往上的那几行真正的报错,详见第 30 章 30.6。
  • 容器主进程退出:docker ps 里没有,docker ps -a 是 Exited (1) → 详见第 12 章 12.6。
  • 权限或 PATH 不对:Permission denied,或放进 cron 后报 command not found → 详见第 29 章 29.6。

先确认是哪个分区满了,再一层层往下找是哪个目录、哪类文件。删之前确认没有进程还开着这个文件,否则删了空间也回不来。

症状或报错 Windows(PowerShell) Linux 说明
No space left on device、“磁盘空间不足”、数据库写入失败 Get-PSDrive -PSProvider FileSystem df -h Windows 看 Free 列;Linux 看 / 那一行的 Use%
df -h 还有空间,仍报 No space left on device 无 df -i IUse% 到 100% 是 inode 用完了,多是几百万个小文件
一个目录总共多大 Get-ChildItem <目录> -Recurse -File | Measure-Object Length -Sum du -sh <目录> Windows 的 Sum 单位是字节
哪个子目录最大 对每个子目录跑上一行 du -xh --max-depth=1 <目录> | sort -h 最大的排在最后,一层层往下钻
找大文件 Get-ChildItem <目录> -Recurse -File | Sort-Object Length -Descending | Select-Object -First 10 FullName,Length find <目录> -xdev -type f -size +100M -xdev 不跨到别的磁盘
删了文件空间没回来 无 sudo lsof +L1 列出已删除但还被进程打开的文件
日志和镜像占了多少 docker system df journalctl --disk-usage;docker system df 这两样是服务器上最常见的大头

常见原因 → 处理:

  • 删掉的日志还被进程开着:df -h 显示 100%,du 加起来却少很多 → 重启那个进程或清空文件而不是删除,详见第 29 章 29.6。
  • journald 日志太多 → sudo journalctl --vacuum-size=500M 并设上限,详见第 30 章 30.6。
  • Docker 旧镜像和构建缓存:docker system df 显示几十 GB → 详见第 12 章 12.6。
  • 数据库表膨胀:行数没怎么变,表却越来越大 → 详见第 21 章 21.6。

先分清是整机资源紧张,还是一个进程失控:看完整机的数字,再按内存或 CPU 排序,找到排第一的进程,按 31.2 的方法看清它的命令行再决定怎么处理。

症状或报错 Windows(PowerShell) Linux 说明
整机还剩多少内存 Get-Counter '\Memory\Available MBytes' free -h Linux 看 available 列,free 列小是正常的,空闲内存被拿去做了缓存
整机 CPU 多忙 Get-Counter '\Processor(_Total)\% Processor Time' top -bn1 | head -15 top 第一行的 load average 长期大于 CPU 核数(nproc)就是忙不过来
谁占内存最多 Get-Process | Sort-Object WorkingSet64 -Descending | Select-Object -First 10 Id,ProcessName,WorkingSet64 ps aux --sort=-%mem | head WorkingSet64 单位是字节
谁占 CPU 最多 Get-Process | Sort-Object CPU -Descending | Select-Object -First 10 Id,ProcessName,CPU ps aux --sort=-%cpu | head Windows 的 CPU 列是累计用了多少秒,不是此刻的占用;看此刻用任务管理器或 top
进程突然没了,内存曲线掉到底 无 journalctl -k -g 'killed process' --case-sensitive=false 被内核的 OOM killer 杀了
哪个容器在吃资源 docker stats --no-stream 同左 每个容器一行 CPU 和内存

常见原因 → 处理:

  • Redis 没设内存上限:内存一直涨,最后进程被杀,或报 OOM command not allowed → 设 maxmemory 和淘汰策略,详见第 25 章 25.6。
  • 缓存同时过期:缓存过期的那一刻数据库 CPU 冲到 100% → 详见第 25 章 25.6 的缓存击穿。
  • 连接没关:数据库报 Too many connections,每个连接都占内存 → 详见第 22 章 22.6。
  • 自己的程序失控(死循环、内存泄漏)→ 按 31.2 查清 PID 和命令行再结束;数据库这类进程不要 kill -9,详见第 29 章 29.6。

慢要先分清慢在哪一段:DNS、建连接、等服务器处理,还是下载。浏览器里看 Network 面板的 Timing(第 19 章 19.2),命令行用 curl 的 -w 把一次请求拆成几段时间。

症状或报错 Windows(PowerShell) Linux 说明
一个接口慢,不知道慢在哪 curl.exe -o NUL -s -w "dns %{time_namelookup} connect %{time_connect} pretransfer %{time_pretransfer} ttfb %{time_starttransfer} total %{time_total}\n" <url> curl -o /dev/null -s -w 'dns %{time_namelookup} connect %{time_connect} pretransfer %{time_pretransfer} ttfb %{time_starttransfer} total %{time_total}\n' <url> 每项都是从开始累计的秒数;ttfb 减 pretransfer 才是服务器处理的时间,connect 到 pretransfer 之间是 HTTPS 握手
整台机器都慢、命令都卡 Get-Counter '\PhysicalDisk(_Total)\% Disk Time' vmstat 1 5 Linux 看 wa 列,长期两位数是在等磁盘;si、so 不为 0 是在用交换分区
数据库查询慢 EXPLAIN ANALYZE <SQL>; 同左 在 psql 或 mysql 里执行(MySQL 8.0.18 起支持);大表上出现全表扫描多半是缺索引
Redis 偶尔卡几秒 SLOWLOG GET 10 同左 在 redis-cli 里执行,列出最近的慢命令
日志里找慢请求 看应用日志的耗时字段 journalctl -u <服务> --since '10 min ago' 先缩小到慢的那段时间

常见原因 → 处理:

  • 后端或数据库慢(ttfb 占了大头):全表扫描 → 按第 21 章 21.4 的“找出慢查询并加索引”做;表膨胀拖慢查询 → 详见第 21 章 21.6。
  • Redis 被一条命令堵住:线上 Redis 突然卡几秒,所有请求一起超时 → 多是 KEYS * 或删大键,详见第 25 章 25.6。
  • 备份锁表:mysqldump 导出期间写入全部卡住 → 加 --single-transaction,详见第 22 章 22.6。
  • 响应太大或缓存没生效:ttfb 很短、total 很长 → 压缩或分页(第 19 章 19.2 的计时图);发版后浏览器还在用旧文件 → 详见第 19 章 19.6。
  • Get-NetTCPConnection(Microsoft Learn):按本地端口、状态筛选 TCP 连接,OwningProcess 列给出占用端口的进程号;参数和输出字段的完整说明。
  • Test-NetConnection(Microsoft Learn):测试到某台主机的连通性和指定端口,TcpTestSucceeded 为 False 时再加 -InformationLevel Detailed 看细节。
  • Get-WinEvent(Microsoft Learn):在命令行里读事件查看器的日志,FilterHashtable 按日志名、级别、时间筛选。
  • ss 手册页:-l、-t、-n、-p 各管什么,以及按端口、状态过滤的表达式写法(如 sport = :8080)。
  • 可观测性(Ubuntu Server 文档):Ubuntu 服务器上汇总日志和监控的官方做法:Logwatch 每日日志摘要、Nagios 和 Munin 监控、日志与指标告警栈。
  • 502 Bad Gateway(MDN):502 的含义:网关或代理从上游服务器收到了无效响应;同目录下有 503、504 等其余状态码的说明。