一台机器到底能建多少连接?聊聊 TIME_WAIT 与临时端口耗尽
有一次压测,服务本身 CPU、内存都闲着,接口逻辑也没问题,可一到高并发,客户端就开始批量报错:新连接建不上,日志里一堆 “Cannot assign requested address”。第一反应是服务挂了,可服务端监控一片祥和。最后定位到,问题不在服务端,而在发起请求这一侧——本地用来建连接的端口,被自己用光了。
这件事当时挺反直觉的:一台机器不是能开很多连接吗,怎么还会不够用?这篇把"连接数受什么限制"这件事讲清楚,重点放在最容易踩坑的 TIME_WAIT 和临时端口上。
一、一条连接,靠什么来唯一标识
先建立一个基础认知:网络上的一条 TCP 连接,是由"四元组"唯一确定的——源 IP、源端口、目的 IP、目的端口。四个值定下来,这条连接就被唯一锁定了。
当你作为客户端,去连同一个服务端(目的 IP、目的端口固定),而且你自己的源 IP 也固定(比如就一块网卡一个地址),那四元组里能变的就只剩"源端口"一个。源端口一共是 0 到 65535,去掉系统保留的低端口,真正能拿来当临时端口的,往往只有两三万个。
也就是说,在"固定源 IP 连固定目标"这个最常见的前提下,你同时能持有的连接数,上限基本就是临时端口的数量。这不是服务器限流,是你自己这侧的资源天花板。
二、TIME_WAIT:关了连接,端口却还不释放
更坑的是,连接关掉之后,端口也不是立刻就能再用。
TCP 里主动关闭连接的那一方,会进入一个叫 TIME_WAIT 的状态,并且要在这里停留一段时间(长度是 2 倍的 MSL,报文最大生存时间;不同系统默认值不同,常见的是几十秒量级)。这段时间里,这个四元组被"占用"着,不能马上拿去建新连接。
设计这个状态是有道理的:一是保证最后那个确认包万一丢了还能重传,让连接干净地关掉;二是让网络上可能还漂着的旧报文彻底消散,不会串到新连接里。问题是,它占用端口这件事,在连接频繁的场景里会非常要命。
三、短连接为什么特别容易中招
把前两点连起来看,短连接的危害就清楚了。
所谓短连接,就是"来一个请求,建一次连接,用完马上关"。每一次关闭,都会在主动关闭侧留下一个 TIME_WAIT,占着一个源端口几十秒。如果请求频率很高,比如每秒几千次,那几十秒里堆积的 TIME_WAIT 数量会非常可观。
当 TIME_WAIT 占满临时端口,新连接就申请不到源端口,于是出现开头那种报错:服务好好的,可就是连不上,连不上的原因不是对端,而是自己这侧端口见底。
我们那次压测就是典型:测试程序每发一个请求就新建短连接,QPS 一上去,本地端口瞬间被 TIME_WAIT 塞满,报错铺天盖地,服务端却毫无波澜。
四、客户端和服务端,被卡住的地方不一样
前面都在说客户端这边端口不够。有人会疑惑:服务端不是也开很多连接吗,它怎么不缺端口?
区别在于身份。服务端是被动方,它监听的是一个固定端口,每来一个客户端,连接靠"客户端 IP + 客户端端口 + 服务端 IP + 服务端端口"这个四元组来区分。因为客户端的 IP 和端口千变万化,服务端这一个监听端口背后,可以挂着几十万上百万条连接,它自己并不消耗源端口。
真正容易把端口用光的,是主动发起连接的那一侧。你作为客户端,反复去连同一个目标,源端口这一项很快就到顶。所以排查"连不上"的时候方向要对:先怀疑发起方,而不是服务端。很多人一看到连接失败就去查服务端,其实瓶颈在自己的出口端口上。
五、算一笔账:你的端口到底能撑多高并发
给个直观的数字,避免凭感觉。
假设临时端口可用范围大约三万个,TIME_WAIT 持续约六十秒。这意味着:在任一时刻,最多约三万个端口处于"被占用或待释放"的状态。如果你的模式是每请求一条短连接,那每秒能稳定新建的连接数上限,大致是 30000 除以 60,约等于 500。
超过这个数,新连接就开始排队、报错。换句话说,一台看似很强的客户端机器,在纯短连接、反复连固定目标的场景下,并发天花板可能只有每秒几百次新建连接。这个数字比大多数人的直觉低得多。
一旦明白这笔账,就会理解为什么压测一拉高 QPS,连接数曲线就崩——不是机器弱,是端口这道算术题本来就不宽松。把连接复用起来,同样的端口池能撑的"有效请求量"会成倍往上翻,因为不再每请求消耗一个新建连接了。
六、几个常见的缓解思路
先说清楚一个前提:临时端口范围和 TIME_WAIT 的时长,在不同操作系统里默认值不一样,调整方式也不同。网上很多"改这两个内核参数就好"的帖子,往往是针对某个特定系统的,直接照搬未必适用,动手前先确认自己跑在什么环境上。
思路分两层:
第一层是"腾出更多端口"。把临时端口可用的范围调大,能撑的连接数就多一些。这是治标,端口总数终究有上限,流量再涨还是会碰到。
第二层是"减少 TIME_WAIT 的产生"。核心就是别那么频繁地开关连接。开启端口复用相关的能力,让处于 TIME_WAIT 的端口能被新连接安全复用,是常见做法,但同样要看系统支持和具体语义,不能无脑开。
七、根本解法:少建短连接,用长连接和连接池
前面那些都是缓解,真正能釜底抽薪的,是改变"每请求一连接"的习惯。
如果用长连接(建一次,多个请求复用同一条),或者更规范地引入连接池(一批连接来回借还),那 TIME_WAIT 的产生速度会断崖式下降——因为连接不再频繁关闭,自然也就不再频繁占端口。
这其实和我们之前聊的连接池是一回事:连接池除了省去反复建连的开销,还有一个隐性收益,就是大幅减少 TIME_WAIT 堆积,间接把"端口耗尽"这类的坑也一起填了。所以看到连接数暴涨、新连接建不上,别只盯着服务端,先看看自己是不是在拿短连接猛薅。
八、排障时先盯哪几个信号
最后给一套排查顺序,遇到"连不上但服务没挂"时直接照着看:
第一步,看本地端口的状态分布。如果处于 TIME_WAIT 的数量占了绝大多数,基本就能锁定是频繁开关连接导致的端口堆积,而不是对端拒绝。
第二步,看连接数曲线和压测(或流量高峰)是否强相关。一压就涨、一停就慢慢回落,典型是连接生命周期管理的问题;和流量无关、持续高位,更可能是别处的资源泄漏。
第三步,分清是哪一侧的瓶颈。发起方端口耗尽,报错集中在客户端、建连阶段;对端连接池打满,表现是服务端拒绝或排队。两侧的改法完全不同,别张冠李戴。
第四步,确认客户端和压测工具的建连方式。很多时候"连接数暴涨"是短连接猛打自己造出来的,换成复用连接的策略,曲线立刻平稳,根本不用动服务端。
把这几步走完,大部分端口相关的灵异故障都能在十分钟里定位到源头。
写在最后
连接数不是无限的,它会受四元组里"源端口"这一项的硬约束。短连接 + 高并发,很容易让 TIME_WAIT 把临时端口占满,表现成"服务没挂却连不上"的灵异故障。调大端口范围、开启复用能缓解,但治本是减少短连接、拥抱长连接和连接池。
排查这类问题时,先看本地端口占用和 TIME_WAIT 数量,往往比盯服务端监控更快定位。把"连接是怎么开、怎么关的"想清楚,很多压测和生产故障都能提前避开。
有问题欢迎评论区聊,看到都会回。
本文为原创技术分享,涉及的网络编程思路仅供学习与合法用途参考。请遵守相关法律法规,勿将技术用于任何违规场景。