"服务连不上"这句话里藏着四层不同的事实:进程在不在、端口听没听、连接能不能建立、数据有没有回来。绝大多数排障时间浪费在跳步——一上来就抓包,抓了半天才想起看进程是不是根本没起来。这篇把四层拆开,每层配一组命令。
1. 排查顺序:先把四层分开
| 层 | 问题 | 一条命令 | 否证后的方向 |
|---|---|---|---|
| 1 进程 | 进程活着吗 | ss -lntp 查 8080 |
启动失败,去看应用日志 |
| 2 端口 | 在本机听了吗 | ss -lnt 查 8080 |
监听地址错,或启动没完成 |
| 3 连接 | 能建立 TCP 吗 | curl -v telnet://127.0.0.1:8080 |
防火墙、安全组、监听地址 |
| 4 数据 | 有响应回来吗 | curl -v http://TARGET/PATH |
上游处理、超时、反代配置 |
顺序不能反。第 2 层已经否了还去抓包,抓到的只会是空的。第 1 与第 2 层的区别也很重要:进程活着不代表在监听,监听也不代表进程是你以为的那个。
2. ss:替换 netstat 的主力
netstat 在多数发行版已不默认安装,ss 直接读内核,更快也有更多过滤能力。
$ ss -lntp # TCP 监听 + 端口 + 进程
$ ss -tnp state established # 只看已建立连接
$ ss -tnp state time-wait | wc -l
$ ss -s # 全局统计:各状态计数
$ ss -tnp dst 10.0.0.5:3306 # 按对端过滤
| 组合 | 含义 | 用在哪一层 |
|---|---|---|
-lntp |
监听、数字端口、TCP、带进程 | 第 1、2 层 |
-tnp |
已建立/所有 TCP 连接带进程 | 第 3 层 |
state <NAME> |
按状态过滤 | 判断堆积类型 |
-s |
汇总统计 | 一眼看全机状态分布 |
-i |
接口与队列信息 | 判断 backlog 是否溢出 |
-p 需要 root 或对应进程属主,非 root 时进程列是空的——这不是"没有进程",是权限不足。-i 输出里的 Send-Q 在监听 socket 上是 backlog 队列上限,在连接 socket 上是未确认字节数,两个含义别混。
3. 谁占着端口:lsof 与 fuser
$ lsof -i :8080 -nP
$ fuser -n tcp 8080
$ fuser -k -n tcp 8080 # 强杀占用者,慎用
| 工具 | 输出 | 适合 |
|---|---|---|
ss -lntp |
监听 socket 与进程 | 常规确认 |
lsof -i :PORT |
进程、fd、协议、状态 | 查具体是哪个进程/可执行文件 |
fuser -n tcp PORT |
仅 PID | 脚本里快速取 PID |
lsof -p PID |
该进程打开的全部 fd | 判断是否持有连接/文件 |
-nP 关掉反解,避免 DNS 卡住命令(这一点在 DNS 本身有问题时特别明显)。
4. 连接状态:四种堆积各有病根
| 状态 | 含义 | 大量出现说明什么 |
|---|---|---|
SYN-SENT |
客户端已发 SYN,等 SYN-ACK | 对端不响应:网络不通、被丢包、端口没开 |
SYN-RECV |
服务端收到 SYN,已回 SYN-ACK | 客户端没收到回包,或 SYN Flood / backlog 满 |
CLOSE_WAIT |
对端已 FIN,本端还没 close | 应用没关连接:连接泄漏、没关流、没设读超时 |
TIME_WAIT |
主动关闭方的等待态 | 短连接高频,正常现象,通常不该调 |
ESTABLISHED |
正常 | 看数量是否远超预期并发 |
CLOSE_WAIT 是最有诊断价值的一个:它由本地应用造成,不是网络问题。堆积意味着代码里拿到了连接却没在异常路径关闭——典型是 try 里开了流、finally 里没关,或者用了连接池但没配空闲回收。
TIME_WAIT 相反,是主动关闭方的正常状态,等 2×MSL 自然消失。只有确认短连接量确实极高、端口耗尽时才考虑 net.ipv4.tcp_tw_reuse=1;靠改内核参数掩盖短连接设计,收益有限且容易踩坑。真正常用的两个观察口径是 ss -s 的总量,以及 netstat -s(或 nstat)里的重传、丢包计数。
5. 监听地址:127.0.0.1 与 0.0.0.0 的区别
LISTEN 0 511 127.0.0.1:8080 0.0.0.0:*
LISTEN 0 511 0.0.0.0:8080 0.0.0.0:*
第一行只能被本机回环访问。容器里跑的服务如果绑 127.0.0.1,端口映射出去也连不上——因为容器的回环不是宿主的回环。这是"curl localhost 通、外部不通"的第一嫌疑,第二嫌疑才是防火墙。
三种检查一起做,避免只查一层:
$ ss -lnt | grep :8080 # 监听地址
$ sudo nft list ruleset | grep -i 8080 # 主机防火墙
$ sudo iptables -S | grep -i 8080 # 旧栈
# 云主机还有一层安全组,只能在控制台/API 侧确认
主机防火墙与云安全组是两层独立策略,两层都放行才通。
6. 抓包:最小可用的一组命令
$ sudo tcpdump -i any -nn -c 200 'port 8080'
$ sudo tcpdump -i eth0 -nn -s 0 -w /tmp/cap.pcap 'host 10.0.0.5 and port 3306'
$ sudo tcpdump -i any -nn -A 'port 8080' | grep -E '^(GET|POST|HTTP)'
-nn 关掉反解、-c 限制包数、-s 0 抓完整包、-w 落盘。不设 -c 的抓包在故障期间会长到几个 G,写盘写满反而制造新故障。
抓不到包只有三类原因:
| 现象 | 原因 | 验证 |
|---|---|---|
| 完全无输出 | 接口选错 | 用 -i any 或 ip -br a 确认接口 |
| 有包但无目标流量 | 过滤器写错(端口写反、少了 host) |
先去掉过滤器只抓 20 个包看有没有流量 |
| 有出无回 | 请求根本没到,或被中间设备丢弃 | 内核计数里的 drop:nstat -az |
容器内抓包是另一个视角。docker exec 进容器抓,看到的是容器 namespace 的流量;在宿主机抓 -i docker0(或对应 veth),看到的是经过网桥的流量。NAT 之后源地址会变,所以"宿主机抓到的是容器 IP、应用日志里是网关 IP"完全正常,不需要修。
7. curl 分段定位与 DNS
$ curl -v http://TARGET/api/health
$ curl -o /dev/null -s -w 'dns=%{time_namelookup} conn=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total}\n' https://TARGET/
| 阶段 | 变量 | 变大说明什么 |
|---|---|---|
| DNS 解析 | time_namelookup |
解析慢或走了错误的 resolver |
| TCP 建连 | time_connect |
网络/防火墙/对端 backlog |
| TLS 握手 | time_appconnect |
证书链、OCSP、CPU 不足 |
| 首字节 | time_starttransfer |
上游处理慢,与应用层有关 |
| 总计 | time_total |
与上面几项对比看差在哪 |
time_connect 大而 time_starttransfer 不大,问题在网络;反过来问题在应用。这一对比能省掉很多无效抓包。
DNS 侧只看一个文件:
$ cat /etc/resolv.conf
$ getent hosts TARGET # 走系统解析(含 nsswitch)
$ dig +short TARGET @127.0.0.53
getent 与 dig 结果不一致,说明差异出在 nsswitch.conf 的顺序(files 在前会先读 /etc/hosts)而不是 DNS 本身。容器内 /etc/resolv.conf 由 daemon 生成,search 域与 options ndots 都可能与宿主机不同,跨集群访问短域名时优先写全限定名。
8. 进程层面:ps、pmap 与 /proc
$ ps -eo pid,ppid,rss,vsz,etime,cmd --sort=-rss | head
$ pmap -x <PID> | tail -1
$ ls -l /proc/<PID>/fd | wc -l
$ cat /proc/<PID>/status | grep -E 'Threads|VmRSS|VmSwap'
| 位置 | 看什么 | 异常信号 |
|---|---|---|
ps -o rss |
常驻内存 | 单调增长不回落 |
pmap -x |
内存映射明细 | 大量匿名映射 → 堆或直接内存 |
/proc/<PID>/fd |
打开的文件与 socket | 数量接近上限 → 泄漏 |
/proc/<PID>/status |
线程数、RSS、Swap | Threads 持续上升 → 池泄漏 |
/proc/<PID>/limits |
open files 上限 |
与 fd 数量对照 |
/proc/<PID>/exe 指向进程实际运行的可执行文件,比 which 更可信——which 只反映当前 shell 的 PATH。
9. 排错
| 现象 | 原因 | 处理 |
|---|---|---|
| 服务重启了但端口还是旧的 | 旧进程没退,新进程绑不上端口静默失败 | ss -lntp 找到 PID,确认后 kill,再重启 |
本机 curl 通、外部不通 |
只听 127.0.0.1,或防火墙/安全组未放行 |
改监听 0.0.0.0;两层策略一起查 |
ss 看不到容器里的进程 |
容器在独立 network namespace | docker exec 进容器执行,或 nsenter -t PID -n |
| 抓到的包源地址是容器 IP | 抓点在 NAT 之前 | 在宿主机网桥侧或容器内对比抓 |
| 抓包文件几十 GB | 没设 -c 或没设 -w 轮转 |
先 -c 限量,再落盘分析 |
CLOSE_WAIT 上千 |
应用未关闭连接 | 查流关闭与连接池空闲回收,不是调内核 |
ss 的进程列是空的 |
非 root / 非属主 | 用对应权限执行 |
lsof 卡住不返回 |
反解 DNS 慢 | 加 -nP |
| 排查命令本身超时 | resolver 不可用 | 临时用 curl --resolve 指定 IP 验证链路 |
10. 边界
| 场景 | 更合适的工具 | 理由 |
|---|---|---|
| 需要连接历史与聚合 | eBPF / ss 定时采样 |
单次快照看不到趋势 |
| 需要应用层协议解析 | tcpdump 存 pcap + Wireshark/tshark |
手工看裸包效率低 |
| 需要跟踪系统调用 | strace -f -p PID |
网络层无法说明阻塞点 |
| 需要观测队列溢出 | ss -i 的 Send-Q、nstat 的 ListenOverflows |
只看状态计数会漏 |
| 多节点拓扑内的连通性 | 分布式探针 / mesh 侧指标 | 单机命令无法区分中间节点 |
11. 自测
ss -lntp里看不到进程名,最先怀疑什么?- 大量
CLOSE_WAIT与大量TIME_WAIT,哪个是本地应用的锅,为什么? listen 0 511 127.0.0.1:8080这行里511代表什么?tcpdump完全没输出,可能的三类原因是什么?curl -w里time_connect大而time_starttransfer小,问题在哪一层?- 容器里
curl 127.0.0.1:PORT通、宿主机连不上,为什么?