尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

JMeter压测异常排查全攻略:从报错到定位瓶颈的实战指南

JMeter压测异常排查全攻略:从报错到定位瓶颈的实战指南 做性能测试最怕的不是脚本跑不起来而是跑起来之后报错信息刷屏一屏的红色堆栈看着头大。以前我带新人做压测遇到jmeter报异常十有八九第一反应都是“脚本是不是写错了”但实际排查下来真正是脚本写错的情况不到一半。连接被断开、服务器拒绝服务、线程池耗尽、内存溢出、断言失败每一种异常的根因都不一样处理方式也完全不同。这篇内容就是围绕jmeter性能测试里的异常分析来写的。我会把平时压测中最常见的异常报错、它们的产生原因、排查思路、处理办法全部梳理一遍也会以一个100并发的压测场景为例完整走一遍从脚本搭建、异常定位到参数调整的流程。如果你正在用jmeter做接口测试或者压测看完这篇应该能少踩不少坑。1. 性能测试里的异常先分清责任面再动手1.1 异常不等于脚本写错先划分四个责任面遇到异常时我习惯先做一个分类把问题放到四个责任面里去判断脚本自身逻辑、压测机资源、被压服务状态、中间网络链路。这四个层面都可能导致jmeter报错但表现一样根因可能千差万别。脚本层面的问题最直观比如参数化数据冲突、请求头缺失、断言条件写死导致误报。这类问题通常在并发量很低的时候就会暴露。压测机资源问题是很多人忽略的jmeter本身是Java应用默认堆内存才1G并发线程拉高之后压测机自己先挂了这时候产生的一堆超时异常根本不是被测系统的问题。被压服务的问题才是我们真正想找的比如接口处理不过来、数据库连接池满了、慢SQL拖垮了应用。中间链路的问题则包括防火墙拦截、负载均衡策略、DNS解析超时等。这四个层面的排查顺序很有讲究。我的习惯是先从压测机自身开始查再看脚本逻辑然后看中间链路最后看被压服务。原因很简单压测机和脚本是我们自己可控的查起来最快排除掉这两块之后剩下的问题大概率就在被测系统那边了。1.2 性能测试的目的不是追求零异常这里想多说一句性能测试的目标不是让脚本干干净净一条报错都没有而是通过异常现象去发现系统的性能瓶颈。只要并发压力超过了某个阈值服务端开始拒绝连接、超时、返回5xx这恰恰说明我们找到了系统的能力边界。所以看jmeter结果的时候不要一看到错误率就慌。错误率0%不代表系统没问题可能是压力根本没打上去错误率10%也不代表系统就不行得看这个错误是集中在哪个阶段、哪类接口、什么类型的报错。带着这个心态去分析异常思路会清晰很多。2. 常见异常信息拆解看到报错别慌先判断这几种2.1 连接类异常Connection refused 和 timeout 要分清jmeter里出现频率最高的异常基本都集中在连接层面。最典型的就是java.net.ConnectException: Connection refused这个报错的意思是目标机器的端口根本连不上要么服务没启动要么端口不对要么防火墙把包拦了。还有一个容易被忽略的情况就是服务端的并发连接数到了上限新连接直接被内核拒绝。另一种是java.net.SocketTimeoutException: connect timed out这个和refused完全不同。refused是“对方拒绝我”timeout是“我怎么等都等不到响应”说明网络链路不通或者服务器负载太高根本来不及处理连接请求。排查时先ping一下再telnet一下端口分分钟就能判断是网络问题还是服务问题。还有java.net.SocketException: Connection reset这个报错经常出现在压测中段特点是先跑得好好的突然大量报错。这种情况大多是服务端主动断开了连接常见的背后原因有后端线程池满了直接把请求丢弃、网关设置了空闲超时、服务端程序里主动关闭了连接。出现这个报错时优先去看服务端的线程池配置和访问日志。2.2 响应内容类异常状态码和断言结果要分开看响应层面的异常分两种。一种是HTTP状态码异常比如500、502、503、504。要注意的是不同状态码背后的瓶颈位置不一样502通常出现在网关和后端服务之间503是服务不可用504是网关超时500就得去翻应用日志看具体异常堆栈。另一种是状态码200但断言失败这种最坑。服务端明明返回了正常响应但内容不对比如返回了登录页而不是接口数据或者返回的数据是空值。遇到断言失败我的习惯是先在察看结果树里对比正常请求和异常请求的完整响应体很多情况下“参数化数据重复”造成的业务异常就是这么暴露出来的。比如接口要求手机号唯一但CSV参数化文件里只有100条数据200个线程在跑后面的线程自然拿不到合法数据接口返回“手机号已存在”断言必然失败。还有一个高频问题就是响应乱码。jmeter默认按ISO-8859-1解析响应内容如果接口返回的是UTF-8结果树里就是一堆乱码如果断言中文关键词必然失败。解决方式是在请求里添加HTTP信息头管理器指定Content-Type的charsetutf-8或者修改jmeter的配置文件jmeter.properties里的sampleresult.default.encodingUTF-8。2.3 压测机自身资源异常堆内存和线程数限制压测机自身的异常最经典的就是java.lang.OutOfMemoryError: Java heap space。jmeter默认的堆内存只有256MB到1G在高并发下如果开启了结果树、保存了大量响应数据内存很快就会被耗尽。解决办法是修改bin目录下的jmeter启动脚本调整HEAP参数比如HEAP-Xms4g -Xmx4g。我一般压测时至少给到4G如果机器内存充足给8G也可以。另一个是java.lang.OutOfMemoryError: unable to create new native thread这个表示操作系统的线程数到了上限常见于单台压测机线程数拉得特别高的情况。Linux下可以用ulimit -u查看用户进程数限制调大它或者减少jmeter的线程数改用多台机器做分布式压测。还有跟热词里完全对上的一个报错java.io.IOException: Error writing to server。这个报错我在压测上传类接口时遇到过一般是因为请求体太大、连接被服务端关闭或者发送请求的过程中TCP连接出现了异常。排查时先看是不是单个大文件上传失败如果是检查服务端的上传大小限制如果是普通请求偶发出现重点看连接是否被服务端提前关闭。2.4 SSL证书与HTTPS录制相关异常HTTPS压测相关的报错也很常见比如javax.net.ssl.SSLHandshakeException基本就是证书信任问题。jmeter压测HTTPS接口时要么把目标服务的证书导入jmeter的cacert证书库要么使用HTTP请求里的“use HTTP client 4”并关闭证书验证。还有一个非常容易踩的坑是用jmeter录制HTTPS脚本时需要先安装jmeter自带的ApacheJMeterTemporaryRootCA证书然后浏览器设置代理走127.0.0.1:8888才能正常录制。录制出来的脚本如果直接拿去压测经常会因为代理设置残留导致请求走代理失败所以录制完一定要把浏览器代理关掉。3. 异常排查链路结果树、jmeter.log、系统监控三件套3.1 结果树怎么看别开全量保存学会抽样要定位具体是哪一类请求报错首先要看察看结果树。但这里有个很关键的注意事项压测时千万不要在结果树里开启“全量保存所有响应”。我见过不少人在GUI模式下直接压测结果树里的响应数据越积越多jmeter越来越卡最后整个进程内存溢出报了一堆莫名其妙的异常。我自己的做法是在测试计划里给察看结果树配置“仅记录错误”或者限制最大保存条数。这样跑完之后打开结果树只会看到报错的请求方便集中定位。如果确实需要分析正常请求和错误请求的差异可以用“保存响应数据”功能把部分样本写入jtl文件再通过脚本去对比。排查的时候先看错误请求的请求体再对比响应体。如果请求体里参数化数据是乱的问题大概率在参数化配置如果请求体正常但响应体报业务错误那问题在被测服务。3.2 jmeter.log 才是定位异常的真正突破口结果树里显示的信息其实有限真正完整的信息在jmeter.log里。运行jmeter时log文件会记录完整的Java异常堆栈包含异常类型、具体抛出位置、导致异常的原因链。我在排查Error writing to server这类IO异常时基本都靠jmeter.log里的堆栈来定位。比如有一次我遇到响应报错结果树里只显示“Non HTTP response code: java.net.SocketException”非常笼统但是打开jmeter.log发现后面还跟了一串Caused by: java.net.SocketException: Connection reset by peer这就说明连接是被对端重置的问题在服务端或者中间设备。有一种情况要注意jmeter.log默认是INFO级别有些底层的DEBUG信息不会显示。如果遇到特别难排查的问题可以临时把log级别改成DEBUG跑一个低并发短时间的测试拿到完整日志后再改回来。3.3 监控数据压测机和被测服务必须同时看这一步是很多新手最容易跳过的。判断异常根源时压测机和被测服务器的资源监控数据缺一不可。我自己压测时会同时开三个监控视角压测机的CPU和内存、被测服务器的CPU和内存、数据库的活跃连接数和慢查询。这里分享一个非常实用的判断逻辑如果压测机已经CPU满了那jmeter发出的请求本身就出现严重延迟此时的异常结果全部作废必须先给压测机减负如果压测机没问题但被测服务器CPU满了异常就是被测系统处理能力到达上限这时候的报错正是性能瓶颈的直接体现还有一种情况是压测机和服务器都很闲但异常率仍然高这时候别犹豫去查中间链路比如网闸、防火墙、负载均衡的连接数限制。4. 实操100并发场景下的异常定位与参数调整4.1 搭建一个最基础的100并发测试计划拿一个最常见的场景来说模拟100个用户并发请求一个登录接口然后查看系统表现。先把线程组配置好线程数100Ramp-Up Period建议设成20秒到60秒循环次数可以先设2次。很多人把Ramp-Up设成0意思是一瞬间100个线程同时发起请求这样做的结果往往是压测机先把服务端打成503异常率100%但你根本分不清这个异常是因为服务器真的承受不了还是因为瞬间请求风暴导致的现象。真实的用户不会在完全同一秒操作所以Ramp-Up要留时间。在线程组下面添加HTTP请求默认值把协议、服务器地址、端口填好这样后面所有请求不用重复写域名。然后添加HTTP信息头管理器按接口要求填写Content-Type。有接口鉴权的话还要加HTTP Cookie管理器或者用正则表达式提取器从登录接口的响应里提取token。最后添加聚合报告、图形结果、察看结果树三个监听器其中察看结果树设置为仅记录错误。4.2 参数化数据的坑CSV配置的正确姿势100个并发用户如果都拿同一份用户数据登录后端的账号状态会全乱。所以登录接口压测一般要对用户名密码做参数化。用CSV数据集配置时最容易踩的坑有三个文件路径编码不对导致中文乱码文件里的分隔符和jmeter设置的分隔符不一致导致取值错误多个线程组同时引用同一个参数文件导致数据竞争。具体操作时在CSV数据集配置里文件编码选UTF-8分隔符用英文逗号变量名称按CSV文件里的列顺序填好。还有一个容易被忽略的设置是“Recycle on EOF”和“Stop thread on EOF”。如果100个并发用户和CSV文件行数不匹配行数少的会自动循环重复导致同一个手机号被多个线程同时使用。如果接口对数据有唯一性要求建议用JMeter函数__counter生成动态用户或者时间戳拼接比如${__time(yyyyMMddHHmmss)}加上线程号保证每次请求的数据唯一。4.3 超时参数这组配置影响异常判断的准确性HTTP请求设置里有一个“Timeout”区域包含连接超时和响应超时。这两个值默认是空的也就是jmeter会一直等直到操作系统级别的超时才报错。这是一个严重的问题如果服务器卡住了jmeter每个请求可能要等上几分钟才报错整个压测时间被无限拉长而且超时异常爆发的时间点严重失真。建议连接超时设为2000毫秒到5000毫秒响应超时按接口的性能目标来设比如业务要求接口2秒内返回那响应超时就设5000毫秒超过5秒的一律算异常。这样报出来的超时异常就有明确的业务含义而不是机器层面的随机行为。另外HTTP请求的“实现方式”选择HttpClient4相比Java原生实现在高并发下连接管理更稳定异常率也更低。4.4 跑完之后先看什么异常的时间规律比总数更重要压测跑完之后打开聚合报告看错误率但更关键的是打开图形结果看异常的走势。我一般关注三个规律异常是全程都有还是某个时间点之后才爆发异常是均匀分布还是集中在某一段错误率上升的同时吞吐量是在上升还是下降。以100并发为例如果前面50秒错误率很低50秒之后开始飙升那基本是服务端资源被逐步消耗殆尽比如连接池被占满、内存逐渐涨到顶点。如果错误率从一开始就很高那问题多半在脚本或者环境配置。还有一种典型情况是错误率波动剧烈一段时间好一段时间差这往往和负载均衡的策略有关比如轮询到某台配置较低的机器时异常就出现了。顺着这几个规律去追能在很短时间内锁定问题所在的组件。5. 异常速查表日常压测最容易踩的坑现象报错信息示例常见根因处理方案压测一段时间后全部超时SocketTimeoutException: read timed out服务端线程池耗尽请求排队查看服务端线程池配置调大或优化接口逻辑请求完全连不上ConnectException: Connection refused服务未启动、端口错误、防火墙拦截telnet端口确认检查服务状态和防火墙规则连接被重置SocketException: Connection reset服务端主动断开网关超时或线程池满查看服务端访问日志确认断开原因返回200但断言失败Assertion failed参数化数据重复、响应体不是预期业务结果对比正常响应体检查参数化数据是否冲突响应中文乱码响应数据为乱码编码不匹配jmeter默认ISO-8859-1信息头指定charsetutf-8或修改sampleresult.default.encoding压测机内存溢出OutOfMemoryError: Java heap spacejmeter堆内存不足保存了过多响应数据调大HEAP参数关闭结果树全量保存压测机无法创建线程OutOfMemoryError: unable to create new native thread系统线程数上限或内存不足调大ulimit限制或改用分布式压测发送请求时报IO错误IOException: Error writing to server连接被关闭、请求体过大、TCP链路异常检查服务端上传限制确认连接稳定性SSL握手失败SSLHandshakeException证书不受信任导入证书或关闭证书验证并发不高但错误率高各种连接超时、服务错误混合脚本里存在依赖查询每个请求都查数据库优化脚本逻辑考虑缓存或前置准备数据表格里的每一类问题我都亲自遇到过。其中Error writing to server是我个人认为最难定位的一类因为它的报错信息和真实根因之间的关联比较弱必须结合jmeter.log和服务端日志一起看才查得出来。这类问题一旦出现我建议先把并发降下来确认是不是只有高并发时才触发再逐层排除。6. 异常背后是瓶颈看懂聚合报告才能继续优化6.1 聚合报告的核心指标别说只看平均值异常排查到一定程度接下来要做的是把异常和性能指标联系起来判断系统的真实瓶颈。聚合报告里有几个核心指标需要重点看Samples表示发出的请求总数Average是平均响应时间Error%是错误率Throughput是每秒事务数。还有一个很容易被忽视的指标是90% Line也就是90%的请求都在这个时间以内完成。为什么说不能只看平均值举个例子一个接口压测100次99次都是100毫秒返回有1次卡了10秒平均值是约200毫秒看起来挺健康。但真实的用户体验是99%的用户很流畅1%的用户卡了10秒。这时候看90% Line甚至99% Line会明显偏高才能暴露波动问题。中位数和90%线的差值越大说明响应时间越不稳定系统可能存在阻塞或排队现象。6.2 错误率、TPS、资源使用率三者的联动分析异常率不是孤立看的要把错误率和TPS以及服务器资源使用率结合来看。组合起来基本能判断出几种典型瓶颈错误率上升且TPS掉了一半但服务器CPU还没跑满多半是锁竞争或者数据库连接池满了请求在等待资源。错误率上升但TPS维持不降说明服务端还在努力处理但已经开始抛异常了常见原因是数据库慢查询拖垮了部分请求或者数据校验失败。错误率上升且服务器CPU已经100%这是典型的计算资源瓶颈要么是接口逻辑太耗CPU要么是死循环要么是磁盘IO异常导致的极端情况。错误率不高但TPS在某一数值后增长缓慢无论怎么加并发都上不去这就是系统的吞吐量天花板到了可能是应用线程池已经打满也可能是单机网络带宽到了上限。这个时候继续加并发没有意义要考虑的是优化代码或者做水平扩展。6.3 顺着异常方向去找优化点定位到瓶颈之后优化方向一般就清晰了。如果异常集中在某个接口先去看接口内部逻辑慢SQL加索引、循环调数据库改成批量查询、串行调用改成并行调用这三个手段在大多数业务接口里见效最快。如果异常是整体性的比如所有接口在某个并发区间集体超时那就去看中间件的线程数配置、Tomcat的maxThreads、数据库的连接池大小按照一个经验公式来调整数据库连接池大小建议设置为((核心线程数 * 2) 有效磁盘数)Tomcat线程数可以先从200开始逐档上调配合压测结果验证。还有一个细节我觉得值得分享压测发现的问题一定要记录到团队的性能基线文档里。同一个系统改个版本之后性能可能完全变样。有基线数据在下次再做压测的时候错误率是变高了还是变低了一对比就知道不需要每次从头开始摸索。我在实际工作中每轮压测都会把测试脚本、jtl文件、监控截图、异常分析结论存到一个固定目录下这个习惯帮我省了很多重复排查的时间。最后再分享一个经验jmeter报异常的时候很多人下意识地先去搜“某某异常怎么解决”然后看到一堆千篇一律的回答最后问题还没解决。我更建议的做法是给自己列一个排查清单——压测机的情况、脚本的配置、网络的连通性、服务端的指标一项一项对照排除。90%的异常问题都能在四层定位法里找到答案。压测这件事脚本谁都能跑真正拉开差距的是遇到异常时的分析思路。希望这篇总结能让你下次排查异常的时候少走点弯路。
返回列表