VOL. 02 · NETWORK & PROCESS

Linux 网络与
进程故障

THE OPS FIELD MANUAL
Connections · Ports · FDs · Processes
root@ops:~/network#
ss / netstat lsof tcpdump strace nc / mtr dig / nslookup

网络故障的诊断三件套是分层:能 ping 通不代表能 telnet 通,telnet 通不代表 HTTP 通。从 L3(IP 可达) → L4(端口可达) → L7(协议正确)逐层测,定位断在哪一层。进程故障的核心是状态机:每个进程都有"运行/睡眠/僵尸/停止"几种状态,卡住时先确认状态。

TCP 状态速查 · 排查必备

排查连接异常前必须知道每个状态意味着什么。ss -ant 输出的状态列就是这些。重点记 TIME_WAIT 和 CLOSE_WAIT 的区别——后者更危险。

LISTEN
监听端口,等待连接
SYN_SENT
客户端发了 SYN,等回应
SYN_RECV
服务端收到 SYN,回了 SYN-ACK
ESTABLISHED
连接建立,正常通信
FIN_WAIT_1
主动关闭方发了 FIN
FIN_WAIT_2
收到对方 ACK,等对方 FIN
TIME_WAIT
主动关闭,等 2MSL 才释放
CLOSE_WAIT
被动关闭,本地未调 close()
01

TIME_WAIT 堆积成山

SHORT CONNECTION ABUSE / PORT EXHAUSTION
高频 必会
  • 新建连接失败,报 "Cannot assign requested address"
  • ss -ant | grep TIME_WAIT | wc -l 显示几万甚至十几万
  • 常见于反向代理服务器、爬虫客户端、短连接微服务

主动关闭连接的一方会进入 TIME_WAIT 状态,持续 2 * MSL(Linux 默认 60 秒)。这期间端口被占用,如果客户端短时间大量发起短连接,本地端口很快耗尽(默认范围 32768-60999,约 28K 个)。

# 1. 统计 TCP 各状态数量
ss -ant | awk '{print $1}' | sort | uniq -c

# 2. 看本地端口范围
cat /proc/sys/net/ipv4/ip_local_port_range
# 默认 32768 60999

# 3. 看 TIME_WAIT 都连到哪
ss -ant state time-wait | awk '{print $4}' | cut -d: -f1 | sort | uniq -c | sort -rn
# 方案 A:扩大本地端口范围
sysctl -w net.ipv4.ip_local_port_range="1024 65535"

# 方案 B:允许 TIME_WAIT 端口重用(关键!)
sysctl -w net.ipv4.tcp_tw_reuse=1
# 注意:tcp_tw_recycle 在新内核(4.12+)已删除,别再用

# 方案 C:写入永久配置
cat >> /etc/sysctl.conf << EOF
net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_max_tw_buckets = 50000
EOF
sysctl -p
  • 使用长连接(HTTP Keep-Alive、数据库连接池),从根本上减少短连接
  • Nginx 上游用 keepalive 配置
  • 客户端复用 HTTP Client 实例,别每次 new 一个
tcp_tw_recycle 的坑 · 早期资料推荐开启,但它和 NAT 网络一起用会导致丢包(SYN 被丢弃)。这个参数在 Linux 4.12+ 已被移除,看到老文档让你开启的,直接忽略。
TIME_WAIT 本身不是病 · 它是 TCP 协议的安全机制,防止旧连接的延迟包干扰新连接。少量 TIME_WAIT 是正常的,只在堆积到端口耗尽时才需要处理。
02

CLOSE_WAIT 堆积 — 比 TIME_WAIT 危险

APPLICATION BUG: MISSING CLOSE() CALL
高频 致命
  • ss -ant | grep CLOSE_WAIT 数量持续增长,不释放
  • 应用一段时间后开始拒绝新连接
  • 重启应用后正常,但过一阵又复现

CLOSE_WAIT 是被动关闭方的状态:对端发了 FIN,内核回了 ACK,但本地应用没有调用 close()。这100% 是应用代码的 bug——比如异常路径没关连接、连接池没正确归还、forgot to close。

# 1. 找出哪个进程有大量 CLOSE_WAIT
ss -anpt state close-wait | awk '{print $6}' | sort | uniq -c | sort -rn

# 2. 看该进程持有的所有连接
lsof -p <pid> | grep CLOSE_WAIT

# 3. Java 应用看堆栈
jstack <pid> | grep -A 5 "socket"

# 4. 抓包看哪一方先发的 FIN
tcpdump -i any -n "tcp[tcpflags] & tcp-fin != 0"
这个不是 sysctl 能解决的 · TIME_WAIT 可以调内核参数,CLOSE_WAIT 必须改代码。常见原因:HTTP Client 没调 close、JDBC Connection 异常路径漏 close、第三方 SDK bug。临时只能重启进程缓解。

排查应用代码 checklist

  1. 所有 IO 操作是否在 try-with-resources 或 finally 中关闭?
  2. HTTP Client(OkHttp/Apache HttpClient)是否复用且正确 close response.body?
  3. 数据库连接池(HikariCP/Druid)是否正常归还连接?
  4. 第三方 SDK 是否有已知的 CLOSE_WAIT 泄漏 bug?
  5. 异常路径(超时、网络抖动)是否也会执行 close?
03

端口被占用 / 端口不通排查

PORT BINDING AND CONNECTIVITY
高频 入门
# 找谁占用了端口(三选一,功能等价)
ss -lntp | grep :8080
lsof -i :8080
netstat -lntp | grep 8080

# 找到占用的进程后,看详情
ps -ef | grep <pid>

# 确认无害后,杀掉
kill -9 <pid>
# Step 1. 本机是否在监听
ss -lntp | grep 8080
# 看监听地址:
#   127.0.0.1:8080 → 只允许本机连
#   0.0.0.0:8080   → 允许所有 IP 连
#   ::8080         → IPv6 监听

# Step 2. 本机自连测试
curl localhost:8080
curl 127.0.0.1:8080
curl <本机内网 IP>:8080

# Step 3. 防火墙检查
iptables -L -n | grep 8080
firewall-cmd --list-all          # CentOS
ufw status                       # Ubuntu

# Step 4. 远端测试连通性
# 客户端机器执行
telnet <ip> 8080
nc -zv <ip> 8080      # 更现代
curl -v http://<ip>:8080
监听地址本机能连外网能连说明
127.0.0.1仅本机,生产服务器常见错误
0.0.0.0所有网卡,通用
192.168.1.10取决于网络仅指定网卡
速判口诀 · 本机 curl localhost 通,curl 内网 IP 不通 → 监听地址是 127.0.0.1;远端不通但本机内网 IP 通 → 防火墙或安全组;telnet 都不通 → 服务没监听或网络层不通。
04

网络丢包,延迟抖动

PACKET LOSS DIAGNOSIS
中频 进阶
# 1. ping 看丢包率和延迟波动
ping -c 100 <target>
# 看 packet loss 百分比, rtt min/avg/max/mdev

# 2. mtr 是 ping + traceroute 合体,看每一跳的丢包
mtr -r -c 100 <target>
# Loss% 列:看哪一跳开始丢包,问题在那一跳之后

# 3. 网卡级别丢包统计
ip -s link show eth0
# 看 RX errors/dropped, TX errors/dropped

# 或
ethtool -S eth0 | grep -i "drop\|err"
位置查看命令常见原因
网卡硬件ethtool -S eth0线缆问题,网卡故障
网卡 ring bufferethtool -g eth0突发流量,ring buffer 小
软中断处理cat /proc/net/softnet_statCPU 来不及处理
iptablesiptables -nvL防火墙规则丢弃
conntrack 满conntrack -L \| wc -l连接跟踪表溢出
socket 缓冲区ss -tn 看 Recv-Q应用消费慢
# 看当前连接跟踪数
sysctl net.netfilter.nf_conntrack_count
sysctl net.netfilter.nf_conntrack_max

# 接近 max 就丢包,调大
sysctl -w net.netfilter.nf_conntrack_max=2000000

# 看 dmesg 是否有 conntrack 报错
dmesg | grep -i conntrack
# 抓特定端口的包
tcpdump -i eth0 -nn port 8080 -w /tmp/cap.pcap

# 抓重传包(看有没有丢包重传)
tcpdump -i eth0 -nn "tcp[tcpflags] & (tcp-syn|tcp-fin|tcp-rst) != 0"

# 用 wireshark 打开 pcap,Analyze → Expert Information 看重传/丢包
05

Too many open files — 文件句柄耗尽

FILE DESCRIPTOR LIMIT EXHAUSTION
高频 必会
  • 应用报错 "Too many open files" 或 "EMFILE"
  • 新连接被拒绝,无法打开新文件
  • 常见于高并发服务、连接泄漏的应用
级别查看命令修改方式
系统总限制cat /proc/sys/fs/file-maxsysctl fs.file-max
用户限制ulimit -n/etc/security/limits.conf
进程当前使用ls /proc/<pid>/fd \| wc -l
进程允许的最大cat /proc/<pid>/limits启动时继承 ulimit
# 1. 当前用户的限制
ulimit -n
# 默认 1024,生产远远不够

# 2. 看具体进程的限制和使用
cat /proc/<pid>/limits | grep "open files"
ls /proc/<pid>/fd | wc -l

# 3. 看进程打开了哪些 fd(找泄漏)
lsof -p <pid> | awk '{print $5}' | sort | uniq -c | sort -rn
# 类型分布,看 REG/IPv4/sock 等谁占多
# /etc/security/limits.conf,新增
*    soft    nofile    65536
*    hard    nofile    65536
root soft    nofile    65536
root hard    nofile    65536

# 系统级 file-max,/etc/sysctl.conf
fs.file-max = 2097152

# systemd 启动的服务,需要在 service 文件加
[Service]
LimitNOFILE=65536
session 限制坑 · ulimit 在 limits.conf 改了,但 ssh 登录后还是 1024——检查 /etc/pam.d/sshd 是否启用了 pam_limits.so,以及 /etc/ssh/sshd_configUsePAM yes
systemd 服务特殊 · 通过 systemctl 启动的服务,ulimit 不读 limits.conf,必须在 service 文件里设 LimitNOFILE。改完 systemctl daemon-reload 再重启服务。
06

僵尸进程(Z 状态)清理

ZOMBIE PROCESS REAPING
中频 入门

子进程退出后,在父进程调用 wait() 回收其退出状态之前,会保留一个最小的进程项——这就是僵尸。僵尸不消耗 CPU 和内存,只占用一个 PID。少量僵尸无害,大量堆积会让 PID 耗尽。

# 1. 找出所有僵尸进程
ps -ef | grep "<defunct>"
# 或
ps -eo stat,pid,ppid,cmd | awk '$1 ~ /^Z/'

# 2. 找僵尸的父进程(关键!)
# 输出第三列就是 PPID

# 3. 给父进程发 SIGCHLD,让它回收
kill -CHLD <ppid>

# 4. 父进程不响应?只能杀父进程
kill <ppid>
# 父进程死后,僵尸会被 init/systemd(PID 1)接管并回收
kill 僵尸进程没用 · 僵尸本身已经死了,kill 不掉。要么让父进程 wait 它,要么干掉父进程让 init 接管。
容器内的特殊情况 · 容器内 PID 1 通常是应用进程(如 java),不是 init。如果应用 fork 子进程后不 wait,容器内会堆积僵尸。解决:用 tinidumb-init 作为容器 PID 1,它会负责回收僵尸。
07

进程 hang 住,既不退也不动

DEEP PROCESS DEBUGGING
中频 高级
  • 进程 PID 还在,但没有任何 CPU 使用,没有日志输出
  • kill 普通信号无效,只有 kill -9 能干掉
  • 常见于死锁、网络等待、IO 卡死
# 1. 看进程状态
ps -o stat,pid,cmd -p <pid>
# S=可中断睡眠, D=不可中断睡眠(IO 等待), T=停止, R=运行

# 2. 看进程在做什么系统调用
strace -p <pid>
# 卡住的位置就是它在等的系统调用

# 3. 看内核态调用栈
cat /proc/<pid>/stack
cat /proc/<pid>/wchan      # 等的是什么

# 4. 看用户态调用栈
pstack <pid>
# 或 gdb -p <pid> 后 thread apply all bt

# 5. 看打开的 fd 和最近的网络连接
lsof -p <pid>
ls -l /proc/<pid>/fd/
# 1. 看所有线程状态
jstack <pid> > /tmp/stack.txt
grep "java.lang.Thread.State" /tmp/stack.txt | sort | uniq -c

# 2. 找 BLOCKED 线程(死锁征兆)
grep -A 20 "BLOCKED" /tmp/stack.txt

# 3. 自动检测死锁
jstack -l <pid> | grep -A 30 "Found one Java-level deadlock"

# 4. 看 GC 情况(频繁 Full GC 也会让应用看似 hang)
jstat -gcutil <pid> 1000 5
诊断流程 · 状态 D → 跑去看 Case 02(IO 阻塞);状态 S → strace 看在等什么系统调用;Java 应用 → jstack 看线程状态分布,大量 BLOCKED 就是锁问题。
08

DNS 解析慢 / 间歇性失败

RESOLV.CONF / NSSWITCH / CACHE
中频 入门
  • curl 域名慢(几秒才开始返回),但 curl IP 很快
  • 某些域名能解析,某些不能
  • 偶发性的 "Could not resolve host"
# 1. 测解析速度
dig baidu.com
dig +short +time=2 baidu.com

# 2. 指定 DNS 服务器测试(看是不是 DNS 服务器慢)
dig @8.8.8.8 baidu.com
dig @114.114.114.114 baidu.com

# 3. 查配置
cat /etc/resolv.conf
cat /etc/nsswitch.conf | grep hosts
cat /etc/hosts

# 4. 测整个解析过程(看 getaddrinfo 真实耗时)
time getent hosts baidu.com
time curl -o /dev/null -s -w "%{time_namelookup}\n" baidu.com
# 优化前:默认配置可能很慢
nameserver 192.168.1.1
nameserver 8.8.8.8
search domain.local

# 优化后
nameserver 114.114.114.114
nameserver 8.8.8.8
options timeout:2 attempts:2 rotate single-request-reopen
# timeout:2  → 每个 DNS 超时 2 秒(默认 5 秒)
# attempts:2 → 重试 2 次(默认 2 次)
# rotate     → 轮询 DNS 服务器,而不是总用第一个
# single-request-reopen → IPv4/IPv6 并行查询,避免互相阻塞

K8s 默认 ndots:5,意思是域名包含的点数小于 5 时,先按 search 域拼接查询。这会让每次查 baidu.com 都先发 5 个无效查询。

# 解决:用 FQDN(末尾加点)
curl baidu.com.       # 注意结尾的点

# 或在 Pod 里降低 ndots
# spec.dnsConfig.options:
#   - name: ndots
#     value: "2"
resolv.conf 被覆盖 · 很多发行版上 /etc/resolv.conf 是 systemd-resolved 或 NetworkManager 动态生成的,手动改完重启就没了。永久修改要改对应的网络管理工具配置。
本地 DNS 缓存 · 高频解析场景,装个本地缓存(nscd / dnsmasq / systemd-resolved)能省大量时间。容器环境用 NodeLocal DNSCache。
PREVIOUS VOLUME
← Vol.1 性能与资源故障