
1. 为什么默认参数撑不住高并发先从现象说起搞Linux运维的早晚会撞上这种场景线上服务压测到一半监控面板里connect timeout突然飙起来登录服务器一看ss -s里 TIME_WAIT 几万个再往上追后端报Connection refused日志里一堆Cannot assign requested address。第一反应是加机器但很多情况下机器加一台上限就上去了加多了反而不稳定——因为问题根本不是硬件不够而是内核的默认网络参数压根没为高并发设计过。我在社区和一线运维群里待了这些年发现大部分人对网络参数调优的态度就两个极端要么完全不敢动要么照着网上抄一堆“万能配置”改完心里没底。这两种都不好。内核网络参数这东西确实不擅长“无脑套用”但只要理解了每个参数管的是哪个环节你就能自己判断该不该动、动多少。先说一个最简单的现象你启动一个 Nginx 或 Tomcat默认net.core.somaxconn是 128。这表示操作系统里该监听端口的全连接队列最大长度是 128。按道理说应用层 accept 够快就没事可一旦业务逻辑里有一段稍微慢点的处理比如查缓存、做加解密accept 跟不上队列就溢出了。溢出之后的表现是什么连接直接被内核丢弃客户端那边就是偶发的 connection refused 或超时。压测软件里看着像网络抖动实际是内核在帮你丢包。再比如net.ipv4.tcp_max_syn_backlog默认在多数发行版上只有 1024 或 2048老内核可能更低。这是半连接队列长度。正常握手流程中服务端收到 SYN 后进入 SYN_RECV 状态等客户端的 ACK 回来才建立完整连接。如果短时间内收到大量 SYN半连接队列塞满新来的 SYN 包只能丢。高并发场景尤其容易被 SYN 队列打爆的是两种服务一是连接受限的代理网关二是数据库这种握手成本相对较高的服务。这些现象背后都指向同一个逻辑Linux 默认参数是为“通用场景”设计的照顾的是低并发下资源不浪费、不拖慢普通业务。它根本不会替你考虑某个端口每秒要接受几千个新连接、同时维持几十万个长连接、还要让 TIME_WAIT 的回收速度跟上新连接的增长速度。所以高并发调优的本质其实就三件事让新连接进得来、让存量连接的数据走得动、让关闭的连接资源回得快。把这三点理顺了参数怎么调就清晰了。2. 高并发调优的核心参数按阶段拆给你看既然目标是“进得来、走得动、回得快”那就沿着 TCP 连接的完整生命周期把每一阶段相关的参数拆开讲解。这样你调的时候心里就能画出完整的链路图而不是零散地记参数名。2.1 连接建立阶段让新连接进得来这个阶段的核心是两个队列半连接队列SYN Queue和全连接队列Accept Queue。半连接队列的长度主要由net.ipv4.tcp_max_syn_backlog控制。这个值的含义是内核中 SYN_RECV 状态的最大数量。在开启 syncookies 的情况下这个队列的作用会弱化但依然建议调大。常见的参考区间是 4096 到 65535具体取决于你机器的内存和连接突发量。比如入口网关每秒能收到上万个 SYN4096 只是基本起步。全连接队列的长度上限是net.core.somaxconn。你可以把它理解成“应用还没取走内核先帮你放着的已完成连接的缓冲区”。所有基于 listen socket 的服务Nginx、Redis、Java 中间件等都会受这个上限制约。默认 128 对高并发服务来说确实太小我一般建议至少调到 1024压测过的大流量入口可以配置到 4096 甚至更高。很多人改了 somaxconn 之后发现还压不住这往往是没改应用层。Nginx 的listen指令里有backlog参数它跟 somaxconn 是取较小值的关系。Java 里ServerSocket构造函数第二个参数也是 backlogNetty 里则通过option(ChannelOption.SO_BACKLOG, 1024)设置。我见过一个真实案例sysctl 里 somaxconn 已经调到 4096但 Nginx 配置里 backlog 还是默认的 511压测时全连接队列照样溢出dmesg 里能看到Possible SYN flooding on port 80。把两端都调到一致范围这个问题才真正消失。关于net.ipv4.tcp_syncookies这是个值得说两嘴的参数。它把半连接状态编码进 SYN Cookie 里用来防 SYN Flood 攻击。生产环境我通常建议开启值为 1但要注意它只对 SYN 攻击生效对已经建立连接的流量没有保护作用。而且 syncookies 开启后半连接队列溢出时不一定会直接丢包日志里也看不到溢出记录这会让排查“连接为何偶发超时”变得更难。所以压测和事故排查时最好先把它临时关掉让队列溢出的行为透明可见等定位完问题再恢复。2.2 连接传输阶段让数据流得动连接建立之后数据的收发速度跟 Socket 缓冲区大小直接相关。这里有三组参数要分清。第一组是net.core.rmem_max和net.core.wmem_max它们是所有协议栈共享的接收缓冲区和发送缓冲区的绝对上限单位是字节。默认值在多数系统上是 212992也就是 208KB。对于文件上传下载、大响应体接口这类场景208KB 的发送窗口会限制单连接吞吐。举个例子一条网络延迟 50ms 的跨机房连接如果发送缓冲区只有 208KB那么单个 TCP 连接的理想吞吐上限大约是 208KB / 0.05s ≈ 4MB/s。这远远跑不满你们机房那 10Gbps 的网卡。把这两个值调到 16MB 甚至更高之后单连接的吞吐上限就能从 4MB/s 提升到 300MB/s 以上。第二组是net.ipv4.tcp_rmem和net.ipv4.tcp_wmem这是 TCP 协议自己的缓冲区范围每个值都有三个数min、default、max。Min 是内存压力下自动收缩到的最小值default 是新建连接时使用的初始大小max 是 TCP 层能自动增长到的上限。很多调优文档只改 rmem_max 和 wmem_max却不改 tcp_rmem / tcp_wmem结果就是每个连接的实际缓冲还是按 default 值走调半天没效果。我在生产环境常用的一组配置是net.ipv4.tcp_rmem 4096 87380 16777216 net.ipv4.tcp_wmem 4096 65536 16777216default 值 87380约 85KB对大多数网页请求已经够用max 给到 16MB 则能保证大文件传输和长肥管道有足够的突发空间。注意 min 一般不要设太高否则内存紧张时会反过来拖累整个系统的可用性。第三组是net.core.netdev_max_backlog。这个管的是网卡收包后、进入协议栈之前在内核里的排队数量。并发特别高的机器上如果这个值太小中断处理不过来包就直接丢弃。默认 1000 对高吞吐服务就是瓶颈建议调大一般设到 10000 到 65535 区间。判断它是否成为瓶颈的方法很简单cat /proc/net/softnet_stat第二列数据如果持续变大说明网卡包队列有丢弃就需要调大 backlog 或者优化 RSS/多队列。2.3 连接关闭阶段让端口回得快TCP 关闭流程里最让人头痛的就是 TIME_WAIT。主动关闭连接的一方在收到对端的 FIN 后会进入 TIME_WAIT 状态默认等待 2 *net.ipv4.tcp_fin_timeout时长来保证最后一个 ACK 能被对端收到。默认的tcp_fin_timeout是 60 秒也就是说一个连接从主动关闭到资源释放最长要等 120 秒。在高并发短连接场景下这是最恐怖的资源黑洞。先算一笔账。假设你的服务每秒处理 5000 个请求而且都是服务端主动关连接Nginx 返回响应后 close或者 RPC 框架处理完成后 close那么 60 秒内就会累计 30 万条 TIME_WAIT 连接。如果你用的是默认的ip_local_port_range32768 到 60999客户端端口总共不过 28000 多个瞬间端口就发完了。经典报错就是Cannot assign requested address。针对 TIME_WAIT 的调优手段向来是争议区。先说两个安全的一是把net.ipv4.tcp_fin_timeout调小比如 15 到 30 秒这是缩短 TIME_WAIT 的等待时长。注意它只影响主动关闭方做反向代理这类服务可以放心调小但对点对点通信的服务要谨慎因为时间太短可能导致对端没收到最后一个 ACK双方状态错乱。二是开net.ipv4.tcp_tw_reuse值为 1。它的作用是允许内核在新建连接时复用一个仍然处于 TIME_WAIT 状态的旧连接端口前提是双方时间戳递增且旧连接已经“足够老”。这是解决短连接高并发最实用的参数但它只对主动发起连接的一端有效也就是说它解决的是客户端端口不足的问题服务端 TIME_WAIT 堆积还需要配合其他手段。还有一个参数需要专门点名警告net.ipv4.tcp_tw_recycle。这个参数被无数老教程吹捧过但它在 Linux 4.12 之后的内核里已经被删除了。更重要的是哪怕在旧内核上它最大的坑是跟 NAT 环境冲突它靠时间戳来判定同一 IP 的下一个连接是否合法而 NAT 后面多个设备共用一个出口 IP时间戳判断会让一部分连接直接被丢弃表现就是“部分用户间歇性打不开网页”。我在生产环境里见过不止一次有人照抄老博客把 tw_recycle 设为 1第二天线上出现诡异丢包查了一圈发现是这个参数。结论很简单只记tcp_tw_reuse忘了tcp_tw_recycle。最后是net.ipv4.tcp_max_tw_buckets。这个值表示系统允许存在的 TIME_WAIT 连接上限。默认多数发行版是 180000如果超过上限内核会直接回收并打印日志。注意这个参数是“兜底”的正常情况下调大它只是让系统别那么激进地丢 TIME_WAIT 状态如果频繁触发说明前面几个参数没调好。2.4 端口资源让上限破得开端口资源是这个阶段绕不开的话题。高并发的短连接服务只要吞吐量够大就必然会碰到端口耗尽。默认的net.ipv4.ip_local_port_range是 32768 到 60999一共 28232 个可用端口。这个数量对大多数桌面系统和中小流量服务没问题但对每秒数千新建连接的服务来说就是天花板。改它的原则是尽量往下调低起点。我常用的是net.ipv4.ip_local_port_range 1024 65535这样可用端口变成 64512 个几乎翻了一倍。改成 1024 起步需要注意的是端口 1024 以下是特权端口普通用户态进程无法绑定但作为客户端发起连接时不受这个权限限制所以这个配置对大多数服务端程序没有副作用。另外如果你的服务端需要监听多个端口端口池本来就不是瓶颈真正的瓶颈往往是“客户端 IP 客户端端口”这个组合被限死了也就是 NAT 出口或 LB 源端口数量不够。那如果调大ip_local_port_range还是卡在Cannot assign requested address怎么办我实践中通常先走三步排查确认是不是 TIME_WAIT 堆积导致的端口没有及时释放这属于 2.3 节的关闭阶段问题。看连接是否全部卡在 SYN_SENT 状态如果是说明对端根本没回应这种端口看似在池子里实际已失效要查网络而非调参。确定四元组里是否有大量“同源 IP 同目标 IP 同目标端口”的重复组合如果有就要考虑加ipvs或者 LB 层面的分散策略或者让业务侧改用长连接。3. 实战落地一套可直接抄作业的操作流程参数理解是一回事落地又是另一回事。接下来按一个完整的操作顺序走一遍把从“改之前看什么”到“改完确认什么”的每个环节都过清楚。3.1 改之前先给系统做个“体检”调优之前的体检这步特别容易被人跳过但我觉得恰恰是最值钱的环节。不摸底就调很容易出现“参数改了但瓶颈根本没在这个位置”的尴尬。第一步看全局连接状态ss -s输出里你会看到timewait数量、established数量、syn_recv数量等统计。如果 timewait 已经占到很大比重那重点就要放在 2.3 节那一套上如果 established 数量没异常但请求还是慢那就要往 2.2 节的缓冲区方向查。第二步看具体监听端口的队列溢出ss -lnt看Recv-Q和Send-Q这两列。对处于 LISTEN 状态的 socket 来说Recv-Q表示当前全连接队列里还没被应用 accept 的连接数Send-Q表示队列最大长度。当Recv-Q持续接近Send-Q的值就说明队列快满了。这是一个非常直观的预警信号。第三步翻内核日志。直接dmesg -T | grep -i syn flooding如果有输出说明半连接队列一直在丢包这种系统已经处于“看似活着但大量新连接进不来”的状态比连接数高更危险。3.2 参数落地的三件套临时修改、持久化、生效验证体检完了就可以动手。Linux 网络参数的修改分两层临时改和永久改。临时改用sysctl -wsysctl -w net.core.somaxconn4096 sysctl -w net.ipv4.ip_local_port_range1024 65535 sysctl -w net.ipv4.tcp_fin_timeout15注意sysctl -w只对当前运行的内核生效重启后全部丢失一般只用于快速验证参数是否有用。真正要上线长期使用必须写进/etc/sysctl.conf或/etc/sysctl.d/99-tuning.conf# 连接建立 net.core.somaxconn 4096 net.ipv4.tcp_max_syn_backlog 65535 net.ipv4.tcp_syncookies 1 # 传输阶段 net.core.rmem_max 16777216 net.core.wmem_max 16777216 net.ipv4.tcp_rmem 4096 87380 16777216 net.ipv4.tcp_wmem 4096 65536 16777216 net.core.netdev_max_backlog 65535 # 连接关闭与端口 net.ipv4.tcp_fin_timeout 15 net.ipv4.tcp_tw_reuse 1 net.ipv4.ip_local_port_range 1024 65535写完之后执行sysctl -p让配置生效。这里有个容易忽略的坑新版 systemd 发行版里/etc/sysctl.conf和/etc/sysctl.d/目录下多个文件会按字母序合并加载后加载的会覆盖先加载的同名参数。如果你之前装过 Docker 或 Kubernetes它们可能会在/etc/sysctl.d/下放一些优化配置跟你改的冲突。排查方法很简单sysctl -a | grep 参数名看实际生效值而不是只看/etc/sysctl.conf里写了什么。3.3 调完之后怎么确认有效果参数改完不压测等于白调。我建议的验证方式是压测对比。用同一个压测工具wrk、ab、h2load 都行分别在调优前后跑同样的并发和请求量记录三组数据每秒请求数QPS有没有提升响应时间 P99/P95 有没有下降ss -lnt里 Recv-Q 是不是不再持续贴着 Send-Q 了。举个例子。我曾经帮一个网关服务做过一次调优调之前压测 2000 并发时Recv-Q长期顶满QPS 卡在 8000 左右上不去P99 高达 2.3 秒。把somaxconn从 128 调到 4096、Nginx 的backlog也调到 4096 之后同样并发下 QPS 直接翻到 15000P99 掉到 480ms。这不是我技术多厉害而是全连接队列溢出这个瓶颈被拔掉了之后最上层的阻塞消失了。另外再提醒一句调优之后要顺手验证一遍 TIME_WAIT 的数量变化。watch ss -s | grep timewait看两分钟如果 TIME_WAIT 数量能稳定在一个低位说明tcp_tw_reuse和tcp_fin_timeout的组合方向是对的如果还是持续增长到接近tcp_max_tw_buckets说明连接本身压满端口了光调内核还不够得从业务层考虑复用连接或减少主动关闭的频率。4. 调优路上的坑与排查实录这部分我想讲几个真实遇到的坑当做一个“问题速查表”用。每一项都是我自己或身边同事在生产环境里踩过的写出来让你们少走弯路。4.1 TIME_WAIT 堆积我踩过的那个坑有一年我给一个互联网金融类的前置网关做优化那个服务本身是个纯转发层逻辑很简单接收客户端请求往后端转发再把响应原样返回。压测到每秒钟 1500 个请求的时候报Cannot assign requested address看ss -s差不多有 18 万个 TIME_WAIT端口池全被占着。当时第一直觉就是调tcp_tw_reuse开了之后确实缓解了一部分但过了一阵子发现又不稳了。后来排查下来发现一个细节我们的网关是双网卡 keepalived 做成主备模式主备切换时备用机刚起来旧连接全留在 TIME_WAIT而备用机自己也产生了大量新连接。调tcp_tw_reuse在这种切换场景下没用因为它只影响新连接复用但那些旧 socket 还是占着端口。最终的解法是两条路并走一是把tcp_fin_timeout从 60 降到 15让 TIME_WAIT 快速退出二是给网关服务做了连接池后端改用长连接大大减少服务端主动关闭的频率。从那之后 TIME_WAIT 的数量基本稳定在个位数以下。这个案子教会我的就是参数调优能解决的是“系统层面的资源瓶颈”如果瓶颈出在“业务的连接模型”上参数只能治标不能治本。4.2 端口看起来很充足为什么还是 connect timeout另一个案例更有意思。一个内部 RPC 服务客户端调ip_local_port_range已经改成1024 65535端口数量完全够连接数也不高但压测时客户端日志依然大量connect timeout。我看的时候是第一反应先抓包结果发现 SYN 发出去了服务端也确实回了 SYNACK但客户端没有继续发 ACK 完成握手而是直接超时了。查到最后发现是net.ipv4.tcp_tw_recycle在作祟。那台客户端服务器上跑着一个老服务它初始化时把tcp_tw_recycle设成了 1服务端在同一个 NAT 出口后面还有一堆其他机器NAT 设备用时间戳做了校验导致部分 SYNACK 被客户端内核直接丢弃。两台机器之间明明网络是通的就是握手合不上。把tcp_tw_recycle设回 0 之后问题秒消失。这个故事送给所有喜欢“面向搜索引擎调优”的兄弟们别在网上一看到某个参数就顺手改到生产上。tcp_tw_recycle在旧内核的年代可能有用但在 NAT 和高并发客户端场景下就是个炸弹。4.3 buffer 调太大也会出事之前团队有人看到别人博客说“把 rmem_max 调到 64MB”于是直接照搬。结果第二天线上服务内存暴涨GC 频繁最后把整个应用拖垮了。这个例子特别典型你每开一条连接内核都会按当前实际使用情况从tcp_wmem/tcp_rmem里分配内存虽然它不会真的给每条连接都划满 64MB但在高并发下自动增长起来的 buffer 总量依然非常可观。一条连接上如果发送缓冲和接收缓冲都增长到几 MB一万条长连接就是几十 GB 的内存消耗。这跟 JVM 堆外内存增长一样平时看着没事突然业务流量上涨就 OOM。我的建议是大 buffer 给下载、流媒体这类大流量服务没关系但对内部 RPC、网关这类小包高频服务tcp_rmem的 max 给到 4MB 到 8MB 就足够了没必要无脑设 16MB。如果实在拿不准可以先压测看看内存曲线的变化再决定要不要继续往大调。4.4 改了不生效的几类原因这类问题在社区里被反复问但其实翻来覆去就几个原因第一个是应用层 backlog 没跟着调这在 2.1 里提过Nginx、Tomcat、Netty 都有各自的 backlog 参数内核 somaxconn 再大也会被应用层限制住。第二个是/etc/sysctl.conf格式错误比如参数名写错、值带上单位、或者用了中文字符。注意sysctl的参数名大小写敏感net.core.Somaxconn是无效的。写完最好sysctl -p看一下有没有报错提示。第三个是被其他配置覆盖。现代发行版里/etc/sysctl.d/下可能有多个文件如果一个参数在 99-tuning.conf 和 50-default.conf 里都出现后加载的文件会覆盖先加载的。排查时不要只盯一个文件。第四个是某些参数在特定内核版本中被移除了。最典型就是tcp_tw_recycle在 Linux 4.12 之后被彻底删除写到 sysctl.conf 里虽然不报错但啥用没有而且容易让人误以为已经开了。4.5 还有两个“不算网络参数”但一定绕不开的高并发场景下网络参数调得再好如果卡在文件描述符上照样白搭。每个 socket 在应用层都是个 FDulimit -n不能设太低。不过这个不能只靠 shell 里临时ulimit -n 65535解决因为 systemd 管理的服务有独立限制要在 service 文件里加LimitNOFILE1048576。很多 Java 应用启动脚本里明明设了ulimit -n但 systemd 起来后还是被限制在 1024原因就在这里。另外一个容易被忽略的是 TCP 拥塞控制算法。Linux 默认的 cubic 在高带宽高延迟网络下表现一般而 bbr 在部分场景能显著提升吞吐。改法很简单sysctl -w net.ipv4.tcp_congestion_controlbbr要确认内核支持它先sysctl net.ipv4.tcp_available_congestion_control看看列表里有没有 bbr。多数较新的发行版都支持如果你的内核没有就升级一下。这算是个“锦上添花”的调整带宽小、延迟低的局域网上差异不大但公网链路高延迟跨地域传输时效果相当明顯。5. 不同场景的参数推荐组合写到最后我把实践中的参数组合按场景整理成一张表你们可以参考。再次强调这只能作为起点不能当“最终答案”建议每套组合都先做一轮压测验证。场景特点推荐调整重点网关反向代理层大量短连接、服务端主动关闭somaxconn 调大、tcp_fin_timeout 调小、tcp_tw_reuse 开启、ip_local_port_range 放宽内部 RPC 长连接连接数量稳定、数据包较小不需要大 buffer重点调大 netdev_max_backlog、确认 FD 数文件上传下载大流量、单连接吞吐要求高调大 tcp_rmem/tcp_wmem max、调大 rmem_max/wmem_max、可尝试 bbr高并发 IM 推送长连接高维持、心跳频繁关注单机连接上限、谨慎调大 buffer、避免内存被连接数吃光我实际使用中发现网关层最值得无脑改的是somaxconn和tcp_tw_reuse前者直接拔掉新连接进不来的瓶颈后者解决端口复用。内部 RPC 最容易被忽略的反而在 FD 数和net.core.netdev_max_backlog上很多团队只盯着 TCP 参数没想到内核收包队列早就满了。最后再分享一个我自己的小习惯每次线上调完参数我都会把sysctl -a | grep -E somaxconn|fin_timeout|tw_reuse|ip_local_port_range|rmem|wmem的输出随手存到这次变更的运维文档里。这样下次压测或者出问题时对比快照就能立刻知道系统和上次成功状态差在哪。别小看这一步我至少有三次排查问题的关键入口就是这么找到的。