首页 / 资讯中心 / 文章详情

Linux TCP内核参数调优实战:从TIME_WAIT到拥塞控制

Linux TCP内核参数调优实战:从TIME_WAIT到拥塞控制 ★ FEATURED ARTICLE
1. 为什么TCP内核参数值得花时间调干运维这行的大概都经历过这种场景一台服务器CPU和内存看着都挺闲磁盘IO也不高但业务方就是反馈接口慢连接超时偶尔丢包。排查半天应用代码没毛病最后定位到内核网络参数上——这种问题最磨人因为它不像内存泄漏那样有明显的曲线也不像磁盘满了一样直接报错它就是偶尔抽风让你抓不住把柄。Linux的TCP协议栈默认参数是给通用场景设计的换句话说它要兼顾从嵌入式设备到超级计算机的各种情况所以很多值都偏保守。但你的业务场景是特定的可能是高并发的短连接API服务可能是长连接推送网关也可能是内网之间的大文件传输。这些场景对TCP参数的需求完全不同用默认值跑轻则性能打折重则连接堆积、端口耗尽、TIME_WAIT爆表。这篇总结面向的是有一定Linux基础、需要实际动手调优的运维和后端同学。我会把每个参数背后的逻辑讲清楚告诉你为什么这么调、调完有什么效果、什么情况下不能乱调。不是把sysctl.conf往你脸上一贴就完事而是让你理解每个数字的来龙去脉这样遇到新场景你也能自己判断。需要提前说明的是内核参数调优不是银弹。如果你的瓶颈在应用层的锁竞争、数据库慢查询、或者带宽本身就不够那调TCP参数基本没用。调优的第一步永远是定位真正的瓶颈确认是网络协议栈层面的问题之后再动手。2. 动手之前先搞清楚你的场景和瓶颈在哪2.1 三类典型场景的参数取向不同业务对TCP的需求差异极大我把它分成三类你可以对号入座第一类高并发短连接。典型代表是HTTP API网关、反向代理层。特点是连接建立后很快传输完数据就关闭每秒可能有几万甚至几十万次连接建立和断开。这类场景最怕的是TIME_WAIT堆积、端口耗尽、SYN队列溢出。调优重点是加快TIME_WAIT回收、扩大端口范围、增大SYN队列。第二类长连接高吞吐。典型代表是消息推送网关、数据库连接池、gRPC服务。特点是连接建立后长期保持数据持续传输。这类场景最怕的是缓冲区不够导致吞吐上不去、KeepAlive探测不及时导致死连接占资源。调优重点是增大读写缓冲区、合理配置KeepAlive。第三类内网大流量传输。典型代表是跨机房数据同步、分布式存储节点间通信。特点是带宽大、延迟低、数据量大。这类场景最怕的是拥塞控制算法不适合、窗口缩放没开导致大带宽下吞吐受限。调优重点是换拥塞控制算法、开启窗口缩放、调整初始拥塞窗口。先想清楚你的业务属于哪一类或者哪几类的混合再往下看具体参数。盲目照搬网上的万能配置很可能适得其反。2.2 调优前必须做的基线测量我见过太多人上来就改参数改完也不知道有没有效果。正确的做法是先测量建立基线改完再测对比才有意义。几个必须看的指标连接状态分布ss -s看总的连接数ss -ant | awk {print $1} | sort | uniq -c看各状态连接数量。重点关注TIME_WAIT、ESTABLISHED、SYN-RECV的数量。重传率netstat -s | grep -i retrans看重传次数配合总发送包数算重传率。正常应该在0.01%以下超过1%说明网络质量有问题。丢包情况netstat -s | grep -i drop和grep -i overflow看是否有队列溢出导致的丢包。端口使用ss -ant | wc -l对比net.ipv4.ip_local_port_range的范围估算端口耗尽风险。缓冲区溢出netstat -s | grep -i buffer或grep -i pruned看是否有因为缓冲区不足被裁剪的包。提示测量要在业务高峰期做低峰期的数据没有参考价值。最好连续采样一段时间用脚本每隔几秒记录一次看趋势而不是看瞬时值。2.3 改参数的正确姿势改内核参数有两种方式临时生效和永久生效。临时生效用sysctl -w命令比如sysctl -w net.ipv4.tcp_tw_reuse1重启后失效。这种方式适合测试验证确认没问题再写进配置文件。永久生效要写进/etc/sysctl.conf或者/etc/sysctl.d/下的独立配置文件然后执行sysctl -p加载。我推荐用/etc/sysctl.d/下的独立文件比如/etc/sysctl.d/99-tcp-tuning.conf这样配置清晰也方便回滚。改之前一定要备份原配置改完一定要验证。验证方法是sysctl -a | grep 参数名看实际生效的值而不是只看配置文件里写了什么。有些参数之间有依赖关系单独改一个可能不生效。3. 核心参数逐个拆解从TIME_WAIT到拥塞控制3.1 TIME_WAIT相关短连接场景的重灾区TIME_WAIT是主动关闭连接的一方在发送最后一个ACK后进入的状态持续2MSL通常60秒。它的存在有两个目的一是确保最后一个ACK能到达对端如果对端没收到会重发FIN此时还能响应二是让本次连接的残余数据包在网络中消散避免影响后续使用相同四元组的新连接。问题在于高并发短连接场景下服务器作为主动关闭方会积累大量TIME_WAIT占用端口资源。当端口耗尽时新连接就无法建立了。net.ipv4.tcp_tw_reuse这个参数控制是否允许将TIME_WAIT状态的socket重新用于新的TCP连接。注意它只对主动发起连接的一方有效也就是作为客户端时才有用。设为1开启。它的原理是当一个新的连接请求到来时如果四元组源IP、源端口、目的IP、目的端口与某个TIME_WAIT状态的连接相同且TIME_WAIT已经持续超过1秒就复用这个socket。这里有个前提需要开启tcp_timestamps默认开启因为时间戳能保证复用的连接不会收到旧连接的延迟数据包。sysctl -w net.ipv4.tcp_tw_reuse1注意这个参数在NAT环境下要谨慎。如果多台内网机器通过NAT访问同一目标复用TIME_WAIT可能导致四元组冲突。不过现代内核的时间戳机制基本能避免这个问题实际影响很小。net.ipv4.tcp_tw_recycle这个参数在较老的内核里用来快速回收TIME_WAIT但它在NAT环境下有严重问题——会导致NAT后面的客户端连接被丢弃。从Linux 4.12开始这个参数已经被移除。如果你的内核版本较新根本找不到这个参数如果还在用老内核我强烈建议不要开启它用tcp_tw_reuse就够了。net.ipv4.tcp_max_tw_buckets这个参数限制系统中TIME_WAIT socket的最大数量。超过这个数量后新的TIME_WAIT会被立即销毁并打印警告。默认值通常是180000对于高并发短连接场景可能不够。sysctl -w net.ipv4.tcp_max_tw_buckets500000但要注意这个值不是越大越好。每个TIME_WAIT socket占用少量内存50万个大概占用几百MB。如果你的内存紧张要权衡。另外超过限制后直接销毁TIME_WAIT会破坏TCP的可靠性保证所以这个参数更像是兜底而不是优化。net.ipv4.tcp_fin_timeout这个参数控制FIN_WAIT_2状态的超时时间默认60秒。FIN_WAIT_2是半关闭状态等待对端发送FIN。如果对端一直不发FIN这个连接就会一直占着。适当调小可以加快回收。sysctl -w net.ipv4.tcp_fin_timeout30但注意这个参数不影响TIME_WAIT的持续时间。TIME_WAIT固定是2MSL由内核的TCP_TIMEWAIT_LEN常量决定不能通过sysctl调整。3.2 端口与连接队列别让新连接被拒之门外net.ipv4.ip_local_port_range这个参数定义本地端口范围格式是起始端口 结束端口。默认通常是32768 60999大约28000个端口。对于高并发客户端场景这个范围可能不够。sysctl -w net.ipv4.ip_local_port_range1024 65535把起始端口调到1024结束调到65535可用端口数增加到64000多。但要注意1024以下的端口是特权端口普通用户不能绑定所以从1024开始是安全的。提示端口范围扩大后要确保防火墙规则没有限制端口。另外如果服务器同时作为服务端和客户端服务端监听的端口不能落在本地端口范围内否则会冲突。net.ipv4.tcp_max_syn_backlog这个参数控制SYN队列的长度即收到SYN但还没完成三次握手的连接数量。默认值通常是1024或2048在高并发场景下可能不够导致SYN包被丢弃客户端重试。sysctl -w net.ipv4.tcp_max_syn_backlog65536但光调这个不够还要配合应用层的listen backlog。Nginx的listen ... backlog65535Redis的tcp-backlog 65535都要相应调整。内核实际使用的队列长度是min(应用层backlog, tcp_max_syn_backlog)。net.core.somaxconn这个参数限制listen队列的最大长度也就是已完成三次握手但还没被应用accept的连接数量。默认值通常是128或4096对于高并发服务明显偏小。sysctl -w net.core.somaxconn65535这个参数和应用层的backlog也是取最小值的关系。所以三个地方要一起调somaxconn、tcp_max_syn_backlog、应用层backlog。net.ipv4.tcp_syncookies这个参数控制是否开启SYN Cookie。当SYN队列满了之后内核可以用SYN Cookie来响应新的SYN请求而不需要分配资源。这是一种防御SYN Flood攻击的机制。sysctl -w net.ipv4.tcp_syncookies1但SYN Cookie有副作用它会把一些TCP选项信息编码到序列号里导致无法支持某些TCP扩展如窗口缩放、SACK。所以它应该是最后一道防线而不是常规手段。正常情况下应该通过增大队列来解决问题SYN Cookie只在队列真的满了的时候才起作用。3.3 缓冲区与窗口决定吞吐量的关键net.core.rmem_max / net.core.wmem_max这两个参数分别控制接收缓冲区和发送缓冲区的最大值单位是字节。默认值通常是212992约208KB对于高带宽场景明显不够。sysctl -w net.core.rmem_max16777216 sysctl -w net.core.wmem_max16777216设为16MB。这个值怎么定一个简单的估算方法带宽字节/秒× 往返延迟秒 需要的缓冲区大小。比如1Gbps带宽、10ms延迟需要125MB/s × 0.01s 1.25MB。考虑到突发流量设2-4倍比较合适。net.ipv4.tcp_rmem / net.ipv4.tcp_wmem这两个参数是TCP专用的缓冲区设置格式是最小值 默认值 最大值三个数字。它们会覆盖net.core.rmem_max和net.core.wmem_max对TCP连接的限制。sysctl -w net.ipv4.tcp_rmem4096 87380 16777216 sysctl -w net.ipv4.tcp_wmem4096 65536 16777216接收缓冲区最小值4096默认87380最大16MB发送缓冲区最小值4096默认65536最大16MB。内核会根据连接的实际需求动态调整缓冲区大小在最小值和最大值之间浮动。注意缓冲区不是越大越好。太大的缓冲区会增加内存占用而且可能导致缓冲区膨胀bufferbloat——数据在缓冲区里排队延迟增加。对于延迟敏感的应用反而要限制缓冲区大小。net.ipv4.tcp_window_scaling这个参数控制是否开启窗口缩放。TCP头部的窗口字段只有16位最大表示65535字节。开启窗口缩放后可以通过选项字段协商一个缩放因子把窗口最大值扩大到1GB。sysctl -w net.ipv4.tcp_window_scaling1这个默认是开启的一般不用动。但如果你发现大带宽下吞吐上不去要检查这个是否被关闭了。关闭窗口缩放会导致窗口最大只有64KB在高速网络上严重限制吞吐。net.ipv4.tcp_moderate_rcvbuf这个参数控制是否开启接收缓冲区自动调优。开启后内核会根据连接的实际吞吐和延迟自动调整接收缓冲区大小。sysctl -w net.ipv4.tcp_moderate_rcvbuf1默认开启建议保持。它能让内核自动适应不同的网络条件比手动设死一个值更灵活。3.4 拥塞控制选对算法比调参数更重要net.ipv4.tcp_congestion_control这个参数选择拥塞控制算法。Linux支持多种算法常见的有cubic、reno、bbr。sysctl -w net.ipv4.tcp_congestion_controlbbrcubic是Linux默认算法适合大多数场景。reno是老算法现在基本不用。bbr是Google开发的算法特点是基于带宽和延迟的估计来调整发送速率而不是像cubic那样基于丢包。bbr在有丢包但带宽充足的链路上表现更好比如跨机房传输。但bbr不是万能的。在共享带宽的场景下bbr可能会抢占其他连接的带宽导致不公平。而且bbr对延迟的估计依赖RTT测量在RTT变化剧烈的场景下可能不准。选择算法要根据实际网络条件测试。查看当前可用算法sysctl net.ipv4.tcp_available_congestion_control。加载bbr模块modprobe tcp_bbr。net.ipv4.tcp_slow_start_after_idle这个参数控制连接空闲后是否重新进入慢启动。默认是1即空闲后会重新慢启动。对于长连接但间歇性传输的场景这会导致每次空闲后吞吐都要重新爬升。sysctl -w net.ipv4.tcp_slow_start_after_idle0设为0关闭这个行为空闲后直接以之前的速率发送。对于HTTP/2、gRPC这类多路复用的长连接关闭它通常有好处。net.ipv4.tcp_fastopen这个参数控制TCP Fast Open允许在SYN包中携带数据减少一次往返。值是一个位掩码1表示客户端开启2表示服务端开启3表示都开启。sysctl -w net.ipv4.tcp_fastopen3但TFO需要应用层支持不是所有程序都用了这个特性。而且TFO在某些中间设备上可能被干扰。开启前要确认应用和网络路径都支持。3.5 KeepAlive别让死连接占着茅坑net.ipv4.tcp_keepalive_time这个参数控制连接空闲多久后开始发送KeepAlive探测包默认7200秒2小时。对于需要快速发现死连接的场景2小时太长了。sysctl -w net.ipv4.tcp_keepalive_time600设为600秒10分钟。但注意KeepAlive是TCP层的机制应用层可能自己有心跳机制。如果应用层已经有心跳TCP KeepAlive可以设长一点避免重复探测。net.ipv4.tcp_keepalive_intvl这个参数控制KeepAlive探测包的发送间隔默认75秒。sysctl -w net.ipv4.tcp_keepalive_intvl30net.ipv4.tcp_keepalive_probes这个参数控制发送多少个KeepAlive探测包后判定连接死亡默认9个。sysctl -w net.ipv4.tcp_keepalive_probes3三个参数配合起来空闲600秒后开始探测每30秒发一个发3个没响应就断开。总共需要600 30×3 690秒发现死连接。比默认的7200 75×9 7875秒快得多。提示KeepAlive只对ESTABLISHED状态的连接有效。如果连接已经处于其他状态KeepAlive不会起作用。另外KeepAlive探测包本身也占用带宽如果连接数很多探测包的总量也不小。4. 一份可直接参考的配置模板与落地步骤4.1 高并发短连接场景配置适用于API网关、反向代理、负载均衡层。核心诉求是快速回收TIME_WAIT、扩大端口和队列。# /etc/sysctl.d/99-tcp-tuning.conf # TIME_WAIT复用加快回收 net.ipv4.tcp_tw_reuse 1 net.ipv4.tcp_max_tw_buckets 500000 net.ipv4.tcp_fin_timeout 30 # 端口范围扩大 net.ipv4.ip_local_port_range 1024 65535 # 连接队列扩大 net.core.somaxconn 65535 net.ipv4.tcp_max_syn_backlog 65536 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 # KeepAlive加快死连接发现 net.ipv4.tcp_keepalive_time 600 net.ipv4.tcp_keepalive_intvl 30 net.ipv4.tcp_keepalive_probes 3 # 拥塞控制 net.ipv4.tcp_congestion_control cubic net.ipv4.tcp_slow_start_after_idle 04.2 长连接高吞吐场景配置适用于消息推送、数据库连接池、gRPC服务。核心诉求是缓冲区充足、KeepAlive合理。# /etc/sysctl.d/99-tcp-tuning.conf # 缓冲区大幅增大 net.core.rmem_max 33554432 net.core.wmem_max 33554432 net.ipv4.tcp_rmem 8192 262144 33554432 net.ipv4.tcp_wmem 8192 262144 33554432 net.ipv4.tcp_moderate_rcvbuf 1 net.ipv4.tcp_window_scaling 1 # KeepAlive net.ipv4.tcp_keepalive_time 300 net.ipv4.tcp_keepalive_intvl 15 net.ipv4.tcp_keepalive_probes 3 # 拥塞控制 net.ipv4.tcp_congestion_control cubic net.ipv4.tcp_slow_start_after_idle 0 # 连接队列 net.core.somaxconn 65535 net.ipv4.tcp_max_syn_backlog 655364.3 落地步骤与验证方法改完配置不是就完事了要有一套验证流程备份原配置cp /etc/sysctl.conf /etc/sysctl.conf.bak记录当前所有TCP相关参数的值。写入新配置把上面的配置写到/etc/sysctl.d/99-tcp-tuning.conf。加载配置sysctl -p /etc/sysctl.d/99-tcp-tuning.conf看有没有报错。验证生效sysctl -a | grep -E tcp_tw_reuse|somaxconn|rmem_max确认值已经变了。应用层配合检查Nginx、Redis等应用的backlog配置确保和内核参数匹配。观察指标用ss -s、netstat -s观察连接状态和重传率的变化。压测验证用wrk、ab等工具做压测对比调优前后的QPS、延迟、错误率。注意生产环境改参数要一台一台来不要一次性全改。先改一台观察确认没问题再推全量。如果出问题能快速回滚。5. 踩过的坑与排查实录5.1 调了参数没效果先看这几个地方坑一参数写了但没生效。最常见的原因是配置文件位置不对或者被其他文件覆盖了。/etc/sysctl.conf和/etc/sysctl.d/下的文件加载顺序有讲究数字大的后加载会覆盖前面的。用sysctl -a看实际值不要只看配置文件。坑二应用层backlog没改。内核的somaxconn和tcp_max_syn_backlog调大了但Nginx的backlog还是默认的511实际队列长度还是511。三个地方要一起改。坑三端口范围改了但防火墙没放行。把ip_local_port_range改成1024-65535后如果防火墙只允许32768以上的端口出站那1024-32767的端口就用不了。检查iptables或firewalld规则。坑四缓冲区调大了但应用没设置SO_RCVBUF。有些应用会自己调用setsockopt设置缓冲区大小这会覆盖内核的默认值。如果应用设的值比内核最大值还大会被截断到内核最大值。检查应用的socket选项。5.2 常见问题速查表现象可能原因排查命令解决方向连接超时SYN_RECV多SYN队列满netstat -s | grep -i SYN增大tcp_max_syn_backlog和somaxconnTIME_WAIT数量巨大短连接多回收慢ss -ant | grep TIME_WAIT | wc -l开启tcp_tw_reuse增大tcp_max_tw_buckets端口耗尽无法建连本地端口范围小ss -ant | wc -l扩大ip_local_port_range吞吐上不去缓冲区小或窗口缩放关闭ss -ti看cwnd和rwnd增大缓冲区确认tcp_window_scaling1重传率高网络质量差或拥塞netstat -s | grep retrans换拥塞控制算法检查网络链路死连接占资源KeepAlive太长ss -anto看timer缩短keepalive_time和intvl队列溢出丢包缓冲区或队列满netstat -s | grep overflow增大缓冲区检查应用处理速度5.3 几个容易忽略的细节关于tcp_tw_reuse的适用方向。这个参数只对主动发起连接的一方有效。如果你的服务器是服务端被动接受连接那开启它没用。服务端的TIME_WAIT是客户端关闭连接时产生的服务端无法控制。所以服务端要解决TIME_WAIT问题只能靠增大tcp_max_tw_buckets或者让客户端主动关闭。关于bbr的公平性。bbr在和cubic共存的网络里bbr连接会抢占更多带宽。如果你的服务器和别人的服务器共享网络路径开启bbr可能影响别人。内网环境自己控制问题不大公网环境要谨慎。关于somaxconn的上限。这个值不是可以无限调大的。内核有个编译时的上限SOMAXCONN通常是4096或65535。超过这个值会被截断。用cat /proc/sys/net/core/somaxconn看实际值。关于内存占用。每个TCP连接的内核数据结构大约占用2-4KB加上缓冲区可能更多。如果连接数达到百万级内存占用会非常可观。调大缓冲区之前先算算内存够不够。关于容器环境。在容器里改sysctl参数有些是命名空间隔离的如net.ipv4.tcp_tw_reuse有些是全局的如net.core.rmem_max。Docker默认不允许容器修改全局参数需要--privileged或者--sysctl参数。Kubernetes里可以通过securityContext.sysctls设置但只支持部分参数。6. 我个人的调优体会调了这么多年TCP参数最大的体会是大部分时候你不需要调。现代Linux内核的默认参数已经能覆盖80%的场景真正需要调优的是那20%的极端情况。而且很多时候问题不在TCP参数上而在应用代码、数据库、或者网络架构上。我一般遵循这个流程先看监控确认瓶颈在TCP层再看基线确认默认参数确实不够用然后小步调整每次只改一两个参数观察效果最后固化配置写进自动化部署脚本。还有一个经验不要迷信网上的最优配置。那些配置可能是针对特定场景的你的场景不一样照搬可能出问题。比如有人把tcp_rmem最大值设成64MB如果你的服务器内存只有4GB开几百个连接就把内存吃光了。参数要结合自己的硬件和业务来定。最后说一个容易被忽略的点TCP参数调优要和监控配合。你改了参数怎么知道有没有效果必须有监控数据支撑。我习惯用Prometheus的node_exporter采集TCP指标配Grafana看板改参数前后对比曲线。没有监控的调优就是盲人摸象改好了是运气改坏了是灾难。如果非要说一个万能的建议那就是先把tcp_tw_reuse开了把somaxconn和tcp_max_syn_backlog调大把缓冲区适度增大这三步能解决大部分常见问题。剩下的根据实际监控数据再针对性调整。
阅读完成 · 觉得有帮助?
咨询建站