Linux 高并发连接优化策略

1次阅读

内核实际生效的backlog是min(somaxconn, listen()传入值),应用层若设小则调大somaxconn无效;tcp_tw_reuse仅对客户端新连接有效且依赖tcp_timestamps;systemd服务需在unit文件配limitnofile;et模式易丢事件因未读完缓冲区数据。

Linux 高并发连接优化策略

为什么 net.core.somaxconn 调小了还是被拒绝连接?

因为内核实际生效的 backlog 长度,是 min(somaxconn, listen(sockfd, backlog))。应用层调用 listen() 时传的第二个参数(比如 Node.jsserver.listen(port, cb) 默认是 511),若小于 net.core.somaxconn,那再怎么调大内核参数也没用。

  • 检查应用代码里是否显式设置了 backlog,如 Python 的 socket.listen(1024)、Go 的 net.Listen("tcp", ":8080") 默认由运行时决定,但某些框架会硬编码低值
  • ss -lnt 查看当前监听 socket 的 Recv-Q 值,持续接近或等于你设的 backlog,说明队列已在溢出边缘
  • 临时验证:启动服务前先 echo 65535 > /proc/sys/net/core/somaxconn,再确保应用调用 listen() 时传入 ≥65535 的值(部分语言 runtime 不支持超大值,需查文档)

net.ipv4.tcp_tw_reuse 开启后反而丢连接?

它只对「客户端主动发起的新连接」有效,且要求 TIME_WAIT socket 对应的四元组(源IP+源端口+目标IP+目标端口)不冲突。服务端大量短连接 + NAT 环境下,复用失败率高,还可能触发对端 RST。

  • 仅在明确是客户端连接池不足(如负载机发请求)时开启;服务端高并发 http API 场景下,优先调大 net.ipv4.ip_local_port_range 和降低 net.ipv4.tcp_fin_timeout
  • tcp_tw_reuse 依赖 tcp_timestamps=1,而某些老旧设备或中间件(如某些防火墙)会丢弃带 timestamp 选项的包,导致握手失败
  • 观察 netstat -s | grep -i "times" pruned from time-wait 是否激增——那是内核强制回收,说明配置没起作用或压力远超预期

文件描述符不够用,ulimit -n 改了却无效?

systemd 服务默认无视 shell 的 ulimit 设置,必须在 service 文件里显式声明 LimitNOFILE,否则进程启动时仍沿用 systemd 的默认限制(通常是 1024)。

  • 查当前进程限制:cat /proc/<pid>/limits | grep "Max open files"</pid>,确认是不是 systemd 拉起的
  • 修改 /etc/systemd/system/<service>.service</service>,加入 LimitNOFILE=65536,然后 systemctl daemon-reload && systemctl restart <service></service>
  • 注意:ulimit -n 只影响当前 shell 及其子进程,无法穿透到由 init 系统管理的守护进程

epoll 边缘场景下 EPOLLETEPOLLONESHOT 更容易丢事件?

ET 模式要求应用必须一次性读完 socket 缓冲区所有数据(直到 recv() 返回 EAGAIN),否则后续可读事件不会再次触发。而 EPOLLONESHOT 是事件触发一次后自动禁用,需手动 epoll_ctl(... EPOLL_CTL_MOD ...) 重新启用,逻辑更可控。

  • HTTP/2 或 TLS 分块传输场景中,单次 recv() 可能只读到半个帧,ET 下若没继续读,连接就“卡住”了
  • EPOLLONESHOT 配合线程池更安全:工作线程处理完一个事件后,再通知 epoll 重新关注该 fd,避免状态竞争
  • glibc 的 readv() 在部分内核版本中与 ET 模式存在边界 bug(如读到 EOF 后未清空缓冲区),建议用 recv(fd, buf, MSG_DONTWAIT) 替代

高并发不是参数就能解决的事——每个 sysctl 项背后都对应着内核路径上的具体判断逻辑,改之前得先知道它在哪一环起作用。线上调参最危险的不是设错值,而是没配好配套机制(比如开了 tcp_tw_reuse 却没关 tcp_timestamps,或者调大 somaxconn 却忘了改应用层 backlog)。

text=ZqhQzanResources