网络高并发的内核调优 —— 往内核里拧哪些旋钮
overview 说高并发有应用层和内核层两条优化线;应用层(IO 模型、并发模型)在 io-models/epoll/thread-models,本篇讲内核层——一台机器要扛住成千上万连接,内核网络栈的默认参数往往不够,得按领域拧一组旋钮。
本篇按"连接队列 / fd 上限 / 缓冲区 / TIME_WAIT / 端口 / 网卡中断 / conntrack"分块,每块讲什么问题、调哪个参数、怎么权衡、怎么观测。参数"往哪写、临时 vs 永久"见 kernel-tuning,本篇不重复入口机制,只讲该调什么。
一、连接队列(backlog):连接还没被 accept 就排在这
一个 TCP 连接从 SYN 到能被 accept() 取走,要经过两个内核队列:

- 半连接队列满:新来的 SYN 被丢(SYN flood 攻击就是灌满它)。调
net.ipv4.tcp_max_syn_backlog;开tcp_syncookies抗 SYN flood(队列满时用 cookie 不占队列)。 - 全连接队列满:握手都完成了却因应用
accept()太慢挤爆,连接被丢/重置。上限是min(net.core.somaxconn, listen() 第二个参数)——两个都要够大,只调 sysctl 不改代码的listen(fd, backlog)也白搭。 - 观测:
ss -lnt看 listen socket 的Recv-Q(当前全连接队列积压)/Send-Q(队列上限);netstat -s | grep -i "listen\|overflow"看溢出计数(times the listen queue of a socket overflowed)。
一句话:连接进不了应用,先怀疑两个队列——SYN queue 满是握手阶段丢 SYN、accept queue 满是应用 accept 跟不上;
somaxconn和代码里的listen()backlog 要一起调。
二、文件描述符上限:每连接吃一个 fd
高并发第一道硬墙:一个连接 = 一个 fd,万级连接就要万级 fd,而默认 ulimit -n 常常只有 1024。三层上限都要够:
| 层级 | 参数 | 含义 |
|---|---|---|
| 进程级 | ulimit -n(RLIMIT_NOFILE) | 单进程能开多少 fd,默认常 1024,高并发要调到几十万 |
| 系统级(可分配) | fs.nr_open | 单进程 fd 上限的硬顶(ulimit 不能超过它) |
| 系统级(总量) | fs.file-max | 全系统能打开的 fd 总数 |
不调 fd 上限,连接数一到 1024 就 accept() 返回 EMFILE(too many open files)——高并发第一个撞的坑。
三、TCP 收发缓冲区:大带宽/高时延链路要调大
每个 TCP 连接有收(rmem)/发(wmem)缓冲区。缓冲区太小,高带宽×高时延(BDP 大)的链路灌不满、吞吐上不去;太大则每连接占内存多,万级连接下内存爆炸——高并发要在两者间权衡。
net.core.rmem_max/wmem_max:单个 socket 缓冲区的上限。net.ipv4.tcp_rmem/tcp_wmem:最小 默认 最大三元组,内核在这范围内自动调整(autotuning)。- BDP 视角:理想缓冲区 ≈ 带宽 × RTT。10Gbps × 10ms ≈ 12.5MB——默认几百 KB 根本喂不满,长肥管道(LFN)必须调大
tcp_rmem/wmem上限。 - 权衡:高并发短连接(内存敏感)别把默认值调太大;少量大吞吐长连接才需要大缓冲区。
四、TIME_WAIT:大量短连接的头号麻烦
主动关闭方进入 TIME_WAIT 状态并停留 2×MSL(默认 60s)。高并发短连接(如没连接池的 HTTP)会瞬间堆出几万个 TIME_WAIT,占满临时端口,新连接 connect() 失败(端口耗尽)。
| 手段 | 作用 | 注意 |
|---|---|---|
net.ipv4.tcp_tw_reuse=1 | 允许把 TIME_WAIT 的连接复用于新的出站连接(有时间戳保护,安全) | 只影响主动连接侧(客户端/反代到上游) |
net.ipv4.ip_local_port_range 调宽 | 扩大临时端口池(如 1024 65535) | 缓解端口耗尽 |
| 长连接 / 连接池 | 从根上不产生海量短连接 | 真正的根治,优先做这个 |
tcp_tw_recycle | 已废弃、别用 | NAT 环境下会错误丢包,4.12 内核已移除 |
一句话:TIME_WAIT 堆积是"短连接 + 主动关闭"的必然产物;
tcp_tw_reuse+ 调宽端口范围能缓解,但根治是改用长连接/连接池;tcp_tw_recycle是坑,永远别开。
深入阅读:tcp-timewait —— TIME_WAIT 为什么设计 2×MSL、
tcp_tw_reuse的时间戳安全原理、tcp_tw_recycle废弃根因、SO_LINGER 的陷阱、短连接端口耗尽的数学上限与根治方案。
五、网卡与中断层:别让单核扛所有收包
连接数上去后,网卡收包中断可能压垮单个 CPU 核(所有软中断都在一个核上,呼应 interrupts 的软中断/ksoftirqd)。硬件侧高并发靠"把收包分散到多核":
| 技术 | 做什么 | 层次 |
|---|---|---|
| RSS(Receive Side Scaling) | 多队列网卡按流哈希(五元组)把包分到多个硬件队列,每队列绑一个 CPU | 硬件 |
| RPS(Receive Packet Steering) | 单队列网卡的软件版:内核把收到的包按哈希分发到多核处理软中断 | 软件 |
| RFS(Receive Flow Steering) | 在 RPS 基础上让包尽量送到"处理该连接的应用所在的核",提升 cache 命中 | 软件 |
| 中断亲和性 | /proc/irq/<n>/smp_affinity 把网卡各队列的中断绑到不同核,别全挤一个核(呼应 thread-affinity) | 配置 |
| offload(GRO/GSO/TSO) | 把分包/合包卸载给网卡硬件,减少 CPU 处理的包数(ethtool -k 看) | 硬件卸载 |
一句话:高并发下"收包"本身就是负载——用多队列网卡 + RSS/RPS/RFS 把中断和软中断摊到多核、把队列中断绑到不同 CPU,别让一个核成为全网卡的瓶颈。
六、conntrack:防火墙连接跟踪表会满
如果机器开了 netfilter/iptables 的连接跟踪(NAT、状态防火墙都依赖它),每条连接在 nf_conntrack 表里占一项。高并发下表满 → nf_conntrack: table full, dropping packet → 丢包。
- 调大
net.netfilter.nf_conntrack_max、nf_conntrack_buckets;缩短nf_conntrack_tcp_timeout_*。 - 高并发纯转发网关若不需要状态跟踪,直接在
raw表NOTRACK关掉 conntrack,省表省 CPU。
七、其他常用旋钮
TCP_NODELAY(socket 选项,关 Nagle):小包低延迟场景关掉 Nagle 算法,别攒包。延迟敏感服务必设。net.ipv4.tcp_fastopen:TFO 让部分数据在三次握手期间就带上,省一个 RTT。SO_REUSEPORT:多 worker 各自 listen 同端口、内核负载均衡、无惊群——详见 thread-models。
八、总表:问题 → 参数 → 观测
| 症状 | 关键参数 | 观测命令 |
|---|---|---|
| 连接被拒/丢 SYN | tcp_max_syn_backlog、somaxconn + listen backlog、tcp_syncookies | ss -lnt、netstat -s | grep overflow |
| accept EMFILE | ulimit -n、fs.file-max、fs.nr_open | ls /proc/<pid>/fd | wc -l、ulimit -n |
| 吞吐上不去(长肥管道) | tcp_rmem/tcp_wmem、rmem_max/wmem_max | ss -ti(看 cwnd/rtt) |
| 端口耗尽/TIME_WAIT 多 | tcp_tw_reuse、ip_local_port_range、连接池 | ss -s、ss -tan state time-wait | wc -l |
| 单核软中断 100% | RSS/RPS/RFS、中断亲和性 | mpstat -P ALL(%soft)、/proc/interrupts |
| 丢包 + conntrack full | nf_conntrack_max、NOTRACK | dmesg | grep conntrack、conntrack -C |
九、和本仓库其他文档的关系
- 配置入口:kernel-tuning(sysctl / /proc/sys / /sys / 临时 vs 永久)——本篇讲"调什么",那篇讲"往哪写"。
- 观测:ss(连接状态、队列积压)。
- 中断/收包:interrupts(软中断、网卡收包全流程)、thread-affinity(中断/worker 绑核)。
- 应用层配套:thread-models(SO_REUSEPORT)、epoll、overview。
十、一句话总结
内核网络调优围绕"连接进得来、连接扛得住、收包分得开"三件事:队列(SYN/accept backlog + somaxconn/listen)保证连接进得来、fd 上限和 TCP 缓冲区保证连接扛得住且不撑爆内存、TIME_WAIT/端口范围解决短连接端口耗尽(根治是长连接)、RSS/RPS/RFS + 中断亲和性把收包摊到多核别让单核成瓶颈、conntrack 表调大或关掉防丢包。调什么看本篇,往哪写看 kernel-tuning.md,调完用 ss/mpstat/netstat -s 验证。