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

为什么 net.core.somaxconn 调小了还是被拒绝连接?
因为内核实际生效的 backlog 长度,是 min(somaxconn, listen(sockfd, backlog))。应用层调用 listen() 时传的第二个参数(比如 Node.js 的 server.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 边缘场景下 EPOLLET 比 EPOLLONESHOT 更容易丢事件?
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)。