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

资讯详情

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

Jmeter一万并发接口压测实战:串联接口关联与参数化全解析

Jmeter一万并发接口压测实战:串联接口关联与参数化全解析 1. 项目背景与需求拆解提起jmeter接口压测很多人第一反应是加线程、跑脚本、看报告一套流程下来好像很顺但真到了一万名用户同时请求两个活动接口这种场景事情就没那么简单了。我接到这个任务的时候甲方只给了两句话第一两个活动接口是串联的第二个接口必须用到第一个接口的返回结果第二压测目标是模拟一万名并发用户。听起来很常规但实际操作时涉及线程模型设计、接口关联提取、参数化数据准备、结果监控与指标分析每一步都有坑。先说这个场景本身。两个接口串联在真实业务里非常常见典型的例子就是先创建订单再查询订单详情或者先获取活动标识再参与活动抽奖。第一个接口返回一个动态生成的ID、token或者某个活动编码第二个接口必须带着这个值才能正确响应。如果接口关联没做好压测脚本跑到一半全是失败请求一万并发就成了空话。所以这个任务的核心不只是压得动更在于脚本逻辑对不对、数据链路通不通。这篇文章适合谁看我建议三类人重点读一是刚接手jmeter性能测试、对线程组和关联还不熟的测试工程师二是要搭建高并发压测环境但拿不准参数怎么配的项目组成员三是已经在压测中碰到接口串联导致TPS上不去返回结果提取不到并发一高就报错这类问题的朋友。内容会尽量讲透不只是给结论还把为什么这么选、踩过哪些坑一并写出来。2. 压测场景设计一万名并发用户怎么落地2.1 线程组参数设计的关键逻辑jmeter里多少用户并发对应线程组的线程数。但一万名用户同时请求如果直接在线程组里填10000通常不是最佳做法。原因在于10000个线程同时启动对客户端本身的CPU、内存和socket资源压力非常大很可能压测机先被压垮同时服务器端如果没有经过预热和缓冲瞬间涌入的流量容易触发限流、连接池打满、超时最终结果反而不真实。我一般会在线程组里配置三个核心参数线程数Number of Threads、Ramp-Up Periodramp-up period和循环次数Loop Count。这里的核心判断是同时请求不等于同一毫秒内建立连接更合理的理解是在可接受的窗口期内持续处于满载状态。如果甲方的硬性要求是1秒内必须把所有用户全部顶上去那Ramp-Up可以设成0或1秒如果目标是考察系统在短时间内的抗压能力而不是瞬时脉冲冲击那我通常建议把Ramp-Up设成60到120秒让用户逐步进入。这里有个经验公式可以分享Ramp-Up时间大致等于线程数除以每秒期望新增用户数。比如1万用户、想让系统每秒新增200个用户Ramp-Up就是10000/20050秒。再结合压测机的负载情况微调。实际测试中如果压测脚本里还有登录、鉴权、业务数据写入等操作单线程占用的内存会更高这时线程数反而要控制得保守一些可以通过多台压测机做分布式压测来分摊。2.2 Ramp-Up时间与瞬时并发的取舍再往细里说Ramp-Up设0代表所有线程同时启动这在真实场景中往往并不是业务常态。比如一场秒杀活动用户是陆续点击进来的真正意义上同一时刻的并发量远没有数据库连接数那么可怕。压测的目的是验证系统能否扛住最大负载而不是验证系统能否扛住一次极端脉冲。所以我的建议是如果业务方没有特别强调必须1秒内全部到达就将Ramp-Up设置为30到120秒并且在压测报告中明确记录实际达到的最大并发用户数。做好取舍之后还有一个容易被忽略的地方——循环次数的设置。如果每个线程只循环一次一万个用户跑完就结束了系统压力曲线会迅速下降不利于观察稳定性如果循环次数设成永远或者一个较大值且配合调度器设置持续时间比如持续跑10分钟这样就能让系统在持续负载下暴露内存泄漏、线程池回收异常等深层问题。对于活动接口这类突发型业务我会用持续5分钟、无业务停顿的模式来压重点看TPS曲线是否平稳。2.3 单机跑不动怎么办分布式压测的方案与坑当线程数超过压测机本身的能力上限时分布式压测几乎是绕不开的选项。这里有个前提不是所有压测都必须上分布式。判断标准很简单先看本机CPU、内存占用以及网络带宽。如果压测机CPU已经飙到80%以上但被测系统TP99还是很好说明瓶颈在压测机而不是目标系统这时候就需要分布式。我在做一万用户压测时通常会准备3到5台执行机每台跑2000到3000左右的线程通过jmeter的Controller-Slave模式进行调度。配置的时候要注意所有Agent的jmeter版本必须一致否则脚本和插件兼容性很容易出问题。另外CSV数据文件如果每个Agent都读取同一个绝对路径会导致数据重复。解决办法是给每台执行机分发独立的参数文件或者用随机函数生成部分动态参数。分布式压测还有一个让人头疼的问题结果收集。我建议Controller只负责调度和汇总不参与实际压测汇总结果时尽量关闭所有数据写入本地文件这种模式否则等待结果合并的时间会比压测本身还长。用influxdb加grafana的方式来做实时监控是后话但如果你只是临时压一把把每台Agent的结果单独保存最后用合并工具汇总也行。3. 接口关联的核心实现第二个接口怎么拿到第一个接口的返回值3.1 JSON提取器最常用的关联解法这个任务里最关键的技术点是接口关联。两个接口串联第一个接口的响应结果中一般会有一个动态字段比如订单号、活动ID、用户凭证等。jmeter中处理关联的方式有很多正则表达式提取器、JSON路径提取器、XPath提取器、BeanShell后置处理器。针对现代Web接口绝大多数返回JSON的情况我首推JSON提取器因为它配置直观、匹配精准、调试方便。举个例子假设第一个接口获取活动信息返回如下内容{ code: 200, data: { activityId: ACT20250101, activityName: 周年庆 }, message: success }第二个接口参与活动需要把activityId作为参数传过去。在jmeter中我在第一个HTTP请求下面新增一个JSON提取器配置如下变量名称activityIdJSON路径表达式$.data.activityId默认值NOT_FOUND这样第二个接口的请求参数中直接填写${activityId}就能动态引用。需要注意JSON路径表达式必须与返回结构严格匹配。如果返回的结构是数组像$.data.list[0].activityId这种就需要带下标如果取了多个值还可以用activityId_1、activityId_2这种索引来引用多个结果这在批量场景下很实用。使用过程中我踩过的最深的坑是请求失败或者响应为空时JSON提取器取不到值第二个接口拿到的是默认值然后继续跑下去。这会让错误请求数量虚高还会污染整体的成功率统计。解决办法是加一个如果上一个请求失败则停止该线程的逻辑或者用正则表达式提取器中的匹配失败时返回空加上逻辑控制器做分支判断确保第一个接口成功后再跑第二个接口。3.2 调试关联变量的三个技巧接口关联写好了怎么确认变量真取到了值我通常用三种方式验证。第一种在第二个请求前加一个调试取样器Debug Sampler运行一次脚本后查看查看结果树中Debug Sampler的输出。如果activityId显示为ACT20250101说明提取成功如果显示为NOT_FOUND说明JSON路径写错了。第二种用用户定义的变量配合正则表达式做临时验证。在测试计划里加一个用户定义的变量值写成${activityId}然后运行后确认变量是否被正确展开。这种方式适合需要把变量引用到文件路径或自定义header的场景。第三种也是我强烈推荐的是用响应断言加JSON断言双重校验。断言里检查第一个接口的响应是否包含关键字段如果断言失败整个请求链条标记为失败方便定位是哪个环节出了问题。另外如果第二个接口返回的结果里还存在嵌套关联——比如第二个接口响应中又返回了一个新token第三个接口还要用那就在第二个请求下面再添加一个提取器提取规则完全一样只是变量名换掉。有人会问为什么不用正则表达式提取器老项目或者返回文本型接口确实可以用正则但它对JSON中的转义字符、嵌套层级处理起来比较痛苦。比如activityId:ACT20250101这种正则要写成activityId:(.?)看着简单一旦字段顺序变了或者返回里多了空格很容易失效。JSON提取器直接按路径取值和后端数据结构一一对应稳定性高得多。对于老系统返回的不是标准JSON那就用正则这个不冲突。4. 数据准备与参数化保证一万个用户的数据不打架4.1 CSV参数化真实用户数据怎么进来真实业务里一万个并发用户不可能都用一个账号、同一个手机号去请求活动接口否则服务端的数据唯一性校验会拦截大部分请求导致压测结果失真。所以参数化不是可选项而是必须做的基础工作。jmeter里最常用的参数化方案是CSV Data Set Config通过读取外部文件来喂给请求参数。我在准备CSV文件时通常会生成与总请求量匹配的用户ID、手机号、活动编码等字段。比如要跑一万并发、每个线程循环5次那么最少需要5万条数据否则同一个用户会被重复使用。这里有个容易搞错的细节CSV Data Set Config中线程共享模式Sharing Mode的选择。如果选择All threads所有线程共享同一个游标数据不会重复如果选择Current thread每个线程独立读取文件适合每个线程运行期间需要独立数据集的场景。但用All threads时如果文件行数不足读取完会循环或报错因此数据文件最好留出20%的余量。生成大批量测试数据我一般用Python脚本或者直接在数据库里批量insert然后用jmeter自带的函数助手中__CSVRead也可以。不过更推荐先写一段独立脚本生成CSV再做预处理比如手机号按规则生成、用户状态置为有效等免得压测中途因为脏数据产生各种奇怪报错。4.2 唯一性冲突与服务端校验并发下最容易被忽视的问题一万并发场景下最让人头疼的往往不是jmeter本身而是被测系统对数据的唯一性校验。比如活动接口限制了同一个用户只能参与一次那么第一次请求成功后第二次再带同一个用户ID服务端就会返回重复参与。压测结果中这类错误和真正的系统异常混在一起很难区分。解决的思路有三个层级。第一层数据准备阶段就把用户状态处理干净保证每个用户都是第一次参与第二层参数化文件里给每个并发线程分配不同的用户段比如线程1用user_0001到user_0100线程2用user_0101到user_0200第三层如果被测系统有幂等设计比如允许同一个用户重复提交但只算一次成功那么断言的逻辑也要相应调整不能简单认为返回非200就是失败。另外有些活动接口需要先获取用户token或者登录态这时就要把登录流程和活动流程分开处理。登录产生的token可以写入一个全局变量或使用HTTP Cookie管理器统一管理否则每一个请求都去重新登录既消耗接口资源又会让登录接口成为瓶颈完全掩盖了真实活动接口的性能水平。5. 断言、监听器与结果分析怎么判断压测到底过了没过5.1 响应断言这样配置才不会误判压测脚本写完了跑起来一堆绿色通过可这个通过是真的通过吗很多项目里HTTP请求返回200不代表业务成功。比如第一个接口返回的是JSON格式的{code:500,message:系统繁忙}但因为HTTP状态码是200jmeter默认就算成功。所以断言必须定义清楚业务成功的标准。我的做法是双重断言。第一层响应断言Response Assertion检查响应文本中是否包含预期字段比如参与成功或status:1第二层JSON断言JSON Assertion直接验证JSON路径的值是否符合预期比如$.status等于1。这样既能保证接口有响应也能保证业务逻辑真的成功。注意断言不能太宽松也不能太严苛。太宽松容易把坏请求放过影响指标准确性太严苛则可能把正常业务数据中的动态内容比如时间戳当成断言的比对对象导致误报。推荐的做法是断言固定字段和状态码不反向断言。比如不确定字段值就一定不断言确保断言随着接口演进仍然有效。在压测脚本中我还习惯把断言和事务控制器Transaction Controller配合使用。把第一个请求第二个请求放入同一个事务控制器并且在事务控制器属性里勾选Generate parent sample这样统计结果时就能直接看到一条完整的业务链路耗时而不是分散的两个接口采样分析起来直观很多。5.2 监听器选择这几个指标必须盯住监听器不建议一次性加太多因为监听器本身会消耗压测机资源尤其查看结果树在压测过程中如果一直开着内存占用会非常恐怖。一万并发场景下每一步都要克制。我一般会加四个组件聚合报告Aggregate Report、用表格查看结果View Results in Table、响应时间图Response Time Graph和后置的Summary Report。压测过程中重点看以下指标样本数Sample Count是否达到预期请求量错误率Error%真实错误率通过断言过滤后才有意义平均响应时间Average中位数Median90%或99%百分位响应时间P90/P99TPSThroughput接收/发送字节数用于判断带宽是否成为瓶颈实际操作中我的习惯是压测过程中关掉查看结果树只保留聚合报告和简易数据写入器Simple Data Writer把结果以CSV格式落盘。压测结束后再用查看结果树打开日志文件按取样器名称筛选失败请求分析具体报错信息。这样既不影响压测机性能又能拿到完整的失败详情。还有一种更高级的监控方式就是通过JMeter后端监听器Backend Listener把指标实时发送到InfluxDB再用Grafana展示压测过程中就能实时盯着TPS和响应时间曲线。这种方式适合长时间稳定性测试但对环境搭建有额外要求临时压测一把的话可以不做把结果落盘后集中分析就够了。5.3 从TPS曲线看瓶颈什么时候该停、什么时候该调压测过程中我习惯每隔一段时间记录一次TPS和响应时间。如果TPS随着线程数增加而线性上升说明系统还有余力可以继续加压如果TPS开始持平甚至下降而响应时间仍在上涨基本可以断定系统已经到达瓶颈。这时不要急着继续增加线程数而是去查服务端的CPU、内存、数据库连接池、GC日志定位瓶颈到底在哪里。这里有一个非常典型的误导压测机TCP连接耗尽。当你发现jmeter报错中有大量Connection reset、Connect timeout、No buffer space available时不一定是被测系统的锅。很可能是压测机自己的端口资源被占满了。解决办法是调大本地临时端口范围、减少TIME_WAIT状态的连接数或者干脆上分布式压测分摊压力。这个问题不解决线程加得越多错误越多指标毫无参考价值。6. 常见问题与排查实录一万并发下的那些坑6.1 连接超时和线程上不去怎么办这个场景我实在遇到太多次了线程数设了10000启动后curl被测系统却很正常但jmeter里大量报错Connect timeout或Read timeout。先别急着怀疑服务器不行按这个顺序排查第一个检查项压测机自身的最大文件句柄数。Linux下默认的ulimit -n往往只有1024而1万并发需要一万多个socket连接所以需要提前执行ulimit -n 65535并确认net.core.somaxconn等内核参数已经被调大。第二个检查项被测系统的并发连接数限制比如Nginx的worker_connections、Tomcat的maxThreads。这两个参数如果没调好再大的压测压力也进不去。第三个检查项HTTP请求头中是否缺少必要的Connection复用配置jmeter中HTTP请求默认勾选了Use KeepAlive建议保持勾选能减少TCP握手开销。我记得有一次我们压测时发现1万并发下失败率高达70%查了半天最后发现是压测机上开了多个浏览器标签页和IDE占用了不少端口和CPU。关掉这些杂七杂八的进程后失败率立刻降到2%以内。压测环境一定要尽量干净这不只是玩笑而是真实的性能杀手。6.2 第二个接口大量拿不到参数导致失败如果第一个接口响应正常但第二个接口大量返回参数校验错误优先排查关联变量是否没有正确传递。常见表现有两个一是在请求参数里直接写了${activityId}变量名和提取器里定义的不完全一致二是第一个接口的响应体较大JSON提取器配置没问题但如果响应内容超过了jmeter默认的缓冲大小或者响应头编码不是UTF-8提取出的值可能是乱码。我遇到过一次很隐蔽的问题第一个接口返回的字段名是activity_id但接口文档里写的是activityIdJSON提取器路径按文档写的$.data.activityId配运行始终拿不到值。排查时查看结果树才发现真实返回字段是下划线风格。这类问题没有捷径只能靠调试取样器加查看结果树一条条对照。还有一个高频坑第一个接口返回结果里有多个同名节点JSON提取器默认只取第一个但业务场景需要第二个接口循环使用所有节点。这种情况下要在JSON提取器中设置Match Numbers为-1然后通过activityId_1、activityId_2逐个引用或者配合ForEach控制器做循环处理。6.3 数据库连接池被打满的经典判断高并发压测下最脆弱的往往是数据库连接池。现象是jmeter端错误率飙升日志里大量Connection pool exhausted或者Too many connections。你在聚合报告里能看到的只是错误率但判断根因需要去服务端看监控。我曾经被这个坑折腾了一整天。接口压测一开始TPS有4000稳定了几分钟突然掉到800错误率涨到40%。去服务端看CPU和内存都没问题后来翻了数据库慢查询日志发现是连接池里的连接被慢SQL占用新的请求拿不到连接全部排队超时。根本原因是压测数据里出现了大量重复的主键插入触发了数据库的锁等待。后来把参数化数据改成了分布式唯一ID问题就消失了。这也提醒我们压测不是单纯跑脚本数据和脚本一样重要。如果数据本身不符合业务规则压出来的结果没有任何参考价值。6.4 常见问题速查表为了让你排查问题更方便我把这次一万并发场景中最常见的几类问题整理成了一个表格对应现象、可能原因和解决思路一目了然。问题现象可能原因解决思路大量Connect timeout压测机端口耗尽、内核参数不足调大本地端口范围、ulimit -n、采用分布式压测大量Read timeout被测系统连接池满、慢SQL、线程阻塞查服务端监控与数据库慢查询逐步定位瓶颈第二个接口参数为空或默认值接口关联配置错误、提取路径不对用调试取样器查看结果树检查提取结果错误率很高但HTTP状态码200业务逻辑失败但HTTP正常增加JSON断言校验业务状态字段TPS曲线周期性下降垃圾回收、缓存雪崩、定时任务抢占资源看gc log、缓存命中率、系统定时任务同一用户重复参与被拦截参数化数据不足或重复使用生成足够多的测试数据避免复用压测机CPU飙升监听器过多、线程数超过压测机能力关闭不必要的监听器开启分布式压测7. 压测报告怎么写才不挨骂压测做完报告写不清楚等于白做。我已经不只一次看到测试同学明明做了很多工作但因为报告里只有TPS5000、错误率1%这几个数字被业务方连环追问那到底能不能上线要不要扩容这种灵魂拷问。一份有说服力的压测报告至少要包含以下内容压测目标与指标定义、压测环境拓扑包括压测机配置、被测系统部署、中间件版本、线程组设计思路和关键参数、接口关联方案说明、测试数据规模与生成方式、压测结果核心指标TPS、响应时间、错误率、并发数、瓶颈定位过程与优化建议。其中优化建议不能只说建议扩容要说清楚是加应用实例、调大连接池、优化慢SQL还是增加缓存这样才能推动后续行动。在结果呈现上我习惯把并发数-TPS-响应时间三者的关系用表格呈现这样一个核心表格就能看出系统的拐点在哪里。举个简化例子并发用户数TPS平均响应时间(ms)P99响应时间(ms)错误率20001800521200.00%50003200982600.00%800041001865800.70%1000043002408201.10%从这个表格里能清楚地看出系统在8000并发之后开始出现拐点10000并发时P99已经快1秒了。这样报告里给出的建议重点优化数据库连接池并对活动详情接口增加本地缓存的结论就顺理成章了。8. 一点实操总结与个人建议这个项目做完之后我最大的感受是jmeter压测的技术难点往往不在工具本身而在你对业务场景的理解深度。两个接口串联有没有想清楚数据从哪来、关联关系怎么建立、成功了怎么断言、失败了怎么排查这些事情每一项都影响最终压测结果的可信度。一万用户并发不是简简单单填个数字就能交差的。如果你接下来也会遇到类似的串联接口压测场景我的建议是先从100个用户小规模跑通脚本确认接口关联和数据参数化都正确再逐步加压到1000、5000、10000。不要一上来就顶着最大压力调试脚本那样你根本分不清是脚本问题还是性能问题。另外压测过程中一定要盯牢压测机自身的资源消耗别让压测机成为隐形瓶颈。最后分享一个小技巧在jmeter脚本里把第一个接口和第二个接口用一个自定义的用户参数统一管理起来比如活动ID的变量名统一命名为activityId不要两个接口各用一套命名。这样后续要调整变量名、路径或者切换环境时只需要改一处整个测试计划都能同步更新省下大量排查时间。这个习惯我在多个项目里验证过是真的能帮你少熬夜的好习惯。
返回列表