ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Linux高并发TCP网络调优实战:sysctl参数配置与性能提升

Linux高并发TCP网络调优实战:sysctl参数配置与性能提升 干了这么多年Linux运维我见过太多人一听到“高并发”就冲过去改sysctl把网卡队列、文件描述符、TIME_WAIT一顿调结果压测一跑比没调之前还慢甚至直接把线上搞挂。说实话Linux网络参数调优不是玄学也不是堆数值越大越好关键是搞清楚你的业务到底卡在哪一层然后再动手。这篇文章我就结合自己处理过的几个高并发场景把网络参数调优的思路、关键参数背后的原理、可复制的配置项以及踩过的坑一次说清楚。内容面向的是搞后端开发、运维、SRE以及看了一堆“调优大法”却不知道怎么落地的朋友。我会从瓶颈排查开始讲再到具体参数怎么改、为什么这么改最后给一套我实测过的高并发参数配置附上前后压测数据对比和踩坑记录。跟着走一遍你应该能自己判断“我的服务到底需不需要调网络参数调哪些有效果调完怎么确认有效果。”1. 别急着改参数先确认你的瓶颈到底在哪高并发场景下服务反应慢原因可能五花八门CPU不够、数据库扛不住、应用层锁竞争、内存换页、磁盘IO慢网络参数只是其中一环。如果连瓶颈都没定位就动sysctl等于头痛医脚调完只能靠运气。1.1 高并发“卡”在哪一层我自己判断瓶颈有个三板斧的顺序先看CPU和负载再看网络连接状态最后才翻内核统计。如果CPU跑满优先怀疑应用本身而不是网络如果CPU不高但请求大量超时再看连接队列和丢包如果CPU、内存都不高但ab/wrk压测QPS上不去才重点查网络参数特别是nginx、网关这类接入层服务很多时候瓶颈是epoll事件处理逻辑或者worker进程数不够网络参数的锅并不大。我遇到过一台8核机器压测时CPU直接烧到95%同事还在拼命调somaxconn结果真正的瓶颈是nginx worker进程只配了1个。这个坑很典型。1.2 快速定位手段几条命令看穿问题先列几条我每次排查都会敲的命令全都能在线上安全执行放心用# 系统整体负载和CPU top # TCP连接状态统计重点关注TIME_WAIT、SYN_RECV、ESTABLISHED ss -s ss -ant | awk {print $1} | sort | uniq -c | sort -rn # 查看全连接队列溢出情况Recv-Q大于0且数值大说明accept队列塞满了 ss -lnt # 看丢包、超时等协议栈统计 netstat -s # 中断和网卡处理能力 mpstat -P ALL 1还有一个很容易被忽略的命令是dmesg。如果出现nf_conntrack: table full, dropping packet说明连接跟踪表满了这种情况下你调什么tcp_rmem都没用该调的是nf_conntrack_max。我在OpenStack环境里就栽过这个跟头搞了半天才在dmesg里看到丢包原因。ss -lnt的Recv-Q列很有意思。如果你在nginx机器上执行后看到某个监听端口的Recv-Q长期不为0说明accept队列满了客户端连进来但nginx还没来得及accept。这就是典型的应用层处理不过来而非网卡或内核的锅。这时候调大backlog只是治标真正要做的是提升应用accept能力或者加worker进程。1.3 不同瓶颈对应不同参数组合把这套逻辑整理成表格大家排查时可以直接对号入座现象大概率瓶颈该动的参数CPU跑满QPS上不去应用层/worker数/锁改代码、调nginx worker别动网络参数大量TIME_WAIT端口耗尽短连接太多/连接回收慢端口范围、tw_reuse、长连接改造SYN_RECV堆积握手慢SYN队列不够/半连接丢包tcp_max_syn_backlog、somaxconn、syn_cookiesRecv-Q堆积在监听端口accept队列满/应用accept慢backlog、somaxconn、应用层并发模型dmesg报conntrack丢包连接跟踪表满nf_conntrack相关参数而非TCP参数带宽高延迟大传输慢TCP窗口/拥塞控制rmem/wmem、拥塞控制算法看清自己的问题属于哪一类再往下翻参数才不会白调。接下来我要讲的参数基本都是针对“连接多、短连接频繁、队列积压”这几个典型高并发痛点。2. 文件描述符、全连接队列与TIME_WAIT第一组见效最快的配置先说结论在高并发场景下文件描述符、accept队列和TIME_WAIT是三个最先捅破窗户纸的地方也是最容易调出肉眼可见效果的地方。2.1 文件描述符限制第一个必须加大Linux一切皆文件socket也是文件描述符。默认情况下一台机器通常允许单个进程打开1024个文件描述符这个数对高并发毫无意义。假设你的进程要维持1万个长连接再加上日志、配置文件、临时文件占用1024根本不够用。调整个进程限制有两种方式。临时生效用ulimitulimit -n 1048576注意这个只对当前shell及其子进程有效重启后失效。生产环境我建议改在systemd服务文件里因为现在大部分服务都由systemd托管[Service] LimitNOFILE1048576同时还要确认内核级限制sysctl -w fs.file-max2097152这里有段踩坑经历有一次我只改了nginx的worker_rlimit_nofile没有看systemd的LimitNOFILE结果nginx启动后文件描述符上限还是1024因为systemd会先约束进程资源限制再让它继承。后来把LimitNOFILE也设成1048576才生效。所以检查顺序别反了先看systemd/进程的limit再看内核fs.file-max。fs.file-max是内核级别的全局上限如果跑的是容器还可能受cgroup限制影响需要看/sys/fs/cgroup/pids.max或者/sys/fs/cgroup/.../pids.current确认是否撞到容器上限。2.2 backlog与somaxconnaccept队列才是隐藏乡下很多人调完文件描述符依然大量超时问题出在accept队列。当客户端发起TCP连接内核完成三次握手后这个连接不是立刻交给应用层而是先放进监听socket的accept队列应用调用accept()后才会真正拿到连接。如果这个队列满了新连接直接被内核丢掉客户端表现为连接超时或者被重置。内核里有两个参数控制这个队列net.core.somaxconn和net.ipv4.tcp_max_syn_backlog。严格来说listen时传入的backlog参数会被内核和somaxconn取较小值。也就是说即使你写代码时传入backlog1024如果somaxconn是128最终队列长度还是128。nginx默认listen时backlog是511redis默认是511这些都依赖somaxconn支持。所以我通常直接把somaxconn调到1024以上sysctl -w net.core.somaxconn2048同时把nginx配置里的backlog也写清楚listen 80 backlog2048;验证方法很简单压测的时候看ss -lntss -lnt | grep :80如果Recv-Q长期占用很高说明队列确实在排队调大backlog有帮助如果Recv-Q本身一直是0或者很小那问题根本不在队列。有个特别容易混淆的概念这里说的accept队列是“已完成三次握手、等待应用accept”的队列而SYN队列是“还没完成握手、等待SYN ACK”的半连接队列。tcp_max_syn_backlog管的是后者在SYN Flood攻击或握手大量堆积时才有意义普通高并发下基本不用优先动它。2.3 TIME_WAIT与端口回收调了可能踩坑的重灾区TIME_WAIT是TCP四次挥手后主动关闭连接的一方进入的状态会持续大约2倍的MSL很多资料会说默认60秒。如果业务短连接多比如Java服务直连MySQL、HTTP api频繁短连TIME_WAIT会积压到几万个多到占用端口耗尽。ss -s里如果TIME_WAIT数量长期超过几万先别急着搜“怎么快速干掉TIME_WAIT”先想想为什么会有这么多主动关闭的连接。如果是服务端主动断开很可能是KeepAlive超时设置太短、连接池配置不合理如果是客户端还好调整频率即可。实在需要回收有几个参数组合值得慎重。net.ipv4.tcp_tw_reuse允许内核在TIME_WAIT状态还没结束时就复用该端口发起新的连接前提是tcp_timestamps开启默认开启。这个参数对客户端主动发连接特别有效。但注意它只对outbound连接生效且不能保证完全可靠如果连接四元组撞车且时间戳校验出问题偶发连接异常。旧内核还有一个tcp_tw_recycle这个参数在NAT环境下有大坑会导致不同客户端被NAT成同一IP后因时间戳递增判断异常而互相影响出现随机性连接失败。幸运的是Linux 4.12及以后内核直接把它移除了所以如果你用较新内核不用纠结这个参数。如果你还在用老内核建议直接别开。还想从根本上减少TIME_WAIT可以调整MSL相关的tcp_fin_timeoutsysctl -w net.ipv4.tcp_fin_timeout15这是一把双刃剑调小可以加快TIME_WAIT释放但如果网络上还有迟到的数据包可能出现老连接数据串到新连接上的风险。好在现在的业务大多走TCP且上层有校验风险整体可控。我会强调一点别把tcp_fin_timeout调得太极端比如小于5我在生产上试过10~15比较平衡。端口资源也是隐蔽瓶颈。客户端发起大量短连接时需要占用本地端口默认范围是32768~60999大概不到3万个端口。如果TIME_WAIT又占着端口很快就耗尽。可以适当扩大sysctl -w net.ipv4.ip_local_port_range1024 65000但别把起始端口设成1024以下那里有特权端口和一堆系统服务占用容易冲突。同时也别指望靠扩大端口范围解决一切因为端口总数就那么多核心还是让连接复用和变成长连接。3. TCP缓冲区、窗口与拥塞控制在内核协议栈里抠带宽连接数搞定之后下一个性能瓶颈往往是吞吐量。这部分和上面完全两码事连接多不一定慢但传输慢会让你觉得“系统好像卡了”。TCP传输性能主要由缓冲区大小、窗口缩放和拥塞控制算法三件事决定。3.1 缓冲区大小不是越大越好BDP才是依据TCP的发送和接收缓冲区决定了单条TCP连接能缓冲多少未确认数据。如果缓冲区太小即使带宽很大发送方也得停下来等ACK等同于把高带宽链路跑成了低带宽。这里的核心概念是BDPBandwidth-Delay Product带宽延迟积。BDP 带宽 × RTT举个例子假设你的业务是10Gbps内网RTT是0.5ms那么BDP 10Gbps × 0.0005s 5Mbit 0.625MB。如果是跨机房场景带宽1GbpsRTT50msBDP 1Gbps × 0.05s 50Mbit 6.25MB。也就是说单条TCP连接的缓冲区要能装下6.25MB才能把1Gbps带宽跑满。Linux默认的net.ipv4.tcp_rmem和net.ipv4.tcp_wmem是三个值最小值、默认值、最大值单位是字节sysctl net.ipv4.tcp_rmem # 通常输出类似 4096 131072 6291456中间那个默认值会自动调节但如果要跑大流量、长肥网络建议把最大值调大。我常用的配置是sysctl -w net.ipv4.tcp_rmem4096 87380 16777216 sysctl -w net.ipv4.tcp_wmem4096 65536 16777216注意tcp_rmem的最大值还受到net.core.rmem_max限制tcp_wmem受net.core.wmem_max限制需要一并调大否则写了也不生效。另外一个容易忘的事接收窗口扩大要靠net.ipv4.tcp_window_scaling默认是1开启一般不用动但老内核或某些裁剪内核可能为0那就得打开。这里必须提醒一句缓冲区不是越大越好。如果单条连接的缓冲区设置到16MB1万个连接意味着最大可能占用160GB内存直接把机器搞OOM。所以在比值调优时一定要结合连接数估算内存别光看大数字爽。3.2 拥塞控制算法CUBIC够用BBR给网络情况差的人惊喜拥塞控制决定TCP在链路出现丢包和延迟时怎么调整发送速率。传统CUBIC对通用网络友好是默认选择。如果链路高带宽高延迟且偶发丢包CUBIC遇到丢包后退避太激进带宽利用率下降这时候Google的BBR实测有奇效。查看和切换算法# 查看当前可用算法 sysctl net.ipv4.tcp_available_congestion_control # 切换成BBR需要内核4.9且内核编译了BBR模块 sysctl -w net.ipv4.tcp_congestion_controlbbr要持久化写进/etc/sysctl.d/99-network-tuning.confnet.ipv4.tcp_congestion_controlbbr我实测的一个场景是公网跨地域数据传输带宽约200MbpsRTT约100ms中间偶尔丢包。默认CUBIC时吞吐只有带宽的三分之一切到BBR后几乎跑满延迟还有改善。而在低延迟数据中心内部CUBIC和BBR差距很小此时没必要折腾。如果你的内核没有编译BBR升级内核或者使用发行版自带的内核模块如tcp_bbr可以用modprobe tcp_bbr加载后再说。别在没模块的情况下硬写配置不生效还误导判断。3.3 杂项参数keepalive、SYN重试与netdev_max_backlog一些杂项参数单独看不显眼组合起来效果很明显。我常用的有这几组# 连接空闲多久开始探测以及探测间隔、失败次数 sysctl -w net.ipv4.tcp_keepalive_time600 sysctl -w net.ipv4.tcp_keepalive_intvl30 sysctl -w net.ipv4.tcp_keepalive_probes3 # 半连接/全连接相关 sysctl -w net.ipv4.tcp_max_syn_backlog4096 sysctl -w net.core.netdev_max_backlog10000 # 减小握手失败等待时间 sysctl -w net.ipv4.tcp_syn_retries3 sysctl -w net.ipv4.tcp_synack_retries3netdev_max_backlog是网卡接收队列的积压上限突发流量进来时如果协议栈处理不过来这个队列满就会丢包。调大它对突发流量有缓冲但也不会神奇地提升稳定QPS。tcp_syn_retries控制SYN重传次数默认5对公网客户端可以适当调低让快速失败的请求尽快结束减少半连接占用。这里有个小技巧keepalive参数为什么要调因为很多业务层没有心跳连接又是TCP长连接如果对端崩溃或者网络闪断你不调整keepalive可能很久都发现不了死连接连接数被死连接占满。我调过一台连接数2万的机器里面至少有3000个死连接调低keepalive_time后僵尸连接被清理连接数马上降下来。4. 一套实测过的调优组合压测前后对比与参考配置看了这么多理论咱们落一个完整场景。前阵子有个业务叫我帮忙处理nginx反向代理到两台后端Tomcat并发峰值8000压测2000并发时就大面积超时TIME_WAIT堆积接近4万偶尔还有连接被重置。4.1 测试环境与操作流程测试机配置8核16G网卡千兆CentOS 7.9内核3.10。压测工具用wrk从另一台机器发起目标URL是一个经过nginx转发的小接口后端返回JSON约40字节。压测命令wrk -t8 -c2000 -d120s http://target/api/ping先采集基线数据然后逐项调整参数每改完一组就重新压测最后汇总对比。调整参数过程我分了四步每次只动一组避免说不清是哪个参数起的作用第一步改文件描述符和accept队列 第二步改TIME_WAIT和端口范围 第三步改TCP缓冲区和拥塞控制 第四步重新压测、观察连接状态。这里要提一个重要习惯每改一组参数立刻压测并记录结果不要四组一起上。否则线上出了问题你根本不知道是哪一个参数引入的回滚都无从谈起。4.2 前后数据对比基线数据未调优QPS约4200P99延迟220ms2000并发下错误率6.5%TIME_WAIT峰值38000端口耗尽导致connect: Cannot assign requested address报错频繁出现。第一轮调整后主要是文件描述符、somaxconn、backlog、端口范围QPS提到5800错误率降到2.1%最明显的是TIME_WAIT不再导致端口耗尽了log里的connect报错消失。第二轮调整加上tw_reuse、把短连接往keepalive长连接方向改并放宽TCP缓冲区后QPS到6700P99延迟降到95ms错误率0.3%。完整参考配置见下方sysctl文件。注意这是针对这台机器和业务压出来的可以参考别直接抄到你所有机器上内存小、连接模式不同的机器需要微调。4.3 一份可直接落地的参考配置cat /etc/sysctl.d/99-network-tuning.conf # 内核文件句柄上限 fs.file-max 2097152 # 单条TCP连接收/发缓冲区最小值 默认值 最大值 net.ipv4.tcp_rmem 4096 87380 16777216 net.ipv4.tcp_wmem 4096 65536 16777216 # 内核层缓冲区上限 net.core.rmem_max 16777216 net.core.wmem_max 16777216 # 监听队列 net.core.somaxconn 2048 net.ipv4.tcp_max_syn_backlog 4096 net.core.netdev_max_backlog 10000 # TIME_WAIT与端口 net.ipv4.tcp_fin_timeout 15 net.ipv4.tcp_tw_reuse 1 net.ipv4.ip_local_port_range 1024 65000 # keepalive net.ipv4.tcp_keepalive_time 600 net.ipv4.tcp_keepalive_intvl 30 net.ipv4.tcp_keepalive_probes 3 # 握手重试 net.ipv4.tcp_syn_retries 3 net.ipv4.tcp_synack_retries 3 # 拥塞控制 net.ipv4.tcp_congestion_control cubic然后执行sysctl --system # 或者老版本用 sysctl -p /etc/sysctl.d/99-network-tuning.conf提醒一句sysctl --system会把所有配置文件都加载一遍如果已有文件里有和这个文件相同的键后加载的生效优先级要看目录顺序。建议先检查现有配置sysctl -a | grep ^net\.避免改完冲突。这个配置里我把拥塞控制写了cubic因为我这台机器内网通信BBR收益不大。如果你跑公网大流量跨地域按前面讲的那套再换成bbr即可。4.4 验证效果的正确方式压测完成后别只看QPS还要看TCP连接状态分布。我用这几条命令做持续观察# 每秒打印一次tcp状态统计持续30秒 for i in $(seq 1 30); do ss -s; sleep 1; done # 看特定监听端口队列情况 watch -n 1 ss -lnt | grep :80 # 看网络协议栈的错误计数 watch -n 1 netstat -s | grep -E timewait|listen|overflow|retransmit重点观察TIME_WAIT是否还堆积、ss -lnt的Recv-Q是否一直高位、netstat -s里的listen queue overflow是否持续增加。如果升级参数后这些数字都降下来了才叫真正调优成功。另外最好结合业务监控看压测时后端Tomcat的线程池使用率、GC频率、连接池活跃数都要看。网络参数调好了但如果后端线程池被打满前端表现还是超时那就继续往后端排查。参数调优不是独立事件永远是全链路协作的一部分。5. 线上踩过坑之后调优的边界、回滚与监控最后这部分才是真正区分“背参数”和“会调优”的分界线。我在生产环境里因为乱调网络参数出过几次事故都记在这里了希望大家能避开。5.1 三个真实踩坑记录第一个坑盲调tcp_rmem/tcp_wmem导致内存暴涨。当时为了追求单连接拉满带宽把rmem/wmem最大值都设成了64MB结果这台机器扛了两万连接内存直接飙到可用内存的90%随后OOM killer把主要进程杀了线上服务中断。后来我估算了一下两万连接每个都开大缓冲区最坏情况几百GB内存完全超出物理内存。教训调缓冲区之前先做数学题。公式很简单最大内存占用 连接数 × tcp_rmem最大 tcp_wmem最大。8G内存的机器如果连接数1万那缓冲区最大支撑就是8G/10000 ≈ 800KB再高就是赌并发不会同时开满。合理做法是区分业务下载/传输型服务才调大缓冲区短请求型服务保持默认小缓冲区即可。第二个坑开启了tcp_tw_recycle在NAT网络下翻车。那台老机器内核3.10同事看TIME_WAIT太多直接开了tcp_tw_recycle1结果上线后用户随机反馈“部分请求连不上刷新几次又好了”排查过程极其痛苦。最后定位到是NAT网关后面多个客户端共用一个出口IPtw_recycle的时间戳机制误判旧连接包为“过期”直接丢弃。教训新内核无此参数老内核千万别碰tcp_tw_recycle。tcp_tw_reuse对客户端出方向连接比较安全能不用尽量不用先考虑改长连接和调小fin_timeout。第三个坑改了net.core.somaxconn2048之后重启服务发现nginx反复失败。排查了很久才发现nginx配置里我写了listen 80 backlog2048但系统里旧版本nginx的listen指令有最大上限或者没有这个backlog选项的权限编译模块有限制。更诡异的是某些应用内部listen传入的backlog参数是硬编码的根本不读你系统的somaxconn你外面调再大也无效。教训改完核对实际生效值。用ss -lnt看当前监听端口的Send-Q列这个值显示的就是listen实际使用的backlog和somaxconn取较小值后的结果如果Send-Q显示还是128说明应用传的backlog太小或者somaxconn改动没生效得继续往上找原因。5.2 参数变更流程与回滚方案运维老手都知道改sysctl最容易闯祸的地方不是参数本身而是过程不对。我给自己定了一套固定流程把要改的参数写进独立的/etc/sysctl.d/99-tuning.conf不直接在/etc/sysctl.conf上改方便回滚时直接删除整个文件执行前用sysctl -a /tmp/sysctl-before-$(date %F).txt做全量备份用sysctl -w临时改一遍验证没有副作用后再写配置回滚时直接删除配置文件和sysctl -w重新设回旧值或重启机器后自然回到默认有一点要知道sysctl -w改的可以立即生效但重启后恢复默认写入配置文件的要重启或者执行sysctl --system才生效。所以推荐的做法是先用-w临时验证确认可行后写文件再执行sysctl --system永久生效。这样既不用反复重启也能快速回滚。回滚还有一个容易被忽视的地方一次改了很多键回滚时分不清哪些是上次改的。所以每次变更先在配置文件里写好注释标注变更日期、原因、预期效果。调优不是一个人的事后来接手的人看到注释才知道这套参数是为了解决哪一次压测事故否则下一个人莫名其妙把这些参数当成“默认配置”出问题也不知道从何查起。5.3 持续监控的意义调完不等于结束参数调完压测好看不代表线上就万事大吉。流量模型会变连接数会涨内核参数还需要动态观察。我的习惯是压测结束后继续观察至少3天重点看这几个指标的趋势TCP连接状态分布TIME_WAIT、ESTABLISHED、SYN_RECV、CLOSE_WAIT端口使用率/proc/sys/net/ipv4/ip_local_port_range范围内已用端口数网卡丢包和重传率netstat -s里的retransmit、listen queue overflow内存中用于socket缓冲区的部分可以用ss -m看每条连接占用把这些指标接到监控系统里设置告警阈值。如果过了几天TIME_WAIT重新堆起来说明业务方的连接池配置又变了或者流量模型变了需要重新评估。我平时还会用一条简化命令看端口是否接近耗尽cat /proc/net/sockstat # 重点关注 TCP: inuse 数值和 # /proc/sys/net/ipv4/ip_local_port_range 做减法估算剩余端口/proc/net/sockstat里的inuse是当前正在用的TCP socket数量虽然不直接等于本地端口占用数但趋势很有参考价值。配合ss -tan | wc -l基本可以判断连接数是否逼近上限。回到最开始那句话调优最难的从来不是敲那几行sysctl而是找到值得调的参数、确认它真的生效、并且能在出问题时快速回滚。把这套方法练熟了以后再遇到高并发网络问题你至少不会被网上那些“万能调优大法”牵着鼻子走了。最后再分享一个小技巧改完任何网络参数先不要急着上生产全量找一台低峰期机器先压测观察确认没有异常后再推全量。这套流程我用了很久几乎可以杜绝网络参数变更引发的线上事故。
返回列表