内网服务 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. 自测
- 拿到"服务变慢"的报障,先看哪一层?用什么命令区分?
- 半连接队列与 accept 队列各由哪个参数控制,溢出时的现象差别在哪?
- 为什么
tcp_fin_timeout改不动 TIME_WAIT 的 60 秒? tcp_rmem的 max 与net.core.rmem_max分别约束谁?- 容器里调了
somaxconn却没效果,先确认哪两件事? - 验证一次参数改动的可比性,最低要固定哪三个条件?