2690 字
约 8 分钟
15
S02-J04 · 网络性能与内核参数:从现象到参数的对应关系

内网服务 p99 从 8ms 涨到 120ms,第一反应是抄一份"高并发 sysctl 清单"。改完重启,第二天问题还在——瓶颈不在参数上。内核参数只在一种情况下有用:已定位到某层队列满或某层在丢包,且这层的容量正好由某个参数决定。


1. 前提:参数是最后一公里

顺序 要回答的问题 工具
1 慢在哪层:应用、队列、协议栈、网卡 分段计时、ss、netstat -s
2 该层有没丢包/溢出计数在涨 看增量,不看绝对值
3 该计数是否由某个 sysctl 决定 ip-sysctl.txt
4 能否同负载复现改善 改前改后同条件对照

跳过前三步直接改参数,等于不知道漏点就换更大的桶。

2. 四层观测点与对应指标

层 指标 命令 异常信号
应用 请求排队、协程积压 应用指标、ss -lnt Recv-Q 持续非零
socket 队列 accept / 半连接队列 /proc/net/netstat 的 ListenOverflows 溢出计数增长
协议栈 重传、乱序、超时 netstat -s、ss -ti 重传率偏高
网卡/驱动 ring 缓冲、丢弃 ethtool -S、softnet_stat rx_dropped 增长
ss -s                        # 汇总:TCP 各状态计数、内存占用
ss -lnt                      # Send-Q 是 backlog 上限,Recv-Q 是当前排队
ss -tani                     # 每连接:rtt、cwnd、retrans
netstat -s | grep -Ei 'retrans|overflow|drop|listen'
ethtool -S eth0 | grep -Ei 'drop|miss|fifo|error'
awk '{print $2}' /proc/net/softnet_stat   # dropped 计数(十六进制)

/proc/net/softnet_stat 第二列是每 CPU 上"backlog 满而丢弃"的计数,十六进制。这是"包到了网卡但协议栈来不及处理"的直接证据,比 netstat -s 的总数精确。

3. 两个 backlog 队列:别混为一谈

对比项 半连接队列 accept 队列
存放 收到 SYN、未完成握手 握手完成、等应用 accept
上限参数 tcp_max_syn_backlog somaxconn 与 listen 取小
溢出表现 SYN 被丢,客户端重试 +1s 起 ListenOverflows 涨;tcp_abort_on_overflow=0 时静默丢 ACK
对应统计 SYNs to LISTEN sockets dropped TcpExt.ListenOverflows
常见误配 只调 somaxconn,SYN 洪泛照样丢 只调队列,accept 循环仍慢
sysctl -w net.ipv4.tcp_max_syn_backlog=4096   # 半连接队列
sysctl -w net.core.somaxconn=4096              # accept 队列(listen 的 backlog 不能小于它)
sysctl -w net.ipv4.tcp_abort_on_overflow=0     # 溢出时静默丢 ACK 而非发 RST

somaxconn 在 5.4 起默认 4096 量级,更早是 128;listen(fd, backlog) 会被截断到它。"应用写了 backlog=1024、队列只有 128"是老内核上的经典现象。

4. TIME_WAIT:两个参数的真实作用边界

参数 真正作用 常见误解
tcp_tw_reuse 允许主动发起的新连接复用 TIME_WAIT(需 tcp_timestamps=1) 以为能清服务端堆积——服务端是被动关闭方,无效
tcp_fin_timeout 缩短 FIN_WAIT_2 以为能缩短 TIME_WAIT——它固定 2×MSL(约 60 秒)
tcp_max_tw_buckets 限制总数,超了销毁并打日志 当成容量优化,实为"丢状态换内存"
tcp_tw_recycle 4.12 已移除 老文章仍推荐,NAT 下丢连接
ss -tan state time-wait | wc -l                    # 总量
ss -tan state time-wait '( dport = :80 )' | wc -l  # 服务端侧(被动关闭)
cat /proc/sys/net/ipv4/ip_local_port_range          # 端口区间

判断逻辑:TIME_WAIT 集中在本端作客户端时(dport 是对方端口)才有端口耗尽风险,tcp_tw_reuse 才有意义;集中在服务端侧,是短连接模型的问题,该改连接复用而不是改参数。

5. tcp_rmem / tcp_wmem 三段值

sysctl net.ipv4.tcp_rmem 输出三个数,依次是 min / default / max,例如 4096 131072 6291456。

字段 含义 改动影响
min 自动调优下限 极小内存下保护,一般不动
default 初始窗口 影响首包吞吐,短连接敏感
max 自动调优上限 高 BDP 链路的吞吐天花板
rmem_max setsockopt(SO_RCVBUF) 上限 应用自设缓冲区时被截断

自动调优由 tcp_moderate_rcvbuf(默认开)驱动:按接收速率扩窗,但不超过 max。"带宽高但单流跑不满"时先算 BDP(带宽 × RTT)再看 max;不看 BDP 就把 max 加到几十 MB,只会让少数连接吃掉内存并放大 bufferbloat。

6. 拥塞控制选型

算法 适用 不适用
cubic 低丢包、低 RTT 常规链路,默认 高丢包、长肥管道
bbr 有丢包率、高 RTT 的跨地域链路 与 loss-based 流竞争有公平性争议;需配 fq
sysctl -w net.ipv4.tcp_congestion_control=bbr
sysctl -w net.core.default_qdisc=fq        # bbr 依赖 fq 的 pacing
sysctl net.ipv4.tcp_available_congestion_control

BBR 不是"更快的算法",它换的是判据:从看丢包改成估计带宽与 RTT。所以在本不该丢包的链路上收益有限,在丢包来自中间设备 buffer 不足时收益明显。混跑两类流时,收益与公平性都要实测。

7. 驱动层丢包:netdev_max_backlog 与 rmem_max

计数位置 含义 对应参数
softnet_stat 第 2 列 每 CPU backlog 满丢包 netdev_max_backlog
ethtool -S 的 rx_dropped 网卡 ring 满 网卡 ring(ethtool -G)
rx_fifo_errors FIFO 溢出,驱动跟不上 ring 与中断合并
packet receive errors 上层丢包汇总 需上面三项细分
ethtool -g eth0                              # 当前 ring
ethtool -G eth0 rx 4096 tx 4096              # 调大(需网卡支持,重启失效)
ethtool -S eth0 | grep -E 'rx_dropped|rx_missed'

调大 netdev_max_backlog 能缓解"软中断来不及处理";但若 softnet_stat 第 3 列 time_squeeze 也在涨,说明单核软中断打满,该做 RPS/RSS 分流——加队列只是推迟丢包。

8. 文件描述符:两处上限都要够

层级 参数 影响
进程 ulimit -n / LimitNOFILE 单进程 fd 数
内核 fs.file-max 全系统 fd 总量
内核硬顶 fs.nr_open ulimit -n 不能超过它
ulimit -n                                # 当前 shell
cat /proc/$(pidof app)/limits | grep 'open files'   # 进程实际生效值
sysctl fs.file-max fs.nr_open

# 改 unit(不要只改 /etc/security/limits.conf)
systemctl edit app.service      # [Service] 段写 LimitNOFILE=65535

limits.conf 只对 PAM 登录会话生效,systemd 服务走 unit 里的 LimitNOFILE,容器还要看运行时上限。三处任一是 1024,流量上来就会撞上 "too many open files"。

9. 容器里调参的三个陷阱

陷阱 表现 处理
netns 隔离 容器内改了宿主机统计不变;hostNetwork 下改容器内完全无效 先确认进程在哪个 netns
继承关系不确定 部分 net.* 是内核默认值,部分带宿主机当前值 以容器内 sysctl -a 实测为准
编排层白名单 Pod 里写 sysctls 被拒或不生效 kubelet 只放行 safe 列表,其余需显式声明;重启丢失

Pod 里声明形如 securityContext.sysctls: [{name: net.core.somaxconn, value: "4096"}],只对白名单内的键生效,其余需要显式声明或特权。

最易忽略的一条:容器里调 somaxconn 只影响该 netns 的监听队列。进程若监听在宿主机 netns(hostNetwork: true)或经 sidecar 转发,改容器内毫无作用。

10. 怎么验证改动有效

要素 要求
负载 同一份压测脚本、同一并发、同一时长
环境 同一台机器、同一时段,避开其他业务高峰
变量 一次只改一个参数
观测 吞吐、p99、重传率、溢出计数四项一起看
回滚 改前保存 sysctl -a > before.txt,一条命令可复原
sysctl -a > /tmp/before.txt; iperf3 -c 10.0.0.9 -t 30 -P 4 -J > /tmp/before.json
sysctl -w net.core.somaxconn=4096          # 只改这一个
sysctl -a > /tmp/after.txt; iperf3 -c 10.0.0.9 -t 30 -P 4 -J > /tmp/after.json
diff <(grep somaxconn /tmp/before.txt) <(grep somaxconn /tmp/after.txt)

可比的底线:同一时段、同一负载、只改一个变量。跨时段对比常把"晚高峰过去了"当成"参数生效了"。

11. 常见坑

坑 后果 正确做法
抄别人的清单 与自身流量模型不匹配 从观测点反推参数
一次改十个 无法归因,出问题只能全回滚 一次一个,记录对照
不看丢包就加缓冲 掩盖拥塞,尾延迟恶化 先看重传率与队列
当银弹 锁竞争、慢 SQL 照样在 参数只解决队列与丢包
改了不持久 重启回默认,问题复发 写 /etc/sysctl.d/99-net.conf

12. 排错

现象 原因 处理
新连接偶发超时 1s 起 半连接队列溢出,SYN 被丢 查 SYNs to LISTEN sockets dropped,调 tcp_max_syn_backlog
客户端已连接但服务端没收到请求 accept 队列溢出,ACK 被静默丢 查 ListenOverflows,调 somaxconn 与 listen
高并发端口不够 短连接 + 本端主动关闭 连接复用优先,其次 tcp_tw_reuse
吞吐上不去、重传高 窗口受 max 限制或链路丢包 算 BDP 比对 max,再看拥塞算法
rx_dropped 增长 ring 太小或软中断打满 ethtool -G 调 ring,或 RPS 分流
容器内调参无效 进程不在该 netns 或被白名单忽略 核对 /proc/<pid>/ns/net 与 sysctl -a

13. 边界

问题 该先做什么 为什么不是调参
p99 抖动 应用分段计时、GC/锁分析 参数不改处理时间
连接数上不去 看 accept 循环与线程模型 队列再大挡不住消费端慢
带宽跑不满 算 BDP、看窗口与 qdisc 盲加缓冲制造 bufferbloat
内核态 CPU 高 perf top、软中断分布 参数不是调度器
跨机房慢 拥塞算法 + 应用分块 改不了物理 RTT

14. 自测

  1. 拿到"服务变慢"的报障,先看哪一层?用什么命令区分?
  2. 半连接队列与 accept 队列各由哪个参数控制,溢出时的现象差别在哪?
  3. 为什么 tcp_fin_timeout 改不动 TIME_WAIT 的 60 秒?
  4. tcp_rmem 的 max 与 net.core.rmem_max 分别约束谁?
  5. 容器里调了 somaxconn 却没效果,先确认哪两件事?
  6. 验证一次参数改动的可比性,最低要固定哪三个条件?
S02-J04 · 网络性能与内核参数:从现象到参数的对应关系
https://www.clxhxhhr.top/posts/6036/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。