add linux troubleshoot

This commit is contained in:
Your Name
2026-09-16 18:13:56 +08:00
parent 694359d9a0
commit 8a1aebe250
3 changed files with 1273 additions and 0 deletions
+305
View File
@@ -0,0 +1,305 @@
---
title: netstat 统计 TCP/UDP 连接状态
date: 2026-09-14
tags:
- linux
categories:
- linux
---
## 1. 经典命令解析
```bash
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a,S[a]}'
```
**作用:统计当前所有 TCP 连接按状态分组的数目**(不是只查 TIME_WAIT,TIME_WAIT 只是输出中可能出现的一种状态)。
实际输出示例:
```
ESTABLISHED 4
TIME_WAIT 2
```
### 逐段解析
| 部分 | 作用 |
|---|---|
| `netstat -n` | 列出所有连接,`-n` 用数字 IP/端口,不做 DNS 反解(更快) |
| `/^tcp/` | 只匹配以 `tcp` 开头的行(即 TCP 连接) |
| `++S[$NF]` | 用每行**最后一列**(连接状态 State)作为 key 计数 |
| `END {for(a in S) print a,S[a]}` | 遍历数组,打印每种状态及数量 |
## 2. 常见 TCP 状态含义
| 状态 | 含义 | 异常信号 |
|---|---|---|
| `ESTABLISHED` | 已建立的正常连接 | — |
| `TIME_WAIT` | 主动关闭方等待 2MSL | 大量出现(成千上万)说明短连接过多,考虑 keep-alive / 连接池 |
| `CLOSE_WAIT` | 对方已关闭、本端还没 close | 大量堆积通常是**程序 bug**(socket 没正确关闭) |
| `LISTEN` | 监听中的服务端口 | — |
| `SYN_SENT` / `SYN_RECV` | 握手进行中 | 异常增多可能是网络问题或 SYN 攻击 |
## 3. 只看 TIME_WAIT 的写法
```bash
# netstat 精确过滤
netstat -n | awk '/^tcp/ && $NF=="TIME_WAIT" {n++} END {print n+0}'
# 或用 ss(推荐,速度快很多)
ss -ant state time-wait | wc -l
# ss 自带汇总
ss -s
```
## 4. 深入:Recv-Q / Send-Q 队列详解
`ss -ant` / `netstat -ant` 输出的第 2、3 列 Recv-Q 和 Send-Q 是排查「连接被拒绝」「请求超时」「应用处理慢」类问题的第一手指标。
### 4.1 Recv-Q 到底是什么
Recv-Q 的含义取决于 socket 所处的状态,两种场景完全不同:
| socket 状态 | Recv-Q 含义 | Send-Q 含义 |
|---|---|---|
| **LISTEN**(监听中) | 已完成三次握手、**等待应用 accept()** 的连接数(全连接队列当前长度) | 全连接队列的最大容量(backlog) |
| **ESTABLISHED**(已建立) | 内核已收到、但应用**还没 read()** 的数据字节数 | 已发送但尚未被对端 ACK 的字节数 |
> ⚠️ 老内核(2.x 及更早的一些版本)中 LISTEN 行的 Send-Q 列显示为 0,新内核(3.10+ 配合新版 iproute2)显示的是 backlog 上限。现代发行版直接看 `ss -lnt` 即可。
### 4.2 查看方式一:ss 命令(推荐)
`ss`(socket statistics)来自 iproute2 包,直接读内核 netlink,比 netstat 快几个数量级,是现在的标准工具。
```bash
# 查看所有 TCP 连接的 Recv-Q / Send-Q
ss -ant
# 只看监听端口(Recv-Q = 等待 accept 的连接数,Send-Q = backlog 上限)
ss -lnt
# 只看已建立的连接
ss -ant state established
# 按状态过滤 + 带进程信息(哪个进程积压了数据)
ss -antp state established
# 只过滤某个端口
ss -antp 'sport = :80'
```
实际输出示例:
```
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 4096 127.0.0.54:53 0.0.0.0:*
LISTEN 0 2048 127.0.0.1:8088 0.0.0.0:*
LISTEN 0 511 0.0.0.0:80 0.0.0.0:*
LISTEN 0 4096 0.0.0.0:22 0.0.0.0:*
ESTAB 1024 0 10.0.0.1:80 10.0.0.2:52314
```
解读:
- `:80` 是 LISTEN 行:Send-Q `511` 是 backlog 上限(nginx 默认 511),Recv-Q `0` 说明 accept 队列没有积压。
- 最后一行 ESTAB:Recv-Q `1024` 表示有 1024 字节数据躺在内核缓冲区里,应用还没读。
进阶参数:
```bash
# -m 显示 socket 内存使用(rb=接收缓冲区上限,r=实际占用)
ss -lntm
# -i 显示 TCP 内部信息(拥塞窗口、RTT、重传等)
ss -anti state established
# -o 显示定时器信息(keepalive/retrans)
ss -anto
```
`ss -ltnm` 输出示例:
```
LISTEN 0 4096 127.0.0.54:53 0.0.0.0:*
skmem:(r0,rb131072,t0,tb16384,f0,w0,o0,bl0,d0)
```
其中 `r0` = 接收队列当前字节数,`rb131072` = 接收缓冲区上限 128KB。
### 4.3 查看方式二:netstat(老命令)
```bash
netstat -ant # 所有 TCP
netstat -lnt # 只监听
netstat -antp # 带进程(需要 root)
```
输出列含义与 ss 一致。注意:netstat 逐条遍历 `/proc/net/tcp` 并做解析,大连接数机器上非常慢,线上优先用 `ss`。
### 4.4 查看方式三:直接读 /proc/net 目录
内核把 socket 信息暴露在 procfs 下,不依赖额外工具,适合容器/精简环境或写脚本:
```bash
cat /proc/net/tcp # TCP IPv4
cat /proc/net/tcp6 # TCP IPv6
cat /proc/net/udp # UDP
cat /proc/net/unix # Unix socket
```
字段格式:
```
sl local_address rem_address st tx_queue:rx_queue tr tm->when retrnsmt uid timeout inode
0: 3600007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 992 0 731603 1 ...
```
关键列:
| 列 | 含义 |
|---|---|
| `local_address` | 本地地址,格式 `十六进制IP:十六进制端口`,IP 是**小端序** |
| `rem_address` | 对端地址,同上 |
| `st` | socket 状态,`0A` = LISTEN,`01` = ESTABLISHED |
| `tx_queue:rx_queue` | **发送队列:接收队列,均为十六进制字节数** —— 这里的 `rx_queue` 就是 Recv-Q |
手工解码示例:`3600007F:0035`
- IP `3600007F` 小端 → 字节序 `7F 00 00 36` → `127.0.0.54`
- 端口 `0035` → 十进制 53
用 awk 快速筛出 rx_queue 非 0 的连接:
```bash
awk 'NR>1 {split($5,q,":"); if (strtonum("0x" q[2]) > 0) print $2, $3, $4, strtonum("0x" q[2])}' /proc/net/tcp
```
> 由 inode 反查进程:`ls -l /proc/<pid>/fd` 中 `socket:[731603]` 的编号对应 inode 列。
### 4.5 典型排查场景
**场景一:LISTEN 的 Recv-Q 持续打满 Send-Q(backlog)**
现象:`ss -lnt` 看到 `Recv-Q ≈ Send-Q`,客户端报 connection refused / timeout。
原因:应用 accept() 速度跟不上握手完成速度,全连接队列溢出,内核开始丢新连接。
确认丢包计数:
```bash
nstat -az | grep -iE 'listenoverflow|listendrop'
# ListenOverflows ← 半连接/全连接队列溢出次数
# ListenDrops ← 因队列满被丢弃的连接数
```
处理方向:
1. 调大应用 backlog(如 nginx `listen 80 backlog=8192;`、Java 的 `backlog` 参数);
2. 调大内核上限:`net.core.somaxconn`(默认常见 128/4096)、`net.ipv4.tcp_max_syn_backlog`(半连接队列);
3. 治本:提升应用 accept 能力(多 worker、SO_REUSEPORT)。
```bash
sysctl -w net.core.somaxconn=4096
sysctl -w net.ipv4.tcp_max_syn_backlog=4096
```
**场景二:ESTABLISHED 的 Recv-Q 持续增长**
现象:某些 ESTAB 连接的 Recv-Q 越来越大且不回落。
含义:对端一直在发,本端应用读得太慢或不读(业务卡死、GC、线程池打满)。
定位:
```bash
# 找到是哪个进程
ss -antp state established | awk 'NR==1 || $2 > 0'
# 持续观察,5 秒采样一次
watch -n5 "ss -antp | awk 'NR==1 || \$2 > 0'"
```
处理方向:查该进程的 CPU/线程/GC 状态,确认消费逻辑是否阻塞。
**场景三:UDP 的 Recv-Q**
UDP 没有连接,`ss -anu` / `cat /proc/net/udp` 里的 rx_queue 表示**已收到但未被应用读取的报文字节数**。UDP 接收缓冲区满后报文被**静默丢弃**,可配合计数器确认:
```bash
nstat -az | grep -iE 'udprcvbuferrors|udpinerrors'
```
### 4.6 持续监控写法
```bash
# 每 2 秒刷新所有队列非空的连接
watch -n2 'ss -ant | awk "NR==1 || \$2>0 || \$3>0"'
# 只盯 80 端口 accept 队列
watch -n1 "ss -lnt 'sport = :80'"
```
采集进监控体系的话,Prometheus node_exporter 的 `node_netstat` / 自定义 textfile 脚本解析 `ss -ant` 输出即可。
### 4.7 速查表
| 需求 | 命令 |
|---|---|
| 看所有连接队列 | `ss -ant` |
| 看 accept 队列积压 | `ss -lnt`(Recv-Q vs Send-Q) |
| 找积压数据的进程 | `ss -antp` |
| 看 socket 内存占用 | `ss -lntm` / `ss -tm` |
| 裸文件方式 | `cat /proc/net/tcp`(第 5 列 `tx_queue:rx_queue`,hex) |
| accept 队列丢包计数 | `nstat -az \| grep -i listen` |
| 调大 accept 队列 | `net.core.somaxconn` + 应用 backlog 参数 |
| 持续观察 | `watch -n2 'ss -ant'` |
## 5. UDP 为什么不能照搬这个命令
关键区别:**UDP 是无连接的,没有 TIME_WAIT 这类状态**。
- `netstat -anu` 输出中 UDP 行的 **State 列是空的**
- 若还按 `$NF` 分组,统计到的是倒数第一列 `0.0.0.0:*`(Foreign Address),毫无意义
- 另外 `netstat -n` 不加 `-a` 时,大部分 UDP socket(未 connect 的)根本不显示
UDP 行长这样(State 列为空):
```
udp 0 0 127.0.0.54:53 0.0.0.0:*
```
## 6. UDP 的正确排查姿势
```bash
# 1. 看所有 UDP socket(必须加 -a,否则大部分不显示)
netstat -anu
# 2. 按本地端口统计,看哪些服务开着 UDP
netstat -anu | awk '/^udp/ {split($4,a,":"); ++S[a[length(a)]]} END {for(p in S) print p, S[p]}'
# 3. 用 ss 看状态计数(UDP 只有 UNCONN / ESTAB 两种)
ss -uan | awk 'NR>1 {++S[$1]} END {for(a in S) print a, S[a]}'
# 4. UDP 整体健康度:丢包/错误统计(UDP 排查最该看的)
netstat -su
```
`ss` 里 UDP 的两种状态:
- `UNCONN`:未调用 `connect()` 的 socket,约等于"监听/空闲"
- `ESTAB`:调用过 `connect()` 的 UDP socket
## 7. TCP vs UDP 排查对照表
| TCP 里看什么 | UDP 里对应看什么 |
|---|---|
| TIME_WAIT 堆积 | 没有这个概念 |
| CLOSE_WAIT 泄漏 | socket 数量是否只增不减(句柄泄漏) |
| 连接异常 | `netstat -su` 里的 `packet receive errors`、`RcvbufErrors`、丢包计数 |
| 连接数 | Recv-Q 列(netstat 第 2 列)是否长期非 0,说明应用消费不过来、缓冲区积压 |
## 8. 一句话总结
> TCP 按状态统计用 `$NF` 没问题;搬到 UDP 上时,把思路换成 **按端口统计 + 看 Recv-Q 积压 + `netstat -su` 看丢包**。Recv-Q 积压的通用规律:**LISTEN 满 = accept 不过来,ESTAB 涨 = read 不过来**。