theme: default themeName: "默认主题" title: "Linux内核参数调优:让Nginx并发能力从5千到5万"
Linux内核参数调优:让Nginx并发能力从5千到5万
线上Nginx扛到5000并发就扛不住了,日志里一堆`Cannot assign requested address`,客户端疯狂超时,老板在群里@你催——这场景熟不熟悉?
别急着加机器,大概率是内核参数没调。Linux默认的网络参数是给桌面系统设计的,不是给高并发服务器用的。我在线上做过无数次这种调优,同样的硬件,调完并发能力直接翻10倍不是吹的。下面把每个关键参数掰开揉碎讲清楚。
先看问题出在哪
压测工具用wrk,4线程1000连接,压一个静态页面:
wrk -t4 -c1000 -d30s http://10.0.0.1/index.html
调优前的结果:
Requests/sec: 5231.42
Transfer/sec: 4.28MB Socket errors: connect 847, read 0, write 0, timeout 23
注意那个`Socket errors: connect 847`,这不是Nginx的问题,是操作系统拒绝了连接请求。内核层面的瓶颈,你Nginx配得再花哨也没用。
TIME_WAIT连接回收:tcp_tw_reuse 和 tcp_fin_timeout
高并发短连接场景下,TIME_WAIT状态的连接会疯狂堆积。一个连接关闭后要等2MSL时间(默认60秒)才能回收,如果每秒建立5000个短连接,一分钟就是30万个TIME_WAIT,端口直接耗尽。
tcp_tw_reuse = 1这个参数允许内核把TIME_WAIT状态的连接重新用于新的TCP连接。打开它,那些等回收的端口就能立刻分配给新连接用。前提是`tcp_timestamps`也得开(默认就是开的),因为复用依赖时间戳来避免旧数据包干扰。
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_timestamps = 1
有人会问`tcp_tw_recycle`呢?别用。这参数在NAT环境下会导致部分客户端连接失败,内核4.12以后已经直接删掉了。踩过这个坑的运维不在少数。
tcp_fin_timeout = 15默认60秒太长了。改成15秒,TIME_WAIT状态最多等15秒就回收。对于内网或可靠网络环境,15秒足够了。如果是公网环境,建议设30秒。
net.ipv4.tcp_fin_timeout = 15
连接队列:somaxconn
这是最容易被忽略的参数。`net.core.somaxconn`定义了全连接队列(accept queue)的最大长度。Nginx的`listen`指令有个`backlog`参数,但实际生效值是`min(backlog, somaxconn)`。内核默认somaxconn是128,Nginx默认backlog是511——所以实际队列长度只有128。
高并发下128个连接排完,后面的直接被内核RST掉。Nginx日志里你会看到连接被重置,但根本原因在内核层。
net.core.somaxconn = 65535
Nginx那边也得配合改:
listen 80 backlog=65535;
这样全连接队列才能真正撑起来。半连接队列(syn queue)由`net.ipv4.tcp_max_syn_backlog`控制,同样调大:
net.ipv4.tcp_max_syn_backlog = 65535
文件描述符限制
每个TCP连接都是一个文件描述符。Linux默认的`ulimit -n`只有1024,对服务器来说等于自废武功。512个并发连接加上日志文件、静态文件句柄,1024秒爆。
系统级限制改`/etc/sysctl.conf`:
fs.file-max = 1048576
用户级限制改`/etc/security/limits.conf`:
* soft nofile 1048576
* hard nofile 1048576 root soft nofile 1048576 root hard nofile 1048576
Nginx自身也要改,在`nginx.conf`里:
worker_rlimit_nofile 1048576;
三处都改了才算完,缺一不可。我见过太多人只改了limits.conf没改nginx.conf,压测一上还是报24: Too many open files。
本地端口范围:ip_local_port_range
客户端连服务器需要临时端口,默认范围32768-60999,大概28000个端口。如果Nginx本身还要做反向代理(上游连接),这个范围就不够用了。
net.ipv4.ip_local_port_range = 1024 65535
直接把可用端口扩到64000+。别担心占用了系统端口,Linux有`ip_unprivileged_port_start`控制,默认1024以下还是受限的。
TCP读写缓冲区
默认的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
三个值分别是:最小值、默认值、最大值。87380/65536是默认读写缓冲,对大多数HTTP请求足够了。最大值16MB留给大文件传输用。
epoll相关参数
Nginx用epoll做事件驱动,epoll本身不需要太多调优,但有个参数值得注意:
fs.epoll.max_user_watches = 1048576
默认值因发行版而异,有些只有8192。这个参数限制了单个用户能注册的epoll监听数量。高并发下worker进程监听的连接数可能超过默认值,超了epoll_wait就报错。
TCP快速打开:tcp_fastopen
net.ipv4.tcp_fastopen = 3
3表示客户端和服务端都开启TFO。第一个连接还是正常三次握手,但会拿到一个cookie,后续连接可以在SYN包里就带上数据,省掉一个RTT。对于短连接API场景提升明显,实测能提升15%-20%的QPS。
调优后的压测结果
同样的wrk命令,同样的硬件:
wrk -t4 -c1000 -d30s http://10.0.0.1/index.html
调优后:
Requests/sec: 52847.63
Transfer/sec: 43.21MB Socket errors: connect 0, read 0, write 0, timeout 0
从5231到52847,10倍提升,Socket errors清零。这还是只调了内核参数,Nginx自身配置都还没动。
再加把劲,4线程5000连接:
wrk -t4 -c5000 -d30s http://10.0.0.1/index.html
Requests/sec: 48216.71
Socket errors: connect 0, read 12, write 0, timeout 0
5000连接稳住了,几乎没有错误。调优前这个量级直接连不上。
生产环境推荐sysctl配置
直接拿去用,`/etc/sysctl.d/99-high concurrency.conf`:
# TIME_WAIT回收
net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_fin_timeout = 15
连接队列
net.core.somaxconn = 65535 net.ipv4.tcp_max_syn_backlog = 65535
文件描述符
fs.file-max = 1048576
端口范围
net.ipv4.ip_local_port_range = 1024 65535
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
epoll
fs.epoll.max_user_watches = 1048576
TCP Fast Open
net.ipv4.tcp_fastopen = 3
TCP keepalive(长连接优化)
net.ipv4.tcp_keepalive_time = 600 net.ipv4.tcp_keepalive_intvl = 30 net.ipv4.tcp_keepalive_probes = 3
SYN重试
net.ipv4.tcp_synack_retries = 2 net.ipv4.tcp_syn_retries = 2
连接跟踪(如果用iptables)
net.netfilter.nf_conntrack_max = 1048576
执行`sysctl -p /etc/sysctl.d/99-high concurrency.conf`立即生效。
几个踩坑提醒
改完sysctl不重启Nginx等于白改。内核参数是即时生效的,但Nginx的worker进程在启动时读取文件描述符限制,得`nginx -s reload`或者直接重启。
云服务器注意安全组限制。有些云厂商的安全组有连接速率限制,你内核调得再好,上游给你限流了,压测数据也上不去。遇到这种情况先找云厂商确认。
Docker容器里的sysctl是独立的。在宿主机改了sysctl,容器里不生效。要么在docker run的时候加`--sysctl`参数,要么用K8S的initContainer来设置。
别一口气全调到最大值。`tcp_rmem`/`tcp_wmem`的最大值调太大,万并发下内存直接爆。根据你的实际内存算一下:最大值 × 并发数 × 2(读写各一份)不能超过可用内存的60%。
调优不是玄学,每个参数都有其背后的原理。搞清楚为什么调,调多少合适,才能稳稳地扛住流量。
【放心,我们兜底】
不管你是自己尝试修复,还是需要专业人员上门,易云城IT服务都给你托底。修不好不收费,修好了质保期内随时找我。
📞 服务热线:13708730161 💬 微信:eyc1689 📧 邮箱:service@eycit.com 🌐 https://www.eycit.com
您身边的IT专家。