做服务端开发和运维这些年,我发现自己有一个习惯:不管接手什么项目,第一件事就是跑到机器上敲几条命令看看系统状态。尤其是高并发场景下,Linux内核那几百个网络参数,从默认值调到适合业务形态的值,往往能把服务的吞吐能力提升一个量级,也能把那些"莫名其妙"的超时、连接失败问题从根源上解决掉。
这篇文章我会把手里的实战笔记整理出来,从排查思路到参数解读,从分场景调优到一次完整的优化案例,尽量把为什么这么调也讲清楚。适合正在被高并发连接折磨的后端开发、运维和SRE同学参考。文章不会太长篇大论讲内核源码,重点放在"你该看哪些参数、怎么调、踩过哪些坑"这些实操内容上。
1. 先搞清楚瓶颈在哪:排查思路与关键指标
1.1 高并发场景下网络瓶颈的常见表现
很多人一上来就开始改参数,改完发现没啥效果,甚至更糟。我在实际项目中踩过这种坑,所以强烈建议先回答一个问题:你的服务到底是卡在哪一层?
有个比较典型的案例。之前帮一个做在线教育直播答疑的团队排查问题,他们的服务端在高峰期会间歇性出现客户端连不上、连接超时的现象。团队第一反应是加机器,但加了之后问题依旧。后来我上机器看了几眼,发现CPU、内存、带宽都挺空闲,反而是 /var/log/messages 里刷满了 TCP: time wait bucket table overflow 和 listen queue overflow。这就非常明确了——问题出在内核网络协议栈,不是业务代码,也不是机器资源不够。
类似的网络瓶颈通常有几种典型表现:
- 连接建立缓慢:三次握手迟迟完成不了,客户端频繁超时重试。
- 连接被拒:客户端报
connection refused或者connection reset,服务端明明还活着。 - 连接数上不去:ulimit和内核参数限制,导致并发连接数摸到一个天花板就再也涨不上去。
- TIME_WAIT 堆积:大量连接处于 TIME_WAIT 状态,端口被占满,新连接无法建立。
- 总体响应变慢但系统资源不高:多半是队列满了,请求在内核层排队等待处理。
这些问题靠"拍脑袋"不好定位,需要有一套固定的排查流程和观测手段。
1.2 必看的几个观测指标和命令
我习惯把排查分成三层来看:先看网络整体情况,再看连接状态分布,最后看是否有丢包和队列溢出。
网络整体情况用 sar -n DEV 1 看 PPS(每秒包数)和带宽使用率,用 ss -s 看当前socket统计。如果PPS已经接近网卡上限,那问题可能是小包冲击,要考虑网卡多队列和RPS。
连接状态分布是我的重点关注项。执行 ss -ant | awk '{print $1}' | sort | uniq -c 可以直接统计各种状态的连接数。重点关注三个值:TIME_WAIT、ESTABLISHED、SYN_RECV。TIME_WAIT 太多说明短连接频率过高;SYN_RECV 堆积说明半连接队列满了,可能有 SYN 攻击或者 backlog 太小。
丢包和队列溢出方面,netstat -s 里藏着很多宝贝。比如 time wait buckets exceeded、listen queue overflow、SYNs to LISTEN sockets dropped,这些计数只要非零,基本就能定位到具体是哪个参数不够用。
还有一个容易被忽略的观测点是连接跟踪表。执行 cat /proc/sys/net/netfilter/nf_conntrack_count 看当前连接跟踪条目数,再对比 nf_conntrack_max。如果这两个值非常接近,说明 conntrack 表快满了,会直接丢新连接。
注意:如果你用了 iptables 或者云平台的 NAT 网关,conntrack 是绕不开的。这块经常是"隐形杀手"。
我自己还习惯装一个 btop 作为辅助监控工具,它可以快速看 CPU 和网络流量趋势,但真正精确到协议栈的指标,还是得靠上面这些命令行工具。定位瓶颈就像医生问诊,望闻问切之后才能开药方。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 网络参数全景拆解:从Socket到TCP
2.1 TCP连接生命周期相关的参数
TCP连接的建立、维持、断开,是整个网络参数调优的核心。先说建立阶段。
net.ipv4.tcp_max_syn_backlog 控制半连接队列长度。三次握手的第一步,客户端发 SYN,内核会把这个连接放到半连接队列里等待服务器回复 SYN+ACK。如果这个队列满了,新来的 SYN 会被直接丢弃。默认值通常是 1024 或者 2048,在高并发场景下必须调大,我一般直接设成 65536。但要注意,这个参数配合 net.core.somaxconn 一起看,因为全连接队列的长度由 min(backlog, somaxconn) 决定,这里的 backlog 是应用调用 listen() 时传入的值。
建立连接后,保活参数也很关键。很多长连接场景(比如IM、消息推送),客户端可能几十分钟不发数据,TCP 的 keepalive 机制会发挥作用。三个参数需要关注:
net.ipv4.tcp_keepalive_time:空闲多久开始探测,默认7200秒太长,我会根据业务改成600秒。net.ipv4.tcp_keepalive_intvl:探测包发送间隔,默认75秒,可以改短到15~30秒。net.ipv4.tcp_keepalive_probes:连续几次探测无响应就断开连接,默认9次,一般保持9或者调成3。
断开连接阶段问题最多。主动关闭连接的一方会进入 TIME_WAIT 状态,默认要等待 2 * MSL(通常60秒)才能完全释放。高并发短连接场景下,TIME_WAIT 连接会迅速堆积,占用内存和端口。这里有几个参数可以调整:
net.ipv4.tcp_fin_timeout:FIN_WAIT_2 状态的超时时间,默认60秒,这个影响的是被动关闭方。调小到15秒左右可以更快释放处于 FIN_WAIT_2 的连接。net.ipv4.tcp_max_tw_buckets:TIME_WAIT 连接的最大数量,默认约18万。超过后新连接会被丢弃,日志里会刷time wait bucket table overflow。在允许复用的情况下可以适当调大,但单纯调大不解决根本问题。
关于 tcp_tw_reuse 和 tcp_tw_recycle这两个参数,我要特别说两句。
tcp_tw_reuse 的作用是允许客户端在发起新连接时,复用还在 TIME_WAIT 状态的端口。它只对主动发起连接的一方(即作为客户端的场景)有效,比如 Nginx 作为代理去连接后端。我自己的经验是 tcp_tw_reuse = 1 在客户端场景下是安全的,能有效缓解端口耗尽问题。
tcp_tw_recycle 这个参数在新版内核(4.12+)里已经被移除了,但旧版本还有很多人开着。它有个著名的坑:开启后会校验连接的时间戳,如果请求经过 NAT 网关,不同设备的时间戳可能不一致,导致部分连接被错误丢弃。我见过不止一个线上事故是因为有人盲目照搬老教程开了这个参数。一句话总结:别开 tcp_tw_recycle,用 tcp_tw_reuse + 调整 tcp_max_tw_buckets + 减少短连接 的组合来治本。
2.2 Socket缓冲区与内存相关的参数
TCP 的收发缓冲区大小决定了单个连接能暂存多少数据。太小会导致吞吐量上不去,太大则可能造成内存浪费。这一组的核心参数是:
net.core.rmem_max和net.core.wmem_max:分别表示接收缓冲区和发送缓冲区的最大值,默认一般是 212992(约208KB)。如果应用需要传输大块数据,这个值不够。net.ipv4.tcp_rmem和net.ipv4.tcp_wmem:这是三个值的数组,分别代表最小值、默认值和最大值。内核会根据网络状况在最小值与最大值之间动态调整。
我常用的一组配置是:
code复制net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 16384 16777216
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
16MB 的最大缓冲区对大部分业务足够用了。需要说明的是,缓冲区调大之后,单连接占用的内存也会上升。如果一个连接占 16MB 接收缓冲 + 16MB 发送缓冲,1万个连接就是 32GB,这显然不现实。所以实际使用中内核是按需动态分配的,不要被"最大"两个字吓到,但也要知道这个上限存在的风险。
与内存相关还有两个参数容易被忽视:net.ipv4.tcp_max_orphans 和 net.ipv4.tcp_abort_on_overflow。
tcp_max_orphans 控制的是孤儿连接(即没有被任何应用持有的socket)最大数量。如果系统频繁出现大量孤儿连接,可能是应用处理不过来或者连接被异常关闭。默认值通常够用,但如果日志里出现 TCP: too many of orphaned sockets,就需要调大并排查应用问题。
tcp_abort_on_overflow 这个参数建议设成 0。有些教程会说设成 1 可以快速拒绝连接,但实际上它会导致 accept 队列满时直接发送 RST,客户端会收到连接被重置的错误。设成 0 时,内核会丢弃 SYN,让客户端重试,整个过程虽然慢一点但对客户端更友好。
2.3 连接跟踪与端口复用相关参数
NAT和iptables场景下的连接跟踪表,经常是高并发环境的瓶颈所在。
默认的 nf_conntrack_max 通常是 65536(也有的是 262144,跟内存有关),对于高并发场景经常不够。常见做法是根据内存大小来定,经验公式大概是 1GB 内存可以支撑 3万左右的 conntrack 条目。比如 16GB 内存的机器,可以设成 nf_conntrack_max = 524288。同时要把 nf_conntrack_buckets 调大,它是 hash 表的桶数量,一般建议和 max 保持 1:4 的比例关系。
端口资源这块,net.ipv4.ip_local_port_range 决定本机作为客户端时能使用的源端口范围。默认是 32768 60999,一共也就 28232 个端口。如果这台机器作为反向代理或网关,需要发起大量到后端的连接,端口很快就会耗尽,报错信息是 Cannot assign requested address。
我一般会把它改大,比如 1024 65535,这样可用的源端口有 6万多个。但注意,这只是端口范围的"天花板",实际可用的连接数还受 tcp_max_tw_buckets、文件句柄数、conntrack 上限的多重约束。
net.core.netdev_max_backlog 也值得一提,它控制网卡接收队列的长度。高 PPS 场景下,如果内核处理不过来,这个队列会堆积甚至丢包。默认1000太小,我会调成 65536,和 tcp_max_syn_backlog 保持一致。
调整这些参数的思路不是"越大越好",而是找到当前业务模式下的瓶颈点,对症下药。盲目加大参数值,可能在极端情况下把内存耗尽,触发 OOM。
3. 分场景调优:不要一套配置走天下
3.1 高并发短连接场景:Nginx网关层
短连接场景最典型的代表是 Nginx 作为反向代理对外提供 HTTP 服务。客户端频繁建立新连接,请求完就断开,TIME_WAIT 会主要集中在 Nginx 这一侧。
这种场景下,我会优先做三件事:调整 TIME_WAIT 相关的参数、扩大端口范围、加大 accept 队列。
参考配置如下:
code复制# /etc/sysctl.d/99-network-tuning.conf
# 提高文件句柄上限和连接队列
fs.file-max = 2097152
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 65536
net.ipv4.tcp_max_syn_backlog = 65536
# 端口和 TIME_WAIT
net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_max_tw_buckets = 2000000
# 防 SYN 攻击,但保留客户端兼容性
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_syn_retries = 2
net.ipv4.tcp_synack_retries = 2
这里有个容易忽略的细节:tcp_syn_retries = 2 和 tcp_synack_retries = 2 是缩短重试次数。对于短连接来说,如果网络确实不通,客户端侧重试3次共约3秒就放弃了,比默认的5次(约3分钟)体验好得多。但如果是跨地域、网络质量差的长连接场景,过少的重试反而容易误判。所以这个值要结合业务容忍度来设。
上配置之前,记得同步修改 /etc/security/limits.conf 里的 nofile 限制。我见过很多团队改了内核参数但忘了 ulimit,连接数照样上不去。建议把 soft 和 hard 都设成 1048576。
3.2 长连接场景:IM/推送服务
长连接场景是另一个极端。大量客户端连接保持 ESTABLISHED 状态,占总连接数的比例非常高(比如 70% 以上),但单个连接的数据量不大、频率不高。这类服务的瓶颈通常在文件句柄、内存和连接跟踪。
核心思路是"能省则省":连接尽量保持,不频繁断开;同时让内核更快地释放异常连接,避免僵尸连接占用资源。
配置参考:
code复制# 长连接场景
net.ipv4.tcp_keepalive_time = 300
net.ipv4.tcp_keepalive_intvl = 15
net.ipv4.tcp_keepalive_probes = 3
net.ipv4.tcp_orphan_retries = 1
net.ipv4.tcp_max_orphans = 65536
net.ipv4.tcp_fin_timeout = 10
# 连接跟踪表适当调大(如果使用了 NAT/iptables)
net.netfilter.nf_conntrack_max = 1048576
net.netfilter.nf_conntrack_buckets = 262144
tcp_keepalive_time = 300 意味着连接空闲 5 分钟就开始探测。对 IM 这种实时性要求高的场景,客户端和服务端通常有自己的心跳机制(比如 WebSocket 的 ping/pong),TCP keepalive 只是兜底。我见过有的团队把 keepalive_time 设在 60 秒以下,这其实没必要,反而会增加探测包的数量。300秒是个不错的折中。
长连接场景的另一个大坑是内存。假设 50 万个长连接,每个连接的内核 socket 缓冲默认也要几十 KB,再加上应用层的内存,一台机器承受50万连接确实需要好好规划内存。我建议这种场景下把 tcp_rmem 和 tcp_wmem 的默认值适当调小,比如:
code复制net.ipv4.tcp_rmem = 4096 16384 4194304
net.ipv4.tcp_wmem = 4096 16384 4194304
这样每个连接初始占用的内存更少,只有真正传输大数据时才动态扩张。
3.3 消息队列与中间件场景
消息队列(Kafka、RocketMQ)和数据库这一类场景,特点是连接数不算特别多(几千到几万),但单连接的吞吐量要求很高,经常要传输大块数据。这时候重点是加大缓冲区,让大包能更快地在内核层搬运。
配置参考:
code复制# 大吞吐场景
net.core.rmem_max = 67108864
net.core.wmem_max = 67108864
net.ipv4.tcp_rmem = 4096 87380 67108864
net.ipv4.tcp_wmem = 4096 16384 67108864
net.ipv4.tcp_slow_start_after_idle = 0
tcp_slow_start_after_idle = 0 这个参数值得单独说说。TCP 有一个拥塞控制机制:连接空闲一段时间后,再次发送数据时会重新进入慢启动过程,导致初始发送速率很低。对于消息队列这种"平时空闲、突然有大消息要发"的场景,这个行为会严重影响突发传输的延迟。关闭它之后,连接会记住之前的拥塞窗口状态,突发传输更快。
还有一个容易被忽略的点:CPU 中断绑定。高吞吐场景下,网卡中断可能都集中在一个 CPU 核上,导致那个核的软中断占用率接近 100%,其他核闲着。这种情况调内核网络参数解决不了,需要用 smp_affinity 把网卡多队列的中断分散到多个 CPU 核上。这块建议用 ethtool -L 和 irqbalance 配合处理,属于网络调优里"软件之外"的重要一环。
4. 实战案例:一次完整调优过程
4.1 从现象到根因的定位步骤
去年帮一个做在线题库系统的团队做优化,他们的服务是典型的 Nginx + Tomcat 架构,高峰期每秒新增连接数能达到 3万以上。用户反馈晚上 8 点到 9 点的刷题高峰期,App 时不时报"网络异常,请重试"。
我上机器之后按顺序做了这几步排查:
第一步,看全局。sar -n DEV 1 显示 eth0 的 PPS 在 8万左右,带宽只有 300Mbps,远没到网卡上限;CPU 整体 40% 左右。
第二步,看连接状态。ss -ant | awk '{print $1}' | sort | uniq -c 的输出很惊人:TIME_WAIT 有 20多万,ESTABLISHED 只有 1万多,SYN_RECV 有几千。这个比例严重失衡,短连接频率远超服务处理能力。
第三步,看丢包和溢出计数。netstat -s | grep -i "listen\|overflow\|bucket" 有大量 listen queue overflow 和 time wait bucket table overflow。
第四步,确认瓶颈。问题集中在两点:一是 TIME_WAIT 太多导致源端口频繁复用失败,二是 accept 队列太小导致部分连接被丢弃。
这个过程我只用了不到 10 分钟,因为关键指标指向非常明确。很多时候定位难不是因为你命令不熟,而是没有形成"先看全局、再看状态、最后看计数"的排查习惯。
4.2 参数变更与效果验证
定位之后,我在预发环境做了一轮参数变更:
code复制# 变更前默认值
net.ipv4.tcp_max_syn_backlog = 2048
net.core.somaxconn = 128
net.ipv4.ip_local_port_range = 32768 60999
net.ipv4.tcp_fin_timeout = 60
# 变更后
net.ipv4.tcp_max_syn_backlog = 65536
net.core.somaxconn = 65535
net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_max_tw_buckets = 2000000
同时把 /etc/security/limits.conf 里 Tomcat 用户的 nofile 调到了 1048576,/etc/sysctl.conf 里 fs.file-max 调到了 2097152。
这里有一个关键操作容易被忽略:改完内核参数后要执行 sysctl -p 重新加载,但如果某些参数依赖模块加载,比如 nf_conntrack 相关参数,需要先 modprobe nf_conntrack 才能生效。另外,net.core.somaxconn 调大后,如果应用本身在 listen() 时传入的 backlog 很小(比如 Java 的默认值 50),那么实际生效的还是小值。所以应用层代码里的 backlog 参数也要同步调大。Nginx 的 listen 80 backlog=65535 就是显式指定的。
验证方面,我们先用 wrk 做了压测对比。同样的 100 并发持续压测 60 秒,变更前平均响应时间 85ms、错误率 1.2%,变更后平均响应时间 43ms、错误率 0.02%。更重要的是 ss -s 里的 TIME_WAIT 数量从峰值 20多万降到了 3万左右,netstat -s 里 time wait bucket table overflow 的计数不再增长。
这个结果其实在意料之中。TIME_WAIT 数量下降的核心功臣是 tcp_tw_reuse 和端口范围扩大,而响应时间下降的功臣是 accept 队列变大,不再有大量重连和排队。
4.3 调优后的持续监控与回归
参数改完之后不是一劳永逸的。我在调优后的两周内持续观察了几个指标:SYN_RECV 的堆积情况、TIME_WAIT 的峰值、以及 listen queue overflow 是否归零。另外也关注了一个容易反弹的指标——连接峰值数是否触到了新的天花板。
比如我们发现,把端口范围改到 1024 65535 之后,单个 Nginx 实例最多能发起约 6.4万个到后端的连接。如果业务继续增长,后端连接数超过这个值,端口还是会耗尽。到时候要么增加 Nginx 实例,要么让后端也支持 keepalive 长连接,减少重复建连的频率。这些属于容量规划层面的问题,单靠内核参数已经解决不了。
持续监控期间我还养成了一个习惯:每次压测或者线上变更后,都会 netstat -s 里挑几个关键计数器做前后对比。内核协议栈的计数器是"从开机累加"的,所以不能只看绝对值,要看增量。这个细节很多教程不会写,但排查问题特别管用。
5. 常见问题与排查技巧实录
5.1 排查技巧速查表
这部分整理了我在多个项目里遇到的高频问题和对应的排查路径。与其遇到问题再查,不如先收藏起来。
| 现象 | 优先排查点 | 常用命令 |
|---|---|---|
| 连接建立慢/超时 | 半连接队列溢出(SYN丢包) | netstat -s | grep -i "drop|overflow" |
| 大量 TIME_WAIT | 短连接频率过高、tw_buckets满 | ss -s、netstat -s | grep -i "time wait" |
| 连接被拒绝(reset) | accept队列溢出、应用backlog过小 | netstat -s | grep -i "listen" |
| 端口耗尽 | 源端口范围不足、孤儿连接过多 | ss -ant | awk '{print $4}' | cut -d: -f2 | sort | uniq -c | wc -l |
| CPU软中断高 | 网卡多队列没打开、RPS/RSS未配置 | top 看 si 占比、ethtool -L |
| 偶发大量连接被重置 | conntrack表满了、NAT丢包 | cat /proc/sys/net/netfilter/nf_conntrack_count |
| 延迟抖动明显 | 缓冲区过小、拥塞控制不合适 | ss -tinp、ping 延时段 |
5.2 几个高频问题与解决思路
问题一:改了 somaxconn 但连接还是上不去。
这是最经典的问题之一。假设你在 /etc/sysctl.conf 里设了 net.core.somaxconn = 65535,应用层却还保留着默认的 listen backlog(比如很多语言默认 128 甚至 50)。内核实际生效的全连接队列长度是 min(backlog, somaxconn),所以真正的瓶颈可能在工作线程太少或者 accept 速度太慢,而不是系统层配置。
解决思路:先看 ss -ltn 输出里的 Send-Q 列。它显示的是当前 socket 的 backlog 值,如果远小于你配置的 somaxconn,说明应用层传的 backlog 太小。Java 里 ServerSocket 的构造函数或者 Netty 的 option(ChannelOption.SO_BACKLOG, 1024) 要显式设置;Nginx 需要 listen 80 backlog=65535;Node.js 的 server.listen(port, backlog) 也要显式传参。
问题二:TIME_WAIT 多到包不住,到底该不该开 tcp_tw_reuse?
tcp_tw_reuse 只对出站连接生效。如果你遇到的是大量入站连接导致的 TIME_WAIT,开 tcp_tw_reuse 是无效的。这种场景真正要做的是降低连接创建频率或者缩短 TIME_WAIT 的存在时间。
在实际操作中,我会分两步走:第一步,调整 tcp_fin_timeout 到 15,这个参数影响的是 FIN_WAIT_2,间接减少后续的 TIME_WAIT 数量;第二步,把业务层的 HTTP keep-alive 打开,让客户端复用连接,从源头减少短连接。如果这两个都做了还有大量 TIME_WAIT,说明连接频率真的太高,需要考虑架构层面调整。
问题三:高并发下出现随机性连接失败,但计数器又没明显溢出。
这种情况最让人头疼。我的建议是抓包看。tcpdump -i eth0 -n 'tcp[tcpflags] & (tcp-syn|tcp-rst) != 0' 抓 SYN 和 RST 包,看看是否有异常的 RST 回应。我曾经在一个项目里发现,云平台的负载均衡器会主动发送 RST 来断开空闲连接,客户端某些版本把 RST 当作错误处理,导致随机性失败。这种问题调内核参数没用,得调整 LB 的空闲超时时间或者客户端对 RST 的处理逻辑。
问题四:调整内核参数后,某些机器没生效。
多数是因为没有执行 sysctl -p 或者配置文件被覆盖了。比如云厂商的镜像里可能有自己的优化脚本,每次重启都会覆盖 /etc/sysctl.conf。我的习惯是统一放在 /etc/sysctl.d/99-network-tuning.conf,并且写好注释。这样即使系统默认配置变化,我们的自定义配置也能在更晚的阶段被加载,优先级更高。同时配合 sysctl --system 重新加载所有配置。
5.3 这些坑我替你踩过了
先说说 tcp_tw_recycle 这个参数。旧内核版本下,开启它确实能快速回收 TIME_WAIT 连接,但代价是只要网络里涉及 NAT(云环境几乎都有),就可能出现部分用户连接被无故重置的情况。我在一个电商项目里遇到过:开启 tcp_tw_recycle 后,不少用户反馈下单时会偶发失败,关了之后立竿见影。后来排查发现是多个客户端共用同一个公网 IP,时间戳各不相同,触发了内核的"时间戳反推"机制。现在新版内核干脆移除了这个参数,但如果你还在用 CentOS 7 等老系统,务必要检查它是不是被某些优化脚本打开了。
再说说文件句柄。很多人调优只盯着 sysctl,忘了 ulimit -n 这个限制。Linux 里每个 TCP 连接至少占用一个文件描述符,如果进程的 nofile 限制是 1024,即使内核参数调到天上去,并发连接数也不可能超过 1024。而且要注意区分 soft limit 和 hard limit,很多程序只继承了 soft limit,需要在启动脚本里显式 ulimit -n 1048576。
最后提一下内存计算。每多一个 TCP 连接,内核就需要分配一定的内存给 socket 缓冲区和协议控制块。在极高的并发连接数下(比如 50 万+),这部分内存开销相当可观。如果不提前规划好内存,调大了连接数上限,反而可能因为内存不足触发 OOM,把整个服务拖垮。这也是为什么我一直强调:调优不是无脑加参数,而是基于业务形态做合理的容量规划。
6. 一页纸总结:我的调优习惯和落地清单
网络参数调优这件事,说简单也简单,说复杂也复杂。我见过有人把网上的参数抄一遍就敢上生产,也见过有人花了一整天抓包分析最后只是改了 somaxconn。两者都不算高效。整理一下我自己的调优习惯,算是给这篇文章画个句号。
第一,永远先定位瓶颈再改参数。连接数上不去可能是文件句柄、backlog、端口范围、conntrack 或者应用层 accept 速度中的任何一个环节导致的。不看指标就改参数,跟盲人摸象没有区别。
第二,参数按场景分组。我会把 sysctl 配置按业务分文件存放,比如网关用一份、IM长连接用一份、大数据传输用一份。这样不同业务可以独立调优,不会互相干扰。
第三,调优之后要有验证手段。要么用压测工具做前后对比,要么在线上灰度观察关键计数器。没有验证的调优,无法确认到底改对了没有。
第四,做好配置注释和变更记录。任何一行被改动的参数,都要留下"为什么改"的注释。半年后再看代码,你会感谢自己当初写的注释。
第五,内核参数不是银弹。如果业务逻辑本身有问题,比如频繁创建短连接、没有连接池、不在应用层做心跳校验,调什么参数都只是延缓症状。架构层解决连接复用和生命周期管理,内核层负责让系统在既有的连接模式下跑得更顺畅,两者缺一不可。
我在实际项目中还发现一个不容易被注意到的细节:很多云的默认镜像里已经内置了一套"优化参数",但不同镜像的默认值差异很大。换一台机器、换一个镜像重新部署时,最好重新核对一遍关键参数,而不是假设"上次调过这次就不用管了"。有一次我排查一个新集群的问题,发现就是云厂商更新了默认镜像,把 tcp_max_syn_backlog 从 65536 改回 2048 导致的。
如果你是从零开始优化一台机器,我建议按照这个顺序来:先确认硬件资源和网卡队列配置,再修改文件句柄和 conntrack 上限,然后调整 TCP 连接生命周期相关参数,最后才考虑缓冲区大小。前几步是"打开天花板",最后一步是"让数据流动更顺畅"。顺序搞反了,很容易出现这边调大了缓冲区、那边连接数又被卡死的情况。
这篇文章覆盖的内容,大部分来自我处理过的实际案例。希望你在自己的环境里动手调试时,能少踩几个我已经踩过的坑。如果照着文章思路成功解决了问题,或者遇到了文中没提到的新情况,欢迎回来交流。
