裸金属服务器 2Gbps 带宽劣化排障实施手册

定位:公开版实施操作手册。目标:任何人或 AI 只读这一篇,就能在相同场景下完成诊断、修复、验证和持久化,并避开已确认的坑。
场景:裸金属服务器(非虚拟化),公网 IP 直发(非 NAT),2Gbps 出口带宽随连接数增加出现抖动/下降。
实测环境:Debian 12 + xanmod 6.x 内核, 双路 Xeon (72 逻辑核), 251GB RAM, ixgbe 10GbE 单口 + VLAN 子接口, 中继/转发类业务 ~4 万并发连接。
⚠️ 安全声明:本文所有 IP、端口、密码、密钥、节点名均为占位符。请替换为你自己的真实值,不要将真实值提交到公开仓库

〇、占位符对照表

在执行本手册前,将以下占位符全局替换为你的实际值:

占位符含义示例格式
<SERVER_IP>服务器公网管理 IP198.51.100.10
<SSH_PORT>SSH 端口22
<SSH_USER>SSH 用户名root
<SSH_PASS>SSH 密码your-password
<SSH_HOSTKEY>SSH 主机密钥指纹ssh-ed25519 255 SHA256:xxxxx
<SSH_KEY_PATH>本地 SSH 私钥路径D:\keys\id_rsa
<NIC>物理网卡名enp130s0 / eth0 / ens5
<VLAN_IF>VLAN 子接口名 (如有)enp130s0.100
<BIZ_IP_A>业务绑定 IP (主)198.51.100.20
<BIZ_IP_B>业务绑定 IP (副, 如有)198.51.100.21
<GW_A>策略路由网关 A198.51.100.1
<APP_BIN>业务进程二进制名my_relay_server
<APP_PID>业务进程 PID12345
<INSTANCE_A>业务实例名 Arelay-primary
<INSTANCE_B>业务实例名 Brelay-secondary

Windows 端 SSH 辅助函数 (PowerShell + plink)

$HK = '<SSH_HOSTKEY>'
function rvps($c) {
    plink -ssh <SSH_USER>@<SERVER_IP> -P <SSH_PORT> -hostkey $HK -pw '<SSH_PASS>' -batch $c 2>&1
}
坑 #1 (PowerShell 转义):plink 传递复杂 bash 命令时,$() / $(()) / awk '{}' 会被 PowerShell 解析。对超过一行的命令,必须用 here-string 写成脚本 → 管道上传 → 远程执行,不要试图在 rvps() 的引号里硬塞。示例见「上传脚本模式」。

上传脚本模式

$script = @'
#!/bin/bash
echo "hello from remote"
whoami
'@
$script | plink -ssh <SSH_USER>@<SERVER_IP> -P <SSH_PORT> -hostkey $HK -pw '<SSH_PASS>' -batch "cat > /tmp/myscript.sh && chmod +x /tmp/myscript.sh && echo ok"
# 然后执行
rvps "bash /tmp/myscript.sh"

一、诊断阶段(只读,不改任何东西)

1.1 环境摸底

uname -a                         # 内核版本
lscpu                            # CPU 拓扑、NUMA 节点
free -h                          # 内存
ip link show                     # 网卡列表 → 记录 <NIC> 和 <VLAN_IF>
ip addr show                     # 确认 IP 绑定
ip rule show                     # 策略路由
ip route show table all | head -50

确认业务进程

ps aux | grep <APP_BIN>
ls /proc/<APP_PID>/fd | wc -l   # fd 数
cat /proc/<APP_PID>/status | grep -E 'Threads|VmRSS'
top -bn1 -p <APP_PID> | tail -1 # CPU%
ss -s                            # 连接总览

1.2 网卡硬件层

ethtool <NIC>                                            # 链路速率
ethtool -i <NIC>                                         # 驱动名+版本
ethtool -g <NIC>                                         # ★ Ring Buffer 当前值 vs 最大值
ethtool -S <NIC> | grep -iE 'drop|error|miss|over|fifo' # ★ 硬件丢包计数
ethtool -l <NIC>                                         # 多队列数
ethtool -c <NIC>                                         # coalesce

判断矩阵

指标正常异常 → 对应修复
rx_missed_errors 持续增长0 或极低→ Fix 1: Ring Buffer
Ring Buffer 当前值 远小于 最大值差距 < 2x→ Fix 1: Ring Buffer
rx_dropped 持续增长0→ 检查 netdev_max_backlog

1.3 内核网络栈

# TCP 缓冲区
sysctl net.ipv4.tcp_rmem
sysctl net.ipv4.tcp_wmem
sysctl net.core.rmem_max
sysctl net.core.wmem_max
sysctl net.ipv4.tcp_mem

# 队列与连接
sysctl net.core.somaxconn
sysctl net.core.netdev_max_backlog
sysctl net.core.netdev_budget
sysctl net.core.netdev_budget_usecs

# 拥塞控制
sysctl net.ipv4.tcp_congestion_control

# conntrack (如在用)
cat /proc/sys/net/netfilter/nf_conntrack_count 2>/dev/null
cat /proc/sys/net/netfilter/nf_conntrack_max 2>/dev/null

1.4 softirq 与中断分布

# IRQ 亲和性
cat /proc/interrupts | grep <NIC> | head -10
# 每个 IRQ 绑定了哪些 CPU?
for irq in $(grep <NIC> /proc/interrupts | awk -F: '{print $1}' | head -5); do
    echo "IRQ $irq: $(cat /proc/irq/$irq/smp_affinity_list)"
done

# softnet_stat — 全局快照
cat /proc/net/softnet_stat | head -10

# time_squeeze 10 秒增量
S1=$(cat /proc/net/softnet_stat); sleep 10; S2=$(cat /proc/net/softnet_stat)
# 对比第三列 (squeeze) 的变化

# irqbalance 状态
systemctl is-active irqbalance

NUMA 感知判断

# NIC 在哪个 NUMA 节点?
cat /sys/class/net/<NIC>/device/numa_node
# 该节点对应哪些 CPU?
lscpu | grep "NUMA"
坑 #2 (IRQ 亲和性误判):如果 NIC 在 NUMA node 1,IRQ 绑定到 node 1 的 CPU(如全部奇数核)是 正确的 NUMA-local 策略,不是"分布不均"。不要盲目把 IRQ 打散到所有核——跨 NUMA 访问内存会增加延迟。

1.5 RPS/RFS 状态

cat /sys/class/net/<NIC>/queues/rx-0/rps_cpus
cat /sys/class/net/<VLAN_IF>/queues/rx-0/rps_cpus 2>/dev/null
cat /proc/sys/net/core/rps_sock_flow_entries

如果 rps_cpus 全零 → RPS 未启用,所有 softirq 仅在硬中断所在 CPU 处理。

1.6 TCP 异常计数器(关键指标)

nstat -az | grep -E 'TcpExtTCPRcvCollapsed|TcpExtPruneCalled|TcpExtListenDrops|TcpExtTCPAbortOnData|TcpRetransSegs'

10 秒采样

C1=$(nstat -az TcpExtTCPRcvCollapsed | awk 'NR==2{print $2}')
sleep 10
C2=$(nstat -az TcpExtTCPRcvCollapsed | awk 'NR==2{print $2}')
echo "delta=$((C2-C1)) per_sec=$(( (C2-C1)/10 ))"
计数器含义增长率 > 0 意味着
TcpExtTCPRcvCollapsed内核合并接收缓冲区 skb应用 recv() 读取不够快
TcpExtPruneCalled内核主动丢弃接收缓冲区数据比 Collapsed 更严重
TcpExtListenDrops监听队列溢出somaxconn / backlog 不够
TcpRetransSegsTCP 重传网络质量问题或对端处理慢

1.7 定时任务与杂项

crontab -l                              # 任何 drop_caches / clean_mem ?
dmesg -T | grep drop_caches | tail -5   # 曾触发过 drop_caches?
systemctl --failed                      # 有没有失败的服务
dmesg -T | grep -iE 'error|oom|panic|call trace' | tail -20

1.8 VLAN 子接口队列数

ls /sys/class/net/<VLAN_IF>/queues/ 2>/dev/null
坑 #3 (VLAN 单队列):VLAN 子接口通常只有 1 个 rx 队列,即使物理网卡有 63 个队列。所有子接口流量只由一个 softirq 上下文处理。这就是为什么 RPS 对 VLAN 场景尤其重要——它是把单队列 softirq 负载分散到多核的唯一途径。

二、诊断结论模板

完成第一阶段后,填写此表:

#问题严重性证据
1Ring Buffer 过小,rx_missed_errors 持续增长★★★ / 无ethtool -g / ethtool -S
2TCPRcvCollapsed 持续增长★★★ / 无nstat 10s 采样值
3softirq time_squeeze★★ / 无/proc/net/softnet_stat
4RPS/RFS 禁用★★ / 无rps_cpus
5定时 drop_caches★ / 无crontab 内容
6conntrack 表满★★★ / 无count vs max
7其他

三、修复操作(按优先级排序)

Fix 1: 扩大 Ring Buffer(消除硬件丢包)

前提条件ethtool -g <NIC> 显示当前值远小于最大值,且 rx_missed_errors 持续增长。

# 记录修复前基线
ethtool -S <NIC> | grep rx_missed

# 执行 (一条命令, 立即生效, 不中断流量)
ethtool -G <NIC> rx 8192 tx 8192

# 验证
ethtool -g <NIC>          # 确认 Current = 8192
ethtool -S <NIC> | grep rx_missed  # 记录值

# 30 秒后再查一次,确认增量 = 0
sleep 30
ethtool -S <NIC> | grep rx_missed

风险:极低。热生效,无需中断网络。
持久化:重启后回退!需要 systemd service:

cat > /etc/systemd/system/nic-ring-buffer.service << 'EOF'
[Unit]
Description=Set NIC ring buffer size
After=network-pre.target
Before=network.target

[Service]
Type=oneshot
ExecStart=/usr/sbin/ethtool -G <NIC> rx 8192 tx 8192
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable nic-ring-buffer.service

Fix 2: 启用 RPS/RFS(softirq 多核分发)

前提条件rps_cpus 全零,或 VLAN 子接口仅 1 个 rx 队列。

CPU 掩码计算

# 获取 CPU 数
nproc  # 例如 72

# 72 个 CPU 全覆盖的掩码:
# 72 bit = 9 bytes = 0xff,0xffffffff,0xffffffff
# 通用公式: python3 -c "n=72; h=hex((1<<n)-1)[2:]; print(','.join([h[max(0,len(h)-8*(i+1)):len(h)-8*i] for i in range(len(h)//8,-1,-1) if h[max(0,len(h)-8*(i+1)):len(h)-8*i]]))"
# 物理网卡所有 rx 队列
for rxq in /sys/class/net/<NIC>/queues/rx-*/rps_cpus; do
    echo "ff,ffffffff,ffffffff" > "$rxq"
done

# VLAN 子接口 (如有)
for rxq in /sys/class/net/<VLAN_IF>/queues/rx-*/rps_cpus; do
    echo "ff,ffffffff,ffffffff" > "$rxq"
done

# RFS 全局表
echo 65536 > /proc/sys/net/core/rps_sock_flow_entries

# RFS 每队列表 — 物理网卡
NRXQ=$(ls -d /sys/class/net/<NIC>/queues/rx-* | wc -l)
PER_Q=$((65536 / NRXQ))
for rxq in /sys/class/net/<NIC>/queues/rx-*/rps_flow_cnt; do
    echo "$PER_Q" > "$rxq"
done

# RFS 每队列表 — VLAN 子接口
NRXQ_V=$(ls -d /sys/class/net/<VLAN_IF>/queues/rx-* 2>/dev/null | wc -l)
if [ "$NRXQ_V" -gt 0 ]; then
    PER_Q_V=$((65536 / NRXQ_V))
    for rxq in /sys/class/net/<VLAN_IF>/queues/rx-*/rps_flow_cnt; do
        echo "$PER_Q_V" > "$rxq"
    done
fi

验证

# 确认配置写入
cat /sys/class/net/<NIC>/queues/rx-0/rps_cpus
cat /sys/class/net/<VLAN_IF>/queues/rx-0/rps_cpus
cat /proc/sys/net/core/rps_sock_flow_entries

# 30 秒后检查 softnet_stat — 之前全零的偶数 CPU 应该开始有 processed 计数
# (用 /proc/net/softnet_stat 第一列, 每行一个 CPU)

风险:极低。热生效,如果有问题写回 00000000 即可还原。
持久化:重启后回退!systemd service:

cat > /etc/systemd/system/nic-rps-rfs.service << 'EOF'
[Unit]
Description=Enable RPS/RFS on all NIC queues
After=network-pre.target
Before=network.target

[Service]
Type=oneshot
ExecStart=/bin/bash -c '\
  for q in /sys/class/net/<NIC>/queues/rx-*/rps_cpus; do echo ff,ffffffff,ffffffff > "$q"; done; \
  for q in /sys/class/net/<VLAN_IF>/queues/rx-*/rps_cpus 2>/dev/null; do echo ff,ffffffff,ffffffff > "$q"; done; \
  echo 65536 > /proc/sys/net/core/rps_sock_flow_entries; \
  for q in /sys/class/net/<NIC>/queues/rx-*/rps_flow_cnt; do echo 1024 > "$q"; done; \
  for q in /sys/class/net/<VLAN_IF>/queues/rx-*/rps_flow_cnt 2>/dev/null; do echo 32768 > "$q"; done'
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable nic-rps-rfs.service

Fix 3: IRQ 亲和性 / softirq 预算(按需)

先判断是否需要

systemctl is-active irqbalance   # 如果 active → 已在自动平衡
cat /sys/class/net/<NIC>/device/numa_node   # NIC 的 NUMA 节点
坑 #4 (盲目调整 IRQ):如果 NIC 在 NUMA node 1,irqbalance 把 IRQ 绑到 node 1 CPU 是 最优策略。不要强制打散到所有 CPU。跨 NUMA 内存访问比不均衡更致命。

如果 irqbalance 未运行

apt install -y irqbalance
systemctl enable --now irqbalance

softirq 预算(仅在 time_squeeze 持续增长时才调):

# 先看当前值
sysctl net.core.netdev_budget
sysctl net.core.netdev_budget_usecs

# 如果 budget < 600 或 budget_usecs < 4000, 考虑提高
# 但如果已经 >= 1000/8000, 不要动
坑 #5 (盲目调参):我们实测发现服务器已有 netdev_budget=1000, budget_usecs=8000(远高于常见建议的 600/4000)。永远先查当前值再决定是否修改。 预设方案的值可能比线上低——"优化"反而是降级。

Fix 4: TCP 缓冲区调整(通常已够大)

坑 #6 (最大的教训):我们在实测中发现服务器的 TCP 缓冲区已经是 32MB(而预设方案假设是 4MB 默认值),差点把 32MB 降到 16MB。必须先 sysctl net.ipv4.tcp_rmem 查看当前值,如果已经 >= 预设目标,不要碰。
# 先看当前值!
sysctl net.ipv4.tcp_rmem
sysctl net.ipv4.tcp_wmem
sysctl net.core.rmem_max
sysctl net.core.wmem_max
sysctl net.ipv4.tcp_mem

仅当当前值明显偏小才执行

sysctl -w net.ipv4.tcp_rmem="4096 131072 16777216"
sysctl -w net.ipv4.tcp_wmem="4096 32768 16777216"
sysctl -w net.core.rmem_max=16777216
sysctl -w net.core.wmem_max=16777216

回滚(如果不小心执行了):

# 立刻恢复原值 (替换为你之前记录的值)
sysctl -w net.ipv4.tcp_rmem="4096 131072 33554432"
sysctl -w net.ipv4.tcp_wmem="4096 131072 33554432"
sysctl -w net.core.rmem_max=33554432
sysctl -w net.core.wmem_max=33554432

持久化:写入 /etc/sysctl.d/99-tcp-tuning.conf,然后 sysctl --system

Fix 5: 停止定时 drop_caches

# 查找触发源
crontab -l | grep -i 'drop_caches\|clean_mem'
grep -rl 'drop_caches' /etc/cron* /var/spool/cron/ 2>/dev/null
systemctl list-timers | grep -i clean

# 注释掉 crontab 条目
crontab -e
# 在对应行前加 ## 注释

# 验证
dmesg -T | grep drop_caches | tail -3   # 记录最后一次时间
# 等 20 分钟后再查,如果没有新的 → 修复成功
为什么不该 drop_caches:在 251GB RAM、仅用 10GB 的机器上,drop_caches 会强制释放所有 page cache/dentries/inodes,导致后续文件 I/O 全部 cache miss。对网络密集型中继服务来说,周期性 cache 抖动 = 周期性延迟毛刺。

四、修复后综合验证

4.1 快速验证脚本

#!/bin/bash
echo "=== 1. Ring Buffer ==="
ethtool -g <NIC> | grep -A4 "Current"

echo "=== 2. rx_missed_errors (应为 0 增长) ==="
V1=$(ethtool -S <NIC> | awk '/rx_missed_errors/{print $2}')
sleep 10
V2=$(ethtool -S <NIC> | awk '/rx_missed_errors/{print $2}')
echo "before=$V1 after=$V2 delta=$((V2-V1))"

echo "=== 3. RPS ==="
cat /sys/class/net/<NIC>/queues/rx-0/rps_cpus
cat /sys/class/net/<VLAN_IF>/queues/rx-0/rps_cpus 2>/dev/null
cat /proc/sys/net/core/rps_sock_flow_entries

echo "=== 4. time_squeeze (10s delta) ==="
T1=0
while read line; do
  hex=$(echo "$line" | cut -c19-26)
  val=$(printf "%d" "0x$hex" 2>/dev/null)
  T1=$((T1+val))
done < /proc/net/softnet_stat
sleep 10
T2=0
while read line; do
  hex=$(echo "$line" | cut -c19-26)
  val=$(printf "%d" "0x$hex" 2>/dev/null)
  T2=$((T2+val))
done < /proc/net/softnet_stat
echo "squeeze_delta_10s=$((T2-T1))"

echo "=== 5. TCPRcvCollapsed (10s delta) ==="
C1=$(nstat -az TcpExtTCPRcvCollapsed | awk 'NR==2{print $2}')
sleep 10
C2=$(nstat -az TcpExtTCPRcvCollapsed | awk 'NR==2{print $2}')
echo "collapsed_delta_10s=$((C2-C1)) per_sec=$(( (C2-C1)/10 ))"

echo "=== 6. drop_caches ==="
dmesg -T | grep drop_caches | tail -1

echo "=== 7. General ==="
uptime
free -h | head -2
ss -s
坑 #7 (mawk vs gawk):Debian 默认 awk 是 mawk,不支持 strtonum()。解析 /proc/net/softnet_stat 的十六进制时,用 cut + printf "%d" "0x$hex" 替代。上面的脚本已经规避了此问题。

4.2 验证通过标准

指标预期未通过则
rx_missed_errors 10s 增量0检查 Ring Buffer 是否生效
time_squeeze 10s 增量< 10检查 RPS 和 netdev_budget
RPS cpumask非全零重新写入 rps_cpus
drop_caches 最后时间20 分钟前以上检查 crontab
TCPRcvCollapsed 速率观测记录即可这是应用层问题,见第五章

五、应用层瓶颈识别(TCPRcvCollapsed)

如果内核层修复全部到位、rx_missed=0、squeeze≈0,但 TCPRcvCollapsed 仍持续增长(如 >1000/s) → 几乎可以确定是应用层处理跟不上

5.1 证据采集

# 进程画像
echo "=== Process profile ==="
cat /proc/<APP_PID>/status | grep -E 'Threads|VmRSS|VmSize'
ls /proc/<APP_PID>/fd | wc -l
top -bn1 -p <APP_PID> | tail -1

# socket 队列分析
echo "=== Recv-Q top 10 ==="
ss -tn | awk 'NR>1 && $2>0' | sort -k2 -rn | head -10
echo "=== Send-Q top 10 ==="
ss -tn | awk 'NR>1 && $3>0' | sort -k3 -rn | head -10

# 连接分布
ss -tn | awk 'NR>1{split($4,a,":"); print a[1]}' | sort | uniq -c | sort -rn | head -10

5.2 判断逻辑

数据路径:
网卡 → Ring Buffer → 内核 softirq → TCP 协议栈 → socket recv 队列 → 应用 recv()
 ✅ Fix1        ✅ Fix2/3          ✅ Fix4          ⚠️ 瓶颈在此         ← 应用层

如果 TCP 参数已到极致(32MB rmem、somaxconn >100K)、硬件层不丢包、softirq 不 squeeze,那 TCPRcvCollapsed 持续增长 只有一个可能:应用没来得及调 recv(),内核被迫合并 skb 腾空间

5.3 典型应用层瓶颈特征

特征含义
线程数 << 连接数/100每线程负担的连接过多
CPU < 200% 但连接 > 2 万不是 CPU 瓶颈,是 I/O 调度受限
RSS 恒定不变没有内存泄漏,但也没动态扩展缓冲
Send-Q 积压 > 10MB下游拉不走 → write 阻塞 → 线程被占 → 其他连接 recv 延迟

级联效应:少量慢客户端 Send-Q 满 → 该连接 write 阻塞 → 线程被占 → 同线程其他连接 recv 延后 → Recv-Q 积压 → TCPRcvCollapsed。

5.4 给应用开发者的建议

  1. 增加处理并行度:139 线程 / 38K 连接 → 273 连接/线程,偏高。72 核机器可支撑 500+ 线程
  2. 检查阻塞点:如果 CPU < 200%,说明线程在等 I/O,非阻塞读写分离可能缺失
  3. 连接级流控:对 Send-Q 持续积压的慢速下游设超时或限速
  4. 多实例部署:拆端口到多进程,各自 CPU 绑定

六、压力测试监测模板

用 iperf3 做高压测试时,同步监测系统指标:

#!/bin/bash
ROUNDS=8
INTERVAL=30
NIC="<NIC>"

BM=$(ethtool -S $NIC | awk '/rx_missed_errors/{print $2}')
BD=$(ethtool -S $NIC | awk '/rx_dropped/{print $2}')
BC=$(nstat -az TcpExtTCPRcvCollapsed | awk 'NR==2{print $2}')
BP=$(nstat -az TcpExtPruneCalled      | awk 'NR==2{print $2}')
BR=$(nstat -az TcpRetransSegs         | awk 'NR==2{print $2}')

echo "BASELINE: missed=$BM dropped=$BD collapse=$BC prune=$BP retrans=$BR"

for ((i=1; i<=ROUNDS; i++)); do
    echo "==== ROUND $i $(date +%H:%M:%S) ===="

    # 流量速率
    RX1=$(cat /sys/class/net/$NIC/statistics/rx_bytes)
    TX1=$(cat /sys/class/net/$NIC/statistics/tx_bytes)
    RP1=$(cat /sys/class/net/$NIC/statistics/rx_packets)
    sleep $INTERVAL
    RX2=$(cat /sys/class/net/$NIC/statistics/rx_bytes)
    TX2=$(cat /sys/class/net/$NIC/statistics/tx_bytes)
    RP2=$(cat /sys/class/net/$NIC/statistics/rx_packets)
    echo "traffic: rx=$((  (RX2-RX1)*8/INTERVAL/1000000 ))Mbps tx=$(( (TX2-TX1)*8/INTERVAL/1000000 ))Mbps rx_pps=$(( (RP2-RP1)/INTERVAL ))"

    # NIC 错误 (与基线比)
    CM=$(ethtool -S $NIC | awk '/rx_missed_errors/{print $2}')
    CD=$(ethtool -S $NIC | awk '/rx_dropped/{print $2}')
    echo "nic: rx_missed=$CM(+$((CM-BM))) rx_dropped=$CD(+$((CD-BD)))"

    # TCP 计数器
    CC=$(nstat -az TcpExtTCPRcvCollapsed | awk 'NR==2{print $2}')
    CP=$(nstat -az TcpExtPruneCalled      | awk 'NR==2{print $2}')
    CR=$(nstat -az TcpRetransSegs         | awk 'NR==2{print $2}')
    echo "tcp: collapse=$CC(+$((CC-BC))) prune=$CP(+$((CP-BP))) retrans=$CR(+$((CR-BR)))"

    # 系统
    echo "load: $(cat /proc/loadavg)"
    echo "mem_avail: $(awk '/MemAvailable/{print $2}' /proc/meminfo)kB"
    ss -s | head -2
    echo
done

echo "==== TOTAL DELTA ===="
FM=$(ethtool -S $NIC | awk '/rx_missed_errors/{print $2}')
FD=$(ethtool -S $NIC | awk '/rx_dropped/{print $2}')
FC=$(nstat -az TcpExtTCPRcvCollapsed | awk 'NR==2{print $2}')
echo "rx_missed: +$((FM-BM))  rx_dropped: +$((FD-BD))  collapse: +$((FC-BC))"

七、持久化检查表

操作重启后保留?持久化方式
Ring Buffer 8192systemd service (见 Fix 1)
RPS/RFSsystemd service (见 Fix 2)
TCP sysctl 参数/etc/sysctl.d/*.conf
netdev_budget sysctl/etc/sysctl.d/*.conf
drop_caches 停止crontab 已注释
irqbalancesystemctl enable
必须确认ethtool -Grps_cpus 写入在 /sys 下的虚拟文件系统,重启即丢失。如果不做 systemd service,每次重启后必须手动重新执行。

八、已确认的坑汇总

#后果正确做法
1PowerShell 里 rvps() 传复杂 bash$() 被 PS 解析,` ` 被拆分写成脚本 → pipe 上传 → 远程执行
2IRQ 只在奇数核 = "分布不均"强行打散 = 跨 NUMA 降性能先查 numa_node,IRQ 在同 NUMA 是正确的
3VLAN 子接口仅 1 个 rx 队列所有子接口流量挤一个 softirq用 RPS 分散(Fix 2)
4不看当前值就调 TCP 参数32MB 降到 16MB = 降级永远先 sysctl 查当前值
5netdev_budget 按文章写 600服务器已经 1000,写 600 = 降级同上,先查再调
6mawk 没有 strtonum()脚本静默失败或输出 0printf "%d" "0x$hex" 替代
7nohup cmd > file 2>&1 & 在 PowerShell SSH 中重定向被 PS 解析整条命令包在单引号里,或走上传脚本模式
8TCPRcvCollapsed ≠ 内核问题在内核参数已极致的前提下,唯一原因是应用 recv() 慢排查应用层而非继续调内核
9iperf3 压测期间 Collapsed 倍增正常现象:额外流量加重应用负担关注 rx_missed 和 squeeze,不要被 Collapsed 涨了吓到
10ethtool -G / echo > rps_cpus 重启后丢失问题"复发"必须做 systemd service 持久化

九、实测数据快照(本次行动)

以下数据供参考对比,数值会因环境不同而异。

修复前 vs 修复后

指标修复前修复后变化
Ring Buffer5128192×16
rx_missed_errors 增量/30s数千~数万0归零
time_squeeze 增量/30s数百<10近零
RPS even CPU processed/30s0~12,000从零到分担负载
TCPRcvCollapsed/s (空闲)~7,400~7,400无变化 (应用层)
TCPRcvCollapsed/s (iperf3 压测)~30,000高压下倍增 (预期)

iperf3 高压测试期间 (8 轮 × 30s)

指标结果
平均流量rx ≈ 2.7 Gbps, tx ≈ 2.7 Gbps
rx_missed_errors+0 (全程零增长)
rx_dropped+42 (极少, 可忽略)
TCPRcvCollapsed+2,060,664 (应用层, 预期)
TcpRetransSegs+416,540
系统负载2.1~3.4 (72 核, 完全正常)
内存241 GB 可用 (无压力)

结论:内核/网卡层修复在高压下完全稳定。rx_missed 全程归零,squeeze 近零。TCPRcvCollapsed 增长仅反映应用层在高压下更来不及消费数据。


十、故障排查决策树

带宽下降/抖动
├── ethtool -S <NIC> | grep rx_missed → 持续增长?
│   ├── 是 → Fix 1: Ring Buffer
│   └── 否 ↓
├── /proc/net/softnet_stat 第 3 列 → squeeze 持续增长?
│   ├── 是 → 检查 IRQ 亲和性 + RPS → Fix 2/3
│   └── 否 ↓
├── nstat TcpExtTCPRcvCollapsed → 持续增长?
│   ├── 是 → 内核 TCP 参数已经很高?
│   │   ├── 否 → Fix 4: 调大 tcp_rmem 等
│   │   └── 是 → 应用层瓶颈 → 第五章
│   └── 否 ↓
├── nstat TcpExtListenDrops → 持续增长?
│   ├── 是 → 调大 somaxconn / backlog
│   └── 否 ↓
├── conntrack count ≥ 80% max?
│   ├── 是 → 调大 conntrack_max + hashsize
│   └── 否 ↓
├── tc qdisc show → 有 tbf/htb 限速?
│   ├── 是 → 调整或移除
│   └── 否 ↓
└── dmesg | grep -i error → 硬件故障?
    ├── 是 → 联系供应商
    └── 否 → 抓包分析具体连接

手册版本: 2026-04-06 v1.0 — 基于火山引擎裸金属实测

最后修改:2026 年 04 月 06 日
如果觉得我的文章对你有用,请随意赞赏