资讯详情

资讯详情

端口耗尽:ip_local_port_range 与 TIME_WAIT 的关系

我不会起名字322· 后端 / 算法 / 数据库 技术栈 力扣 Hot100 Go 项目 Redis MySQL文章目录端口耗尽ip_local_port_range 与 TIME_WAIT 的关系一、先看报错二、为什么端口会用完服务端不会遇到这个问题三、和 TIME_WAIT 的关系怎么确认是 TIME_WAIT 占满了四、解法 1扩大端口范围最快五、解法 2开启 tcp_tw_reuse六、解法 3根治——消灭短连接最常见的元凶连接池没配好数据库连接池Redis / MQ 客户端七、还有一个场景压测机八、K8s 场景的额外注意九、带走物端口耗尽ip_local_port_range 与 TIME_WAIT 的关系一、先看报错端口耗尽的报错非常明确Cannot assign requested address或者connect: cannot assign requested address (errno 99)在 Java 里java.net.SocketException: No buffer space available (maximum connections reached?)看到这两条基本可以直接确诊是本地临时端口耗尽——不用再去怀疑网络、怀疑对端。二、为什么端口会用完TCP 连接由四元组唯一标识(源IP, 源端口, 目的IP, 目的端口)对客户端主动发起连接的一方来说目的 IP 和目的端口是固定的比如10.0.1.20:3306源 IP 也是固定的。所以唯一可变的只有源端口。而源端口来自一个有限的池子cat/proc/sys/net/ipv4/ip_local_port_range# 32768 60999可用端口数 60999 - 32768 1 28232 个。一旦这 28232 个端口都被占用包括处于ESTABLISHED和TIME_WAIT状态的新连接就会失败。服务端不会遇到这个问题服务端监听的是一个固定端口比如 8080。连接数再多源端口都是对端的服务端这边四元组的目的端口始终是 8080所以理论上可以建几万甚至几十万条连接。客户端受限源端口有限28232 个 服务端受限内存、fd、CPU不是端口所以端口耗尽永远发生在主动发起连接的一方。三、和 TIME_WAIT 的关系TIME_WAIT 会占用端口而且占用时间是固定的 60 秒Linux 下 2MSL 固化成 60s。这意味着如果你每秒新建 500 条短连接 60 秒内会累积 500 × 60 30000 条 TIME_WAIT 28232 个可用端口 → 端口耗尽这就是两者的关系TIME_WAIT 本身无害但在高频短连接场景下它会把有限的端口池占满。怎么确认是 TIME_WAIT 占满了# 看 TIME_WAIT 里有多少落在临时端口段ss-tanstate time-wait|awk{print $3}|awk-F:{print $NF}|\awk$132768 $160999|wc-l# 对比可用端口总数cat/proc/sys/net/ipv4/ip_local_port_range如果前者接近 28232就是被 TIME_WAIT 占满了。四、解法 1扩大端口范围最快sysctl-wnet.ipv4.ip_local_port_range10000 65535# 可用端口从 28232 → 55536接近翻倍持久化echonet.ipv4.ip_local_port_range 10000 65535/etc/sysctl.confsysctl-p注意不要低于 1024特权端口也不要和你自己监听的端口段重叠。这是最快见效的一招但只是把上限抬高没解决短连接太多的根因。五、解法 2开启tcp_tw_reusesysctl-wnet.ipv4.tcp_tw_reuse1作用允许内核在新建连接时复用处于 TIME_WAIT 状态的连接要求时间戳严格递增tcp_timestamps默认开启。关键点它只对客户端主动发起方生效对服务端的监听端口无效——正好符合我们的场景副作用很小是安全的一招必须tcp_timestamps1默认是 1。不要开tcp_tw_recycle它在 Linux 4.12 之后已被移除且在 NAT 环境下会大量误杀正常连接。六、解法 3根治——消灭短连接前面两招都是扩大池子真正的解法是少建连接。最常见的元凶连接池没配好Go 里// ❌ 默认 MaxIdleConnsPerHost 2高并发下疯狂建新连接client:http.Client{}// ✅ 正确配置tr:http.Transport{MaxIdleConns:1000,MaxIdleConnsPerHost:100,// 关键IdleConnTimeout:90*time.Second,}client:http.Client{Transport:tr,Timeout:10*time.Second}MaxIdleConnsPerHost默认是 2这是 Go 服务里最高频的性能坑。数据库连接池db.SetMaxOpenConns(100)db.SetMaxIdleConns(50)db.SetConnMaxLifetime(30*time.Minute)// 定期换新避免长连接僵死Redis / MQ 客户端它们默认就是长连接主要检查有没有在循环里反复新建 client// ❌ 每次请求都新建 clientfunchandle(){rdb:redis.NewClient(...)// 每次都建deferrdb.Close()rdb.Get(ctx,key)}// ✅ 全局复用varrdbredis.NewClient(...)七、还有一个场景压测机压测机是端口耗尽的重灾区因为要在短时间内建立海量连接。除了上面的配置还可以# 允许 TIME_WAIT socket 被重新用于新连接客户端sysctl-wnet.ipv4.tcp_tw_reuse1# 缩短 FIN 超时sysctl-wnet.ipv4.tcp_fin_timeout30# 默认 60# 提高 fd 上限ulimit-n100000注意tcp_fin_timeout改的是 FIN_WAIT_2 的超时不是 TIME_WAIT 的时长TIME_WAIT 的 60 秒是硬编码的改不了。很多人误以为改这个能缩短 TIME_WAIT其实不能。八、K8s 场景的额外注意如果 Pod 里出现端口耗尽除了上面这些还要考虑1. Pod 的源 IP 是节点 IPSNAT 模式那么整个节点上所有 Pod 共享同一组源端口。一个 Pod 疯狂建短连接会拖垮同节点的其他 Pod。2. conntrack 表也会同时被打满两个问题会一起出现dmesg|grep-itable full# conntrackss-tanstate time-wait|wc-l# TIME_WAIT3. 解法优先级1. 消灭短连接连接池—— 根本 2. 扩大 ip_local_port_range 3. 开 tcp_tw_reuse 4. 必要时把 Pod 分散到更多节点九、带走物一个快速判定流程# 1. 报错是 Cannot assign requested address 吗是 → 端口耗尽# 2. 看 TIME_WAIT 有多少落在临时端口段ss-tanstate time-wait|awk{print $3}|awk-F:{print $NF}|\awk$132768 $160999|wc-l# 3. 看可用端口总数cat/proc/sys/net/ipv4/ip_local_port_range# 4. 缓解sysctl-wnet.ipv4.ip_local_port_range10000 65535sysctl-wnet.ipv4.tcp_tw_reuse1# 5. 根治查连接池# Go: MaxIdleConnsPerHost默认 2# DB: SetMaxIdleConns / SetMaxOpenConns关键认知三条端口耗尽只发生在主动发起连接的一方服务端不会TIME_WAIT 占端口 60 秒所以每秒新建连接数 × 60 可用端口数就是危险的临界点tcp_fin_timeout改不了 TIME_WAIT 的 60 秒别指望它。一句话结论Cannot assign requested address就是本地临时端口用完了短期靠扩大ip_local_port_rangetcp_tw_reuse顶住长期必须靠连接池把短连接消灭掉。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →