网络故障的诊断三件套是分层:能 ping 通不代表能 telnet 通,telnet 通不代表 HTTP 通。从 L3(IP 可达) → L4(端口可达) → L7(协议正确)逐层测,定位断在哪一层。进程故障的核心是状态机:每个进程都有"运行/睡眠/僵尸/停止"几种状态,卡住时先确认状态。
排查连接异常前必须知道每个状态意味着什么。ss -ant 输出的状态列就是这些。重点记 TIME_WAIT 和 CLOSE_WAIT 的区别——后者更危险。
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
keepalive 配置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"
# 找谁占用了端口(三选一,功能等价) 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 | ✅ | 取决于网络 | 仅指定网卡 |
# 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 buffer | ethtool -g eth0 | 突发流量,ring buffer 小 |
| 软中断处理 | cat /proc/net/softnet_stat | CPU 来不及处理 |
| iptables | iptables -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 看重传/丢包
| 级别 | 查看命令 | 修改方式 |
|---|---|---|
| 系统总限制 | cat /proc/sys/fs/file-max | sysctl 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
/etc/pam.d/sshd 是否启用了 pam_limits.so,以及 /etc/ssh/sshd_config 的 UsePAM yes。
LimitNOFILE。改完 systemctl daemon-reload 再重启服务。
子进程退出后,在父进程调用 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)接管并回收
tini 或 dumb-init 作为容器 PID 1,它会负责回收僵尸。
# 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
# 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"
/etc/resolv.conf 是 systemd-resolved 或 NetworkManager 动态生成的,手动改完重启就没了。永久修改要改对应的网络管理工具配置。