“address already in use”在JMeter里出现频率极高基本每个做过压力测试的人都见过。第一次遇到时我刚入门看着红色报错懵了半天以为是目标服务挂了后来排查了一圈发现锅不在服务端而在压测机自己身上。这个报错翻成大白话就是发压的机器上可用端口不够了。这篇就把我从原理到解决踩过的坑一次性说清楚无论你是刚开始用JMeter的新手还是被这个报错折磨过的老打工人照着做基本都能解决。1. 先搞清楚报错到底是谁发出的1.1 四次挥手和TIME_WAIT是怎么把端口“锁死”的TCP连接建立时客户端会随机占用一个本地端口比如你用JMeter去请求目标服务的8080端口JMeter这台机器的TCP层就会在动态端口池里挑一个端口比如52018作为这次连接的“发件地址”。连接用完之后要断开正常断开需要四次挥手而在这四次挥手的最后阶段主动关闭连接的一方会进入一个叫TIME_WAIT的状态。TIME_WAIT不是说马上消失而是会等一段时间。这段等待时间在Windows上默认是240秒4分钟在Linux上通常由tcp_fin_timeout控制默认60秒。为什么要等这么久主要是怕最后一个ACK报文在网络里丢了对方收不到就会重发FIN这边如果直接关闭端口重发的FIN就没人应答会拖垮下一次连接。所以TCP协议设计了TIME_WAIT让这个端口在等待期内一直占着不允许立即复用。问题就出在这个“占着”上。JMeter高并发压测时如果请求是短连接每次请求都新建TCP连接、用完就断开那么每完成一笔事务就会有一个端口进入TIME_WAIT状态并且要在那边蹲满240秒才被释放。压测越猛被占着的端口越多直到动态端口池全部耗尽新的连接没有端口可用操作系统就会对发起方抛出一个BindException也就是你看到的“Address already in use: connect”。这里要特别提醒一句这个报错的“Address already in use”指的是本地发起连接时找不到空闲端口而不是被压测服务器上8080/80端口被占用。两者报错内容虽然都带“address already in use”但一个是“connect”阶段的客户端端口耗尽一个是“bind”阶段的服务端端口被占用排查方向完全不同。1.2 压测机端口池就这么大算算就明白了Windows上默认的动态端口范围以常见的几代系统来说一般只会开放一小段区间供应用自由使用比如从49152到65535满打满算也就一万六千多个端口。看起来很不少但在压测场景下根本不够看。假设你的压测目标是每秒完成1000个请求每次请求都是一条新连接、请求完就断开。那么同时处于TIME_WAIT状态的端口数量就等于“每秒新建连接数 × TIME_WAIT等待秒数”。按Windows默认的240秒来算只要压测稳定跑上几十秒TIME_WAIT状态的端口就会堆积到1000 × 240 240000个。而你的端口池满打满算才一万六早就被撑爆了报错是必然的。这个公式特别重要我后来调优时一直拿它做估算。只要看清这几个数基本就能判断问题严不严重每秒新建连接数可以理解为没有Keep-Alive时的TPS、TIME_WAIT时间、端口池大小。这三者的乘积关系决定了你的压测机什么时候会开始报错。大多数人在压测刚开始的前一两分钟见不到这个错就觉得没事等跑到某个时间点突然大量抛异常其实就是TIME_WAIT堆积到上限了。1.3 先分清是压测机还是被测机的问题我在企业里帮人排查过很多次发现一个很常见的现象大家一看到这个报错第一反应是去调被测服务的连接数、线程池、端口监听配置折腾半天完全没效果。这里有个简单的判断方法报错日志里如果出现的是“Connection refused”那通常是被测服务真的没起来或者监听端口不对如果出现的是“Address already in use: connect”那问题百分之九十出在压测发起方也就是JMeter所在的那台机器。还有一点容易被忽略JMeter如果是用GUI模式跑的界面本身的渲染、监听器大量写日志都会消耗资源但更关键的是GUI模式会拖慢脚本执行节奏导致连接建立和释放的规律和真实场景差异很大。我在压测时坚持用非GUI模式也就是命令行模式这样JMeter只会专心做施压端口分配也更规矩排查问题时变量也更少。2. 压测机是Windows注册表三板斧直接上2.1 把动态端口范围拉高Windows上端口池的限制可以通过注册表调整这也是网上被提到最多的解决方案。打开注册表编辑器定位到HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters找一下有没有一个叫MaxUserPort的DWORD值没有的话就新建一个类型选DWORD32位把值改成十进制的65534。这个参数决定了一个应用程序最多可以用到多大的动态端口号。改完之后还可以再用命令行确认一下动态端口池的真实区间。以管理员身份打开CMD执行netsh int ipv4 show dynamicport tcp如果显示出来的起始端口和数量比较小可以用下面这条命令把它直接拉大netsh int ipv4 set dynamicport tcp start1025 num64510这里的start是起始端口num是端口总数加起来是65535。注意这条命令设置的范围越大效果越直接但也意味着系统其他软件可用的高端口会变少不过在实际压测场景里压测机就是专门干这个活的影响可以忽略。2.2 把TIME_WAIT的等待期压短光把端口池扩大只是让报错来得晚一些真正要解决的是缩短TIME_WAIT的占用时间。还是在刚才的注册表路径下新建或修改TcpTimedWaitDelay这个DWORD值单位是秒官方允许的范围是30到300之间默认值是240。我们把它改成30意思就是让端口在TIME_WAIT状态只停留30秒释放速度快了8倍。按前面那个公式再算一次每秒新建连接数还是1000TIME_WAIT从240秒变成30秒占用的端口数就从240000降到30000。再配合端口池拉到六万五基本可以支撑到2000多的每秒新建连接数而不报错。这个数字对大多数中小型项目压测来说已经够用了。改完之后记得重启机器或者至少重启相关服务否则注册表参数不会生效。我见过有人改完不重启然后跑来问为什么还是报错十有八九是这个原因。另外TcpTimedWaitDelay的单位是秒注意填10进制而不是16进制注册表编辑器默认显示的是16进制直接填十进制数字更稳妥。2.3 网上说的“端口复用偏方”为什么不靠谱网上有不少帖子说把TcpMaxDataRetransmissions改成2就能让Windows实现端口复用这个说法流传很广但实际上是错的。TcpMaxDataRetransmissions控制的是TCP数据报文最大重传次数属于链路可靠性参数跟TIME_WAIT端口回收没有直接关系改了它不仅解决不了端口耗尽还可能让弱网环境下的重传变少影响传输稳定性我在项目里曾经被这个偏方带偏过折腾了半天不仅没见效反而多出了超时问题。Windows要比Linux简单粗暴它没有像Linux那样的tcp_tw_reuse开关可以主动回收TIME_WAIT端口本质上的优化手段就是两条扩大端口池、缩短等待时间。与其去研究那些不靠谱的偏方不如让脚本侧尽量少建连接再配合合适的压测架构这才是正路。3. 脚本侧调整让JMeter少建连接3.1 JMeter的连接模型长什么样很多JMeter使用者对“线程数”和“TCP连接数”的关系是模糊的以为线程多就是连接多其实不一定。JMeter里每个线程模拟一个虚拟用户这个虚拟用户在执行HTTP请求时如果开启了Keep-Alive那么在同一个线程的多次迭代中是可以复用同一条TCP连接的。也就是说1000个线程压测并不一定会有1000条连接同时存在连接是否新建取决于这个线程当前的请求是否已经和服务器建立了连接。理解了这个模型就能明白一个关键结论压测机的端口耗尽和“线程数”没有直接关系和“每秒新建连接数”才有直接关系。如果一个线程的每个迭代都新建连接那一秒内所有线程发起的请求次数就是每秒新建连接数如果一个线程只建立一条连接然后复用那一秒内新建的连接数最多也就是线程数不会因为请求量变大而暴涨。所以调整脚本的第一原则就是尽量让JMeter复用连接而不是频繁创建新连接。3.2 Keep-Alive和Connection: close的坑HTTP Request取样器里有一个“Use KeepAlive”选项默认是勾选的正常情况下JMeter会尽量复用连接。但这里有个坑就算你在JMeter这边勾选了Keep-Alive如果被测服务返回的响应头里带了Connection: close那这条连接用完之后照样会被关闭JMeter也没办法硬复用。这种情况下你去改脚本是没用的必须让服务端把响应头改成Connection: keep-alive或者确认服务端的连接策略确实支持长连接。还有一种情况是服务端配置了空闲超时比如Tomcat的keepAliveTimeout设置比较短连接空闲一会儿就被服务端主动关闭了客户端感知后会在下一次请求时重新建连。这样即使整体看Keep-Alive是开着的实际每秒新建连接数依然很高。我排查时习惯在压测过程中抓包或者直接看服务端连接数曲线确认TCP连接是不是在频繁建立和销毁这一步比猜配置要靠谱得多。3.3 线程数、Ramp-Up与新建连接速率另一个容易踩的坑是Ramp-Up时间设置得过短。很多新手习惯把Ramp-Up设成0意思是一秒内所有线程全部启动。线程一启动就会立刻抢占系统资源、抢建连接一瞬间的把所有线程所需的连接全都塞到TCP层去端口池一下子就满了。我一般建议把Ramp-Up时间设置成一个合理的值让线程平缓建立。比如500个线程Ramp-Up设为50到100秒每秒新增5到10个线程这样端口分配是平缓的压测曲线也不会出现开头一个大尖峰。合理控制线程启动节奏之后我们看到TIME_WAIT的堆积速度明显下降大妈大妈式的烫手山芋现象很少再现。但也要提醒Ramp-Up只是缓兵之计并不能根治端口耗尽。它的核心价值是让压测曲线更接近真实用户进入系统的节奏同时避开瞬时端口高峰真正决定上限的还是每秒新建连接数和端口池的大小关系。4. 一台压测机不够就上分布式压测4.1 为什么分布式能缓解端口耗尽单台压测机的端口池再大也有上限。Windows上即使把动态端口拉到六万五TIME_WAIT压到30秒理论上限也就每秒两千多新连接这对于一些高并发、短连接的服务来说还是远远不够。这时候就需要换一个思路不在一棵树上吊死把压力分散到多台压测机上去。JMeter的分布式压测模型是典型的master-slave结构。一台JMeter作为调度端不下发请求流量另几台机器运行jmeter-server作为实际施压端。每一台slave都有自己独立的动态端口池端口资源等于翻了几倍。比如你有4台压测机相当于就有4个端口池理论支撑的每秒新建连接数可以到八千甚至更高这在日常项目里已经非常够用。4.2 一套可以直接套用的分布式压测方案搭建分布式没有网上说的那么玄乎基本都是这几步。先准备一台调度机和几台压力机压力机上装好Java和JMeter保证版本一致特别是JMeter的大版本最好统一否则脚本运行经常出怪问题。压力机上启动jmeter-server默认监听的端口是1099启动命令jmeter-server -Djava.rmi.server.hostname压力机IP然后回到调度机的JMeter配置里找到bin目录下的jmeter.properties找到remote_hosts这个配置项把压力机的IP和端口填进去多个压力机用逗号分隔remote_hosts192.168.1.10:1099,192.168.1.11:1099保存后重启JMeter在GUI模式下点“远程启动”就能看到多台压力机开始配合干活了。实际压测时我不用GUI直接命令行jmeter -n -t script.jmx -r -l result.jtl这里的-r就是启动所有配置在remote_hosts里的远程压力机。每次跑完记得看聚合报告里有没有连接到哪一台压力机的异常如果某台机器总在报错单独去掉那台重跑就能验证是不是那台压测机的系统参数没调好。4.3 更彻底的做法把压测机放到Linux上如果条件允许最省心的方案是把压测机的操作系统换成Linux。Linux的TCP协议栈比Windows更开放端口池默认也更大而且支持真正意义上的TIME_WAIT复用。用sysctl命令可以把系统参数调整成非常适合压测的状态sysctl -w net.ipv4.ip_local_port_range1024 65535 sysctl -w net.ipv4.tcp_fin_timeout30 sysctl -w net.ipv4.tcp_tw_reuse1这个tcp_tw_reuse是真正的端口复用允许新连接安全地使用TIME_WAIT状态的端口这是Windows没有的能力。不过要注意tcp_tw_reuse依赖TCP时间戳功能如果内核里net.ipv4.tcp_timestamps被关掉了复用是起不来的正常云主机默认都是开的。参数改完记得sysctl -p让它生效。我在实际项目里给一个模拟的网关接口做过压测单台Windows上短连接跑到每秒3000左右就开始零星报这个错换了Linux压测机之后同样的脚本直接拉到每秒5000还很稳连操作系统都不用重启。所以如果你的压测规模经常超过每秒两三千新连接别犹豫直接上Linux省掉后面所有折腾。5. 排查思路与高频问题速查5.1 从报错日志到根因定位的完整流程遇到“address already in use”我建议按这个顺序排查。先看报错到底出现在哪个环节打开JMeter的查看结果树过滤出报错的样本看异常类型是java.net.BindException: Address already in use: connect还是Connection refused这个动作一分钟就能做不要跳过去直接改配置。确认是客户端端口问题之后登录压测机敲一条命令统计当前TIME_WAIT状态的连接数netstat -an | findstr TIME_WAIT | find /c TIME_WAITWindows的find /c是计数的意思统计出来的数字如果已经接近或超过一万基本可以断定是端口池被TIME_WAIT塞满了。用netstat -ano还能看到这些TIME_WAIT连接是被哪个PID占用的配合任务管理器确认是不是JMeter的Java进程。接下来回到脚本统计一下当前压测场景的TPS再结合压测机上动态端口池大小和TIME_WAIT秒数用前面那个公式算一遍确认是不是入口容量不够。如果算出来理论上够但还是报错那就检查是不是被测服务主动关闭了连接导致实际新建连接数远超预期。这一步我通常是直接抓包或者在被测服务端看连接曲线非常直观。5.2 高频问题速查表症状可能原因解决思路压测跑了几分钟后开始报address already in useTIME_WAIT堆积端口池耗尽拉高动态端口范围缩短TcpTimedWaitDelay一开始压测就报address already in useRamp-Up太短线程瞬间抢连接增大Ramp-Up平缓启动线程脚本勾了Keep-Alive但仍然大量新连接服务端返回Connection: close调整服务端连接策略确认响应头单台压测机每秒新建连接数超过上限端口池物理容量不足上分布式压测或者换Linux压测机修改注册表后还是报错没重启系统或者改的不是Tcpip参数路径确认路径正确重启后再验证JMeter报错但服务端看很空闲压测机自己端口瓶颈不是服务端问题把排查重点从服务端移到压测机这张表我贴了不少次基本覆盖了大家遇到的高频情况。如果你不在表里大概率是环境特殊比如压测机和被测服务之间还有一层负载均衡连接被负载均衡主动断开这种就需要去调中间链路层的参数而不是纠结JMeter。5.3 压测前的五分钟检查清单最后分享一个习惯我每次压测前都会花五分钟过一遍这个清单基本能提前挡掉八成以上的端口类问题。第一确认压测机是不是专用的不要在你自己办公用的电脑上压测随便装个软件、跑个浏览器都在抢端口。第二确认动态端口范围Windows上跑一条netsh int ipv4 show dynamicport tcpLinux上跑sysctl net.ipv4.ip_local_port_range看一眼大小。第三确认脚本里HTTP请求的连接策略是打算走Keep-Alive还是短连接短连接场景要提前估算每秒新建连接数。第四确认压测机的TIME_WAIT秒数Windows看注册表Linux看net.ipv4.tcp_fin_timeout。第五想清楚单台机器够不够不够就直接规划分布式压测别等跑挂了再拆。这套检查看起来琐碎但养成习惯之后你会发现自己排查“address already in use”的速度快很多不会再被一个报错卡住半天。我记得自己在某个模拟项目上第一次完整按这个流程走了一遍从看结果树、数TIME_WAIT、改注册表、重启、重跑到最终压测顺利完成前后不到半小时而之前靠瞎试配置浪费了整整一个下午。希望这篇能帮你把之前浪费掉的下午省回来。