资讯动态

JMeter压测实战指南:从脚本设计到全链路性能瓶颈排查

发布时间:2026/9/30 10:18:26 来源:尧图企业网站定制
1. 项目概述压测这事到底在解决什么问题做后端开发或者系统运维的朋友应该都躲不开一个场景新服务上线前、架构调整后、大促活动前总有人跑来问你一句“系统能扛住多少并发”。其实这个问题本身问得就不太严谨但大家在日常沟通里习惯了这么表达。真正落到技术层面我们需要回答的是在给定硬件资源、网络环境和数据规模下系统的吞吐量上限是多少平均响应时间和错误率在什么水平以及瓶颈到底出现在哪里。我最早接触JMeter是很多年前在一个电商项目中当时线上系统频繁出现接口超时运维和开发互相甩锅后来我花了一周时间把核心链路全部梳理了一遍用JMeter搭了一套完整的压测方案才把问题定位清楚数据库连接池配置太小导致高并发下大量线程在等待获取连接。那次经历让我意识到压测这个动作本身不难难的是怎么设计出一套能真实反映线上问题的压测脚本和方案。这套博客不是官方的JMeter用户手册翻译而是我从实际项目里摸爬滚打总结出来的经验。内容覆盖了环境安装、脚本设计、参数化、断言、文件上传、安全证书处理、复杂场景下的关联取值以及一套完整的微服务迁移验证方案。不管你是第一次接触JMeter的新手还是已经在项目里用它做过几轮压测但总觉得差点意思的进阶用户这篇文章都能给你一些参考。2. 压测方案设计先想清楚再动手远比你想象的更重要2.1 压测之前必须明确的三个问题很多人拿到JMeter就直接开压测完拿个报告就完事这是典型的“为了压测而压测”。真正有参考价值的压测在点击“启动”按钮之前需要先明确三个问题。第一压测目标是什么。是验证新上线的服务能否承载预期的业务量还是排查现有系统的性能瓶颈又或者是为容量规划提供数据支撑目标不同脚本设计思路完全不同。如果是验证类的压测只需要模拟预期的并发规模和业务模型即可如果是排查瓶颈类的压测就需要逐步加压观察系统在不同并发下的表现曲线找到性能拐点。第二业务模型是否清晰。一个真实的业务系统接口的调用比例绝不是平均分布的。比如一个电商系统浏览商品和提交订单的调用频率可能差一个数量级。如果压测脚本里所有接口都跑相同的并发数出来的结果没有任何参考价值。正确的做法是根据线上日志统计各接口的真实调用比例然后在线程组里通过“吞吐量控制器”或者“加权开关”来实现业务模型的比例分配。第三监测手段是否到位。压测只是施压的手段真正的核心在于观察系统在压力下的表现。除了JMeter自身能提供的响应时间、吞吐量、错误率这些指标之外还需要配合被压测端的监控CPU、内存、磁盘IO、网络带宽、JVM堆内内存、GC频率、数据库连接池使用率等等。没有这些底层监控数据你只知道系统“挂了”但不知道为什么挂。2.2 压测环境怎么选直接决定测试结果可信度环境问题是我见过最容易踩坑的地方。很多人图省事直接在本地开发环境压测或者把压测机和被测服务部署在同一台物理机上这样测出来的数据基本没有参考价值。先说环境隔离。压测机和被测服务之间中间不能隔着限流网关、负载均衡器的带宽瓶颈。生产环境一般会有SLBServer Load Balancer或Nginx做流量分发压测时这些组件本身也会成为瓶颈这其实是合理的因为我们本来就要验证全链路的承载能力。但压测机的性能和网络带宽一定要足够不能压测机自己先被压垮了那就分不清是测试工具的问题还是被测系统的问题了。再说数据准备。压测数据要尽量贴近生产环境。比如用户ID、商品ID、订单号这些参数必须是从生产库脱敏出来的真实数据集合或者按照生产数据的分布特征构造的仿真数据。很多人在压测时偷懒所有请求都用同一个用户ID结果压测过程中触发了系统的登录态互踢逻辑导致大量请求失败测试结果完全失真。提示压测数据量也是有讲究的。如果系统里有缓存层压测数据量太小会导致缓存命中率极高测出来的性能远超真实水平反过来数据量过大导致缓存全部失效结果又会偏差很大。通常建议压测数据量至少是接口可能访问到的数据量的10倍以上避免“热数据”集中导致的误判。2.3 为什么要优先选择JMeter而不是自己写脚本可能有人会问用Python写个多线程脚本也能造并发请求为什么非要学JMeter这个问题我确实认真想过。早期我也用过Python的requests库配合ThreadPoolExecutor写过压测脚本优点是灵活想怎么控制逻辑就怎么控制但缺点也很明显每次改并发数要改代码测试报告要自己写代码统计测试结果要自己画图表投入产出比太低。JMeter的核心优势在于“开箱即用”的完整链路线程组控制并发模型、Sampler发送请求、监听器收集结果、断言器自动校验响应。再加上它对HTTP、HTTPS、JDBC、JMS、WebService等主流协议的原生支持大多数压测场景不需要写一行代码就能完成。对于需要动态计算参数的复杂场景JMeter也提供了BeanShell和JSR223Groovy脚本支持灵活性不比手写代码差。另外JMeter本身就是Java写成的天然跨平台。Windows上开发完的脚本直接拷到Linux压测机上就能运行配合命令行模式还能实现分布式压测这些都是手写脚本要花大量时间才能实现的。3. 从安装到第一个压测脚本打通JMeter的基础操作链路3.1 JMeter安装的版本选择与系统配置JMeter的安装本身不复杂但有一些细节不注意就会在日常使用中非常难受。到官网jmeter.apache.org下载安装包时注意选择二进制版本apache-jmeter-xxx.zip或.tgz不要下载源码包。JMeter 5.x系列是目前最稳定的版本它要求JDK版本在8以上。如果你本地同时装了多个JDK版本建议给JMeter单独配置JAVA_HOME环境变量避免因为JDK版本太新导致某些插件出现兼容性问题。解压之后有个细节很多人不注意不要放在有空格和中文的目录路径下。我曾经遇到过脚本文件能正常打开但执行时莫名奇妙报找不到类的错误排查了半天才发现是路径中含有中文字符导致的编码问题。JMeter默认的JVM堆内存只有512MB跑大脚本时经常报OutOfMemoryError需要修改bin目录下的jmeter.bat或jmeter.sh脚本把HEAP参数调大。我个人经验压测脚本不复杂的话1GB到2GB足够如果是大规模分布式压测压测机本身的内存要预留充足否则光GC就能把压测机拖垮。3.2 第一个压测脚本的五个标准步骤JMeter压测脚本的搭建有一套固定套路掌握了这套套路绝大多数接口压测都能套用。第一步创建线程组。右键点击“测试计划”添加“线程组”这里配置的是并发模型的核心参数线程数代表模拟的用户数Ramp-Up Period代表启动全部线程所用的时间循环次数代表每个线程发多少次请求。一个常见的误区是把线程数等同于QPS实际上线程数和QPS之间是通过响应时间关联的QPS约等于线程数除以平均响应时间。因此当你增加线程数时如果系统处理不过来响应时间会上升QPS不一定随之线性增长。第二步添加配置元件。最常用的是“HTTP请求默认值”和“HTTP信息头管理器”。把所有请求共用的协议、服务器地址、端口号和公共Header放在这里后续每个HTTP请求Sampler里只需要填写各自的路径和参数即可。这样设计的好处是后续切换压测环境时只需要改一处配置不用逐个修改Sampler。第三步添加Sampler。对于HTTP接口测试右键线程组添加“HTTP请求”即可。填好协议、服务器名称或IP、端口、请求方法GET/POST等和请求参数。对于POST请求如果Content-Type是application/json需要在“请求体”中输入JSON字符串。第四步添加监听器。在调试阶段用“查看结果树”最直观可以在里面看到每个请求的完整请求数据和响应数据。正式压测时建议用“聚合报告”和“用表格查看结果”这两个监听器提供的数据比较简洁聚合报告里有平均响应时间、中位数、90%响应时间、最小最大响应时间、吞吐量、错误率等核心指标。第五步运行。在调试脚本阶段建议把线程数设为1、循环次数设为1先跑通整个请求链路确认参数传递正确无误后再放大并发配置。3.3 监听器里的指标到底该怎么看看过很多新人拿着聚合报告问我“响应时间多少多少”但你要问他吞吐量多少、错误率多少、90%响应时间多少就答不上来了。本质上判断压测结果不能只看平均值。聚合报告里有几个指标值得特别关注。错误率是最基础的指标只要不为0就说明系统在压力下产生了异常请求需要先排查。但要注意JMeter默认的超时时间是无限等这会导致一些安全因素上的问题某个请求长期挂起线程就一直占着不释放后续请求被阻塞。建议在“HTTP请求”Sampler的“高级”标签页里设置合理的超时时间连接超时建议2000ms到3000ms响应超时建议5000ms到10000ms。吞吐量Throughput是另一个关键指标它表示服务器每分钟能处理的请求数。这个指标和响应时间是一对矛盾体并发数增大时吞吐量会先上升到达一个峰值后随着响应时间的急剧增大吞吐量反而会下降。这个峰值对应的并发数就是系统的“最佳工作点”。90% Line指标表示90%的请求响应时间都小于这个值相比平均值它能更好地反映系统的整体稳定性。如果平均值很低但90% Line很高说明有部分请求响应特别慢需要考虑是不是存在慢SQL或者锁竞争的问题。3.4 命令行模式是真正的实战模式很多人用JMeter都是用GUI模式跑压测这在调试脚本时没问题但正式压测时强烈建议改用命令行模式。GUI模式本身会消耗大量内存和CPU资源高并发时JMeter自身先成为瓶颈而且GUI模式压测时很容易因为操作界面卡顿而产生误操作。命令行模式的使用非常简单jmeter -n -t test.jmx -l result.jtl -e -o report/这条命令的参数含义分别是-n表示非GUI模式运行-t指定测试脚本路径-l指定原始结果输出文件的路径-e表示测试结束后生成HTML报告-o指定HTML报告的输出目录。生成的HTML报告非常详尽包含请求统计、响应时间分布、吞吐量变化趋势、错误信息明细等。注意-o参数指定的目录必须是空目录否则JMeter会直接报错。这个设计是为了防止报告文件被覆盖造成数据混淆。4. 参数化与关联让压测脚本真正模拟真实业务行为4.1 CSV参数化最基础也最实用的数据驱动方案压测时如果所有请求都使用相同的参数数据不仅测出来的结果失真还可能在压测过程中因为数据冲突触发业务层的去重逻辑导致大量请求以业务异常的方式失败。最常见的参数化方案是使用CSV文件。先准备好测试数据文件例如一份用户信息表包含user_id和token两列。然后在JMeter中添加“CSV数据文件设置”配置元件填写文件路径、变量名称多个变量用逗号分隔、文件编码等配置项。在线程组的循环设置中跑完CSV文件里所有行之后可以配置成继续循环读取或停止通常建议压测时选择“继续循环”保证压力持续。CSV参数化的精髓在于线程模式和文件模式的选择。默认的“共享模式”是所有线程共用一个文件指针顺序读取适合大部分场景。但对于需要每个线程独立使用专属数据集的场景比如每个线程模拟一个独立用户的一系列操作则需要设置为“线程独有的当前线程”这样每个线程会从CSV文件的不同位置开始读取避免数据被多个线程共享造成业务状态互相干扰。4.2 JDBC参数化从数据库动态获取真实业务数据CSV适用于数据集合相对固定的场景但有些业务场景数据是动态变化的用CSV文件难以模拟。例如压测一个订单查询接口要求查询的订单属于当前模拟的用户此时可以通过JMeter的JDBC配置从测试数据库中动态查询满足条件的订单号。JDBC参数化的核心是“JDBC Connection Configuration”配置元件和“JDBC Request”Sampler的组合使用。前者负责配置数据库连接信息URL、用户名、密码、连接池参数后者负责执行SQL查询语句并存储结果。具体操作流程如下先在“JDBC Connection Configuration”里配置好数据库连接参数然后在线程组中添加“JDBC Request”在SQL Query中编写查询语句例如SELECT order_id FROM t_order WHERE user_id ${user_id} LIMIT 10在“JDBC Request”的“Variable Names”中填入变量名例如order_id_list执行后查询结果会存储为${order_id_list_1}、${order_id_list_2}等。如果要在后续的HTTP请求中随机选取一个订单ID可以用V函数或__Random函数配合${__BeanShell(vars.get(order_id_list_ (${__Random(1,10,)})))}来动态获取。这里要特别注意JDBC请求在循环中的执行策略。默认情况下JDBC Request会在每次循环中都执行一次SQL查询这样会产生大量不必要的数据库查询压力干扰压测结果。更好的做法是在空间上只执行一次然后通过后续的HTTP请求循环消费查询结果。可以将JDBC Request放在“仅一次控制器”中或者放在线程组的初始化阶段通过setUp线程组实现。4.3 正则表达式提取器处理接口之间的数据依赖现代业务系统几乎没有完全独立的接口接口之间充满了数据依赖。登录接口返回的token要带给后续的业务请求创建订单接口返回的order_id要带给支付请求。JMeter处理这类场景的核心组件是“正则表达式提取器”或“JSON提取器”。以JSON提取器为例假设登录接口返回的响应体是{ code: 0, data: { token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9, userId: 12345 } }在登录请求上添加“JSON提取器”填入变量名token和userIdJSON表达式分别填$.data.token和$.data.userId。勾选“匹配数字”为0表示取第一个匹配项。后续请求中就可以直接使用${token}和${userId}引用。这里有个常见的坑如果登录请求在并发条件下执行每个线程拿到的token不同但如果你把登录请求放在线程组外面或放在“仅一次控制器”中所有线程会共用同一个token。如果业务系统对token有设备绑定或会话绑定逻辑这会导致大量401异常。正确的做法是让每个线程循环都先执行登录请求拿新token或者对token做“每线程唯一”的策略设计。4.4 BeanShell断言自定义校验业务逻辑的正确姿势JMeter内置的响应断言只能做包含匹配、等于匹配这类基础校验遇到“响应里包含某字段但需要动态判断这个字段的值是否合法”的业务场景就需要脚本断言出场了。BeanShell断言是JMeter中功能最灵活的断言方式它可以直接访问JMeter的运行上下文变量和响应对象。一个经典的场景是接口返回的code字段为0表示成功非0表示业务失败同时data字段中包含一个时间戳要求这个时间戳必须大于当前时间。实现这个校验的Beanshell断言代码大致如下String response prev.getResponseDataAsString(); if (response.contains(\code\:0)) { // 提取时间戳做进一步判断 // 如果业务校验失败通过以下方式设置断言结果为失败 Failure true; FailureMessage 业务校验失败错误信息...; }但我要提醒一句BeanShell虽然方便性能却很拉胯。BeanShell是解释执行的脚本语言在高并发压测场景下如果每个请求都执行BeanShell脚本JMeter自身的CPU消耗会急剧上升成为压测瓶颈。在正式压测中更加推荐使用JSR223断言器配合Groovy语言Groovy编译执行性能比BeanShell高一个数量级。def response prev.getResponseDataAsString() def json new groovy.json.JsonSlurper().parseText(response) assert json.code 0 assert json.data.timestamp System.currentTimeMillis()建议压测期间不要在JMeter脚本里做过于复杂的业务校验。断言是用来发现“明显错误”的不是用来替代业务测试的。复杂的业务逻辑校验应该交给自动化测试框架。5. 进阶场景实操文件上传、Cookie管理、HTTPS证书处理5.1 文件上传压测的正确打开方式很多系统都有文件上传功能但做文件上传压测的人却不多。原因很简单文件上传会真实消耗服务器的磁盘和带宽资源压测时容易把磁盘写满或把带宽打满很多人不敢轻易压。JMeter实现文件上传的配置核心点在“HTTP请求”Sampler的“文件上传”标签页。首先要把请求方法设为POST然后在“文件上传”标签页中填写文件名称本地文件完整路径、参数名称对应后端接口的接收参数名、MIME类型根据文件类型填写例如图片填image/jpeg文本填text/plain。同时在“HTTP信息头管理器”中不需要手动设置Content-Type为multipart/form-dataJMeter会自动添加上边界标记。压测文件上传时要注意两个细节。第一个是上传文件的大小要合理压测的目的是验证系统的处理能力不是把带宽打满建议分别使用小文件几百KB、中等文件几MB和大文件几十MB做不同场景的压测对比。第二个是上传完成后要清理服务器上的临时文件避免磁盘空间被压测数据耗尽否则压测后期会触发磁盘空间不足的连锁故障。5.2 Cookie管理模拟带登录态的请求很多业务系统的接口依赖Cookie维持会话状态。虽然很多系统现在主推token认证方式但仍有大量存量系统使用Session-Cookie机制。JMeter处理Cookie有两种方式。第一种是手动处理在“HTTP信息头管理器”中手动添加Cookie头信息这种方式适合已知Cookie值且值固定的场景。第二种是自动处理添加“HTTP Cookie管理器”JMeter会在请求执行过程中自动保存服务器返回的Set-Cookie值并在后续请求中自动携带对应域名的Cookie。自动处理方式最贴近真实用户行为。实际压测时建议这样设计线程组中先执行登录请求登录请求会返回Set-CookieHTTP Cookie管理器自动保存后续业务请求直接使用该Cookie。这里要注意HTTP Cookie管理器要放在线程组最上方确保它最先初始化。同时如果压测环境有多个域名需要在“HTTP Cookie管理器”的“用户定义的Cookie”中配置好域名和Cookie值的对应关系。5.3 录制HTTPS脚本的完整链路有些项目没有现成的API文档只有页面操作流程写JMeter脚本无从下手。这时候可以利用JMeter自带的HTTP代理服务器功能通过浏览器访问页面时自动录制生成的请求。录制HTTPS脚本的步骤如下第一步在JMeter中点击“测试计划”添加“HTTP代理服务器”。在“全局设置”中配置端口号默认8080目标控制器选择“测试计划 线程组”。第二步设置浏览器代理。把本机浏览器的代理服务器设置为localhost、端口与JMeter代理服务器一致。注意如果本地同时开了其他代理工具需要先关闭或排除冲突。第三步配置浏览器信任JMeter的HTTPS证书。JMeter的代理服务器在录制HTTPS请求时生成了一个虚拟证书需要导出并导入到浏览器的受信任证书列表中。在JMeter的bin目录下可以找到ApacheJMeterTemporaryRootCA.crt文件导入方式因操作系统不同略有差异但核心流程是一致的进入系统或浏览器的证书管理设置导入该证书并设置为受信任。登录状态下录制HTTPS脚本时可以手动设置浏览器的HTTPS代理为localhost:8080然后先访问一个需要登录的页面完成登录之后的操作请求都会记录在代理服务器中。录制完成后脚本里可能有大量无关请求静态资源JS、CSS、图片等建议手动清理掉。仅保留核心的业务请求并加上必要的参数化和关联。5.4 RESTful接口参数写法纠结细节才能保证成功率现在越老越多的项目采用RESTful风格的接口设计路径参数和Query参数在JMeter中的写法有差异新手容易混淆。对于路径参数例如GET/api/v1/users/12345/orders在JMeter的HTTP请求中“路径”一栏直接写/api/v1/users/${userId}/orders然后加一个“用户自定义变量”或从CSV参数化中引用${userId}即可。对于Query参数例如GET/api/v1/orders?status1page1pageSize10有两种写法。一种是直接在“路径”中写全/api/v1/orders?status1page1pageSize10更推荐的方式是在“参数”表格中添加status、page、pageSize三行JMeter会自动拼接成QueryString。对于POST请求中的JSON体直接在“请求体”中输入JSON格式的字符串例如{ orderId: ${orderId}, payAmount: 99.99, payChannel: ALIPAY }注意JMeter不会对JSON做语法校验如果JSON格式错误请求会以错误的方式发送到服务器导致压测结果出现大量非业务性失败。建议在小并发场景下先通过“查看结果树”确认请求体的格式是否正确。6. 全链路压测方案落地从单节点K8s到云上ECS迁移验证6.1 场景描述为什么需要云上验证最近做了一个很有意思的压测项目。业务方原本部署在单节点Kubernetes集群上的一套微服务应用要整体迁移到云上的ECS云服务器环境要求全程不停服、不丢数据。迁移完成后需要用JMeter做一轮高并发压测验证云上环境的承载能力是否满足业务SLA要求。这类项目在云迁移中很典型。本地机房或单节点K8s环境往往存在资源受限的问题业务正常运行时看着还好但一到高峰时段就容易出问题。迁移到云上后理论上资源可以弹性扩展但到底能扛多大并发需要压测数据说话。6.2 迁移后的服务地址与端口梳理JMeter脚本在迁移前后最大的变化点是服务地址。单节点K8s环境下微服务一般通过NodePort或Ingress对外提供访问迁移到阿里云ECS后服务会挂载到负载均衡实例SLB上对外提供统一入口。我在这个项目中踩过一个典型坑业务方给的压测地址是一个内网SLB地址压测机在另一个VPC内网络不通但压测脚本又显示所有请求都是000错误连接失败排查半天才发现是网络路由问题。所以启动压测前先做一轮“冒烟测试”——用curl -v或者JMeter单线程模式跑一遍确认网络连通性再放大并发。回到实际项目迁移后我有两个压测方案可以选择直连ECS的弹性公网IP或者通过SLB压测。我最终选择了通过SLB压测因为真实用户流量走的就是SLB这样可以验证整条链路的承载能力。同时我也保留了直连ECS的压测方案用于区分SLB自身瓶颈和应用层瓶颈。6.3 压测脚本设计与业务模型模拟这个项目涉及到的核心业务是用户登录、商品列表查询、商品详情查看、下单、支付回调五个接口。我根据线上日志统计的调用比例设定了如下的业务模型接口调用比例核心参数用户登录10%动态用户名、密码、加密签名商品列表40%分类ID、分页参数商品详情30%商品ID、用户ID下单15%用户ID、商品ID、数量支付回调5%订单号、支付金额、签名在JMeter中实现这种比例分配我用了吞吐量控制器Throughput Controller把每个接口放到独立的吞吐量控制器中分别配置百分比。线程组的总线程数设为1000Ramp-Up时间为60秒持续运行时间为30分钟。压测机用的是4核8G的ECS压测过程中监控JMeter自身资源确保没有达到瓶颈。数据准备这块下了不少功夫。用户数据从生产环境脱敏导出1万条真实用户名密码商品数据准备了两千条订单数据是压测过程中实时生成的。为了模拟真实用户行为我在JMeter中通过CSV数据文件参数化用户名和商品ID每个线程循环读取不同的数据组合。6.4 压测执行过程中的关键监控指标压测过程中除了JMeter自身的聚合报告之外我还实时监控了三块内容。第一块是SLB层监控。重点观察SLB的QPS、活跃连接数、丢包率和公网带宽使用率。如果带宽被压满到接近100%压测结果会失真需要增加带宽规格或减少并发。第二块是ECS层监控。CPU使用率、负载平均值、内存使用率、磁盘IOPS和负载、网络流入流出带宽。如果CPU先到达100%说明应用是计算密集型瓶颈如果磁盘IOPS先到达100%说明数据库或日志写入是瓶颈。第三块是应用和中间件监控。这个项目用到了Redis和MySQL我通过云监控观测Redis的连接数、慢查询数量、内存使用率MySQL的活跃连接数、慢SQL数量、Buffer Pool命中率。同时通过JVM监控观测应用GC频率和FGC耗时。实际压测结果在1000并发下运行30分钟核心五接口的聚合数据整体QPS稳定在3200左右平均响应时间210毫秒90%响应时间380毫秒错误率0.02%主要错误集中在网络超时的重试请求上业务本身没有出现明显的性能退化。SLB的CPU使用率约40%ECS的CPU使用率峰值约75%Redis连接数峰值约1200个MySQL活跃连接数峰值约148个。6.5 高并发压测发现的隐藏问题这次压测过程中发现了一个很有意思的问题。压测进行到第12分钟的时候MySQL慢查询数量和活跃连接数突然飙升我立刻拉取慢SQL日志发现一条按照用户ID和创建时间联合排序的分页查询SQL走了全表扫描。原因是这张订单表的数据量在压测期间涨到了数十万行原来建立的组合索引中用户ID字段选择性太低优化器默认走了全表扫描。排查过程大概是这样的先通过云监控确认了SQL性能下降的时间点和并发数然后通过Slow Log找到具体的SQL语句执行EXPLAIN查看执行计划确认走了全表扫描接着分析索引使用情况发现索引选择性问题最终通过调整SQL写法增加强制索引和优化排序字段以及配合业务侧的接口逻辑优化彻底解决。这类问题在压测中并不少见。单节点K8s环境下数据量小、并发低SQL性能问题被掩盖了迁移到云上后并发能力提升了数据量增长快SQL问题就暴露出来了。这恰恰说明了压测的价值不只是验证云上环境的承载能力更是对全链路的一次“体检”。心得压测发现的性能问题如果能在测试环境复现优先通过调整SQL、增加索引、修改Redis缓存策略等方式解决后再重新压测。压测不只是找问题更是要形成“发现问题-定位原因-优化解决-回归验证”的闭环。7. 压测过程中的常见报错与排查思路7.1 高并发下的JMeter自身报错Error writing to server很多人在高并发压测时遇到过这个报错java.io.IOException: Error writing to server。这个报错看起来像是服务端异常但实际上往往是因为JMeter发送请求时和服务器之间的连接出现了问题原因多种多样。最常见的两个原因一是被测服务器的TCP连接队列满了服务器无法及时accept新的连接二是JMeter本地的Socket连接被防火墙或系统参数限制。排查思路如下先在服务端执行netstat -anp | grep 端口号查看连接状态分布如果大量处于SYN_RECV状态说明服务端的listen队列满了需要调整somaxconn参数或优化应用层连接处理能力。如果是JMeter本地的问题检查JMeter进程的文件句柄数限制ulimit -n默认1024的限制在并发超过500时就会触顶建议调大为65535。7.2 ASP.NET MVC场景下的防伪标记问题有一个场景比较特殊压测ASP.NET MVC项目时接口报错__RequestVerificationToken未提供必要的防伪标记。这是ASP.NET MVC框架内置的防CSRF攻击机制在表单提交时会校验隐藏的防伪token字段。JMeter压测这种场景需要先从页面响应中提取防伪token。ASP.NET MVC的防伪token一般以隐藏字段或Cookie的形式存在。使用正则表达式或CSS选择器提取器从登录页面响应中提取__RequestVerificationToken的值然后在提交数据的请求参数中动态引用。同时注意ASP.NET MVC的防伪校验是Cookie和表单字段配对的需要在“HTTP Cookie管理器”中正确维持Cookie状态。7.3 文件已存在的报错和上传接口的特殊处理压测上传文件接口时有时会遇到“文件已经存在”的业务报错。这在压测中很常见特别是上传接口的幂等性设计不佳时。JMeter脚本层面的解决方案对上传的文件名做动态化处理例如把文件名称参数化为${__time(yyyyMMddHHmmss)}_${__Random(1,9999,)}.jpg每次上传时文件名不同。如果业务要求文件名固定例如头像上传则需要在压测前清理服务器上的历史文件或者配合业务方使用专门的压测存储空间。7.4 JMeter自身负载过高时的处理方案高并发压测时JMeter进程的CPU占用率如果接近100%需要检查脚本中是否有不合理的资源消耗。第一优先排查BeanShell相关组件。我之前跑过一个脚本在线程组里用了多个BeanShell断言器并发500时JMeter的CPU占用率直接拉满换成JSR223 Groovy之后CPU占用率降到了一半以下。第二排查监听器的使用。压测过程中不要使用“查看结果树”这个监听器会把所有请求的响应体保存在内存中跑上几分钟内存就爆了。正式压测用简单的“聚合报告”或“简单数据写入器”即可。第三是检查超时设置。如果HTTP请求没有设置超时时间遇到服务端连接不释放时JMeter线程会无限期等待导致线程堆积。建议在压测机资源允许的情况下尽量采用分布式压测方案。通过JMeter的Master-Slave模式把压力分布到多台压测机上这样既不会压垮JMeter自身还可以实现更大规模的并发模拟。8. 补充话题压力测试不止有JMeter8.1 CPU压力测试和GPU压力测试工具JMeter主要是针对应用层请求的压力测试但压力测试家族中还有CPU压测和GPU压测这两个方向在实际工作中也会遇到。CPU压力测试通常使用stress或stress-ng工具。测CPU密集型负载时直接运行stress --cpu 4 --timeout 60s让4个CPU核心满负载运行60秒观察系统负载和温度变化。轻量一点的是使用sysbenchsysbench cpu --threads4 --time30 run它可以同时测单线程和多线程性能适合对比不同CPU型号的算力差异。GPU压力测试方面NVIDIA官方推荐的工具叫gpu-burn跑起来能让GPU核心温度很快爬升到90度以上。./gpu_burn 60表示满负载运行60秒通过nvidia-smi观察GPU利用率、显存使用率和温度。如果GPU温度在压测后恢复正常散热没问题如果出现显存ECC错误或驱动异常说明GPU硬件存在隐患。8.2 存储压力测试覆盖磁盘写入性能验证有一类比较特殊的压测是针对手机App内部存储空间的写入测试实际上属于存储压力测试的范畴。这类测试需要考虑的不只是简单的“空间够不够用”还包括持续写入场景下的性能表现。在Linux服务器上做存储压测我常用fio工具。比如测试随机写入性能fio -namerandwrite -iodepth16 -rwrandwrite -bs4k -size4G -numjobs1 -runtime60 -group_reporting这条命令会以4KB块大小、16的IO深度随机写入一个4GB大小的测试文件持续60秒输出IOPS、带宽、延迟分布等指标。对于SSD和HDD的横向对比、RAID卡的写缓存策略验证都有非常直观的参考意义。8.3 在Windows系统上做CPU压力测试日常开发电脑上也可以用简单的方法做CPU压测。打开任务管理器观察每个核心的利用率然后运行一个多线程压缩任务比如7-Zip的性能测试模式或者直接用系统自带的Windows性能监视器配合一段密集计算脚本。不过这类压测更多是验证散热和稳定性和服务器上的性能基线测试是两个方向。回到JMeter本身工具只是手段核心还是对业务的建模和对数据的解读。建议每个做压测的同学都在实际项目中建立一套自己的排查方法论把常见的瓶颈类型CPU、内存、磁盘IO、网络、数据库连接、线程池配置逐一验证一遍压测报告才有真正的参考价值。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑