ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

全国大学生软件测试大赛 Web 性能测试实例:JMeter 实战与瓶颈分析

全国大学生软件测试大赛 Web 性能测试实例:JMeter 实战与瓶颈分析 校赛出成绩那天一个学弟在群里发了张截图脚本跑通了聚合报告里平均响应时间 78 毫秒错误率 0他自己看着挺满意结果总分比预期低了一大截。后来复盘才发现问题根本不在脚本能不能跑而在于他没搞清楚这道 Web 性能测试题目真正要评的是什么——他交了脚本交了截图却没交出一份能说明这个系统在多少并发下会崩、崩在哪里、为什么崩的分析。全国大学生软件测试大赛 Web性能测试--实例一这个题面看着朴素实际上它是一道典型的综合题既要你会用工具把请求打出去又要你会设计负载模型还要你能从一堆数字里读出系统瓶颈最后用一份报告把所有推理链条讲清楚。软件测试、Web性能测试这两个关键词背后考的是完整的工程闭环而不是某个按钮点得熟不熟。我把这道题的完整解法拆成几个部分赛题到底考什么、工具怎么选、指标怎么从模糊需求翻译成数字、脚本怎么写干净、场景怎么编排、结果怎么读、指标不达标怎么查、现场怎么分配时间写报告。假设你手里已经有一份赛题和一个被测系统地址跟着往下走就行。不管你是第一次参赛的新手还是做过几个项目想系统梳理一遍的老手这套流程都能直接照着复现。1. 这道实例一实际考核的能力边界在哪里很多人拿到赛题的第一反应是打开 JMeter 就录脚本这是最容易踩的第一个坑。录脚本只是所有工作里最简单的 20%剩下的 80% 才是真正拉开分数的地方。所以先把题面吃透。1.1 赛题通常会给出的四类输入典型的赛题包会包含这么几样东西被测系统的访问入口一般是一个测试站点的地址偶尔会带端口。有些题目会额外给一份简单的接口文档或者在页面上留了可操作路径比如必须先登录才能进商品列表。一组测试账号通常是 CSV 或者表格形式账号数量比参赛人数多但不会多到随你挥霍。需求描述文字这是最关键也最容易被忽略的部分。它可能是系统需支持 500 用户同时在线核心交易接口 95% 响应时间不超过 2 秒也可能写得非常含糊比如评估系统在高负载下的表现。交付物要求脚本文件、测试数据、测试报告有的还会要求录屏或现场答辩。需求描述写得不清楚的情况非常常见这时候不要自己闷头编按合理的行业默认值来定并在报告里明确写出你的假设。评委看的是你的推理是否有据不是你猜得准不准。1.2 评分表上真正打分的那几栏从我观察到的情况看这类赛题的评分通常落在五个维度上权重大致是这样分布维度大概占比具体看什么脚本正确性与可复用性25%参数化、关联、断言是否到位换一份数据能不能直接跑场景设计合理性25%负载模型是否符合业务加压方式是否有依据数据采集完整性15%该采的指标有没有采采的时候口径是否一致瓶颈分析深度25%有没有定位到具体层次结论是否有数据支撑报告规范性10%结构、图表、指标定义是否清晰把这张表贴在显示器边上再动手你会发现很多动作的意义完全变了。比如为什么要把登录、浏览、下单串成一个事务控制器因为不串起来脚本正确性与可复用性和场景设计合理性两栏都拿不到分。1.3 新手最容易走偏的三个方向第一个是把并发数直接当线程数填。赛题说 500 并发很多人在线程组里填 500然后压测机先炸了。这个问题后面第三章会详细算。第二个是只测单个接口。挑一个登录接口压出个 TPS 就交差。但真实系统的瓶颈往往出现在下单要扣库存、扣库存要写数据库、写数据库要等锁这条链路上单接口压测根本暴露不出来。第三个是监听器堆满界面。查看结果树、聚合报告、响应时间图、聚合图全打开看起来数据很丰富实际上每个监听器都在吃压测机的内存和 CPU采到的数据本身就是失真的。正式压测阶段必须关掉查看结果树这是硬规矩。2. 工具选型为什么我把 JMeter 放在第一位工具这件事选错了后面全是返工。市面上能做 Web 性能测试的工具不少但在这类赛题场景里可选项其实没那么多。2.1 JMeter 与商业工具在这个场景下的真实差异JMeter 的优势很实在开源免费赛题环境通常只提供有限的机器装个商业工具的授权流程就能耗掉你半小时脚本文件是纯 XML可以直接提交评委打开就能看到你的线程组结构、参数化配置、断言规则可交付性极强命令行模式成熟非 GUI 执行、生成 HTML 报告都是一条命令的事。商业工具的优势主要在协议覆盖和自带分析报告上如果你的赛题涉及到特殊的协议或者需要极其规范的报告输出它的模板确实省事。但代价是环境依赖重、脚本迁移麻烦而且赛题现场往往不允许你慢慢配环境。提示如果赛题明确指定了工具别犹豫按指定工具来。如果没有指定JMeter 是容错率最高的选择。2.2 JMeter 环境准备里那些不起眼但致命的细节环境准备看着简单实际坑最多。我按踩过的顺序列一遍。JDK 版本。JMeter 是纯 Java 应用版本不匹配直接起不来报的错还特别隐晦。经验做法是用 JMeter 5.4.1 配 JDK 8或者用 5.5、5.6 配 JDK 8/11这是社区里最稳的组合。新版 JMeter 对 JDK 版本的要求会往上抬装之前一定去看一眼官方 release note 里的 Java Version 说明别凭印象。同时确认java -version和echo $JAVA_HOME指向同一个 JDK机器上装了好几个 JDK 的情况太常见了。中文乱码。压测结果里中文变成问号多半是编码没统一。打开bin/jmeter.properties把sampleresult.default.encoding改成UTF-8同时在 HTTP 请求默认值里把 Content encoding 设成 UTF-8。两处都要改只改一处有时候还是乱的。内存分配。JMeter 默认堆内存偏小长时间压测或者线程数上千的时候GC 会变得非常频繁直接表现为压测机 CPU 飙高、采样数据抖动。改bin/jmeterWindows 下是jmeter.bat里的HEAP-Xms2g -Xmx2g -XX:MaxMetaspaceSize512m具体给多少看压测机内存原则是不要超过物理内存的一半留出空间给操作系统和网络缓冲。别在被测服务器上跑压测。这是很多人图省事会干的事结果压测机和被测应用抢 CPU、抢内存数据完全是废的。哪怕赛题只给一台机器报告里也要写清楚这个限制说明数据是在资源竞争条件下采集的。2.3 顺带说一句 k6 和 Locust 的适用边界这两个工具面试里被问到的频率不低顺便理一下。k6 用 JavaScript 写脚本天生适合塞进 CI 流水线做持续压测但它的桌面可视化和报告能力偏弱主要靠导出到外部时序库看。Locust 用 Python基于协程模型单机可以模拟很高的并发量代价是需要你自己搭 Grafana 看图表。这两个工具的共性是单机并发上限高、报告要自己拼。赛题场景下JMeter 一套搞定更省心但如果你打算把性能测试做成团队里的常态化能力k6 在流水线里的位置是 JMeter 替代不了的。这个判断在面试里说出来比背一堆工具参数有用。3. 把高并发翻译成能测的数字工具装好了下一步不是录脚本而是把需求里的模糊词换算成具体数字。这一步不做后面的压测全是盲打。3.1 三个必须锁死的核心指标Web 性能测试的指标体系可以很复杂但核心就三个外加一个补充项吞吐量TPS 或 QPS单位时间内系统成功处理的请求数。注意成功两个字错误请求不能算进去。TPS 一般指事务QPS 指查询很多场景下这两个数会混用但报告里必须写清楚你用的是什么口径。响应时间绝不能只写平均值。要给出 P90、P95、P99 这几个分位值。平均响应时间 100 毫秒、P99 是 4 秒的系统对用户来说体验是灾难的。错误率失败请求占总请求的比例。这里有个大坑HTTP 状态码 200 不代表业务成功接口返回{code: 500, msg: 库存不足}的时候状态码也是 200。所以断言必须做到业务层光看状态码会漏掉大量静默失败。资源利用率补充项CPU、内存、网络带宽、数据库连接数。没有这一项你的瓶颈分析就只能是猜测。3.2 并发数、线程数、在线用户数根本不是一回事这三个词混用是新手最普遍的问题而且会直接导致压测模型跑偏。线程数是 JMeter 层面同时存在的发压者数量是最容易控制的参数。并发数是服务器某一瞬间正在处理的请求数量它等于线程数减去那些正在等待响应或者正在思考的线程。在线用户数是最宽泛的概念包含了正在阅读页面、填表单、发呆的用户这些人根本没给服务器发请求。打个比方线程数是你雇了多少个顾客去商店并发数是同一时刻真正站在收银台前的人数在线用户数是整个商场里晃悠的总人数。赛题里说500 用户同时在线绝不等于你要开 500 个线程。还有一个隐藏因素思考时间。真实用户点完一个按钮会停顿几秒再看下一个负载测试如果不加思考时间等于让每个用户以机器人般的速度狂点测出来的是理论极限吞吐不是真实的并发能力。3.3 用利特尔法则粗算线程数把模糊需求变成线程数最实用的工具是利特尔法则Littles Law公式简单到不行并发数 N 吞吐量 C × 平均响应时间 R单位要统一响应时间用秒。举两个方向的例子。正向算线程数。赛题目标写核心接口需达到 200 TPS响应时间不超过 200 毫秒。代入公式N 200 × 0.2 40。也就是说理论上 40 个线程就能压出 200 TPS。但这个数字是理想值实际要考虑响应时间的抖动、JMeter 自身的调度开销一般再乘 1.2 到 1.3 的余量取 50 个线程起步比较稳。反向验证合理性。赛题说500 并发用户平均响应时间 200 毫秒。这意味着目标 TPS 500 ÷ 0.2 2500。压测机能不能扛住 2500 TPS 的发起量简单估一下每个请求平均 10KB 数据量2500 × 10KB × 8 200 Mbps还要加上 TLS 握手和头部开销千兆网卡基本就贴边了。这种情况下你要么上分布式压测要么在报告里明确说明压测端存在瓶颈。注意利特尔法则算出来的是稳态下的理论值冷启动阶段、缓存预热阶段都不适用。用它做起点估算最后还是要靠阶梯加压实测确认。4. 脚本落地从录制到可复用的完整链路指标定完了开始动手做脚本。这里我建议的路线是先录制拿到原始请求然后手工重构最后补参数化、关联和断言。三个步骤分开做比一次性录完就直接拉起来压要靠谱得多。4.1 录制方式的选择与代理配置的细节JMeter 自带 HTTP(S) Test Script Recorder配置流程是在测试计划里加一个录制控制器在 HTTP(S) Test Script Recorder 里设置端口默认 8080点 start然后浏览器把代理指向127.0.0.1:8080。访问被测站点走一遍业务流程请求就会被记下来。HTTPS 站点要装 CDP 证书。JMeter 启动录制时会自动生成ApacheJMeterTemporaryRootCA.crt导入到系统的受信任根证书里否则 HTTPS 请求全是握手失败。录制完最大的问题是噪音太多。静态资源、埋点请求、广告请求全在里面。在 Recorder 的 Requests Filtering 里加排除规则把.css、.js、.png、.jpg、.woff、.ico以及埋点域名都过滤掉URL Patterns to Exclude 里用正则一条条加。不过说实话现在更推荐的做法是手工搭。用浏览器 F12 或者抓包工具把核心接口的 URL、方法、请求头、请求体一个个复制下来手工在 JMeter 里建 HTTP Request。这样做出来的脚本结构清晰没有冗余节点后期维护和讲解都方便。录制适合快速摸清业务流程不适合直接当交付物。4.2 参数化CSV Data Set Config 的四个必调项参数化的目的是让脚本换一份数据就能跑这是评分表里的重点。JMeter 里最常用的是 CSV Data Set Config有几个参数必须手工调默认值会坑你。参数名默认值建议设置原因File encoding空UTF-8不设的话中文账号或密码会乱码Recycle on EOFTrueFalse默认循环读文件会导致账号重复使用Stop thread on EOFFalseTrue数据用完后线程必须停否则会拿空值继续发Sharing modeAll threadsCurrent thread多线程组并发时避免取到同一个账号Recycle on EOF False和Stop thread on EOF True这两个组合起来用效果是数据用完线程自动退出。这比你手工算线程数和数据行数的对应关系可靠得多。Sharing mode 这个参数最容易被忽略。默认的 All threads 意味着所有线程组共享一个读取游标如果你的脚本里有多个线程组同时跑或者一个线程组里有多处引用了同一个 CSV 变量就可能出现两个线程取到同一个账号的情况。改成 Current thread 之后每个线程独立维护自己的读取位置串联场景下最稳。准备 CSV 数据的时候账号数量要留余量。假设单线程需要迭代 50 次开 30 个线程那就至少要 1500 行数据再乘以 1.5 的余量准备 2000 行以上。数据不够又开了 Recycle账号就会被重复使用如果业务侧有后登录踢掉前登录的逻辑脚本会大面积报错排查起来非常耗时。除了 CSVJMeter 还提供了几种参数化手段简单场景可以直接用User Defined Variables定义全局常量比如域名、端口。Random Variable生成随机整数或字符串适合造订单号。函数助手__Random、__UUID、__time用来生成唯一值。User Parameters适合少量数据的场景缺点是维护麻烦。4.3 关联JSON 提取器和正则提取器怎么选关联解决的是上一个请求的响应结果要被下一个请求用上的问题。最常见的场景是登录后拿到 token后续所有接口都要在请求头里带上它。提取器选型有个简单原则接口返回 JSON优先用 JSON Extractor返回 HTML 或者非标准 JSON用正则表达式提取器。JSON Extractor 的配置里JSON Path expressions 填类似$.data.token这样的路径。如果 token 藏在数组里就是$.data.list[0].id。Match No. 填 0 表示随机取一个匹配填 -1 表示取全部并拼接这种情况下要配合 Compute concatenation 使用具体取第几个就用正整数。正则表达式提取器的写法通常是token\s*:\s*(.?)Reference Name 填tokenTemplate 填$1$Match No. 填 1。非贪婪匹配.?是关键用贪婪匹配会一口吞到最后一个引号取出来的值完全不对。Default Value 一定要填。这是最容易忽略的一步。如果提取失败变量值会是null后续请求就会带着字面量 null 发出去服务端可能直接返回 200 加一个业务错误错误率不涨但业务全失败你从聚合报告里完全看不出来。另外Cookie 的管理交给 HTTP Cookie Manager 就好它会自动处理 Set-Cookie 和请求时的 Cookie 回传。如果要把 Cookie 跨线程组共享把 Cookie Manager 提到测试计划层级并且确认 Clear cookies each iteration? 没有勾选。4.4 断言和事务控制器让报告站得住脚断言的作用是把静默失败变成显式失败。JMeter 里常用这几种Response Assertion检查响应文本、响应代码、响应头。JMeter 5.4 之后还支持 JMESPath 断言可以直接对 JSON 做判断。Duration Assertion设定一个时间上限超过就判失败。用来验证响应时间不超过 2 秒这类需求。Size Assertion检查响应体大小用来发现返回空数据的情况。JSON Assertion对 JSON 响应做路径断言比文本断言精确。一个务实的原则每个需要验证业务是否成功的接口至少加一条业务级断言。比如登录接口断言响应里包含code:0下单接口断言返回了订单号字段。错误率才有意义。事务控制器是把多个请求打包成一个业务事务的组件。在事务控制器上勾选Generate parent sample它会在报告里生成一条汇总记录代表登录到下单这个完整业务的耗时再勾选Include duration of timer and pre-post processors in generated sample思考时间和前后置处理器的耗时都会计入测出来的才是用户真实感受到的时间。5. 负载场景编排梯度加压与思考时间的拿捏脚本能跑了接下来是编排负载。这一步决定了你的测试能不能找到系统的容量边界。5.1 阶梯式加压和一次性打满各有各的用处阶梯式加压是从低并发逐步往上加每一档保持一段时间观察 TPS 和响应时间的变化。它的价值是找拐点TPS 涨到某个点之后不再上升响应时间开始陡增错误率抬头这个点就是系统的容量上限。报告里的系统最佳并发为 XX超过后性能急剧下降这种结论只能靠阶梯加压得出来。一次性打满是直接上目标并发并持续一段时间验证系统在目标压力下的稳定性。它主要用来暴露内存泄漏、连接池耗尽、锁竞争这类跑得越久越明显的问题。原生 JMeter 的线程组支持 Ramp-up 时间但它只是在这段时间内把线程陆续启动做不到严格的阶梯。要做得漂亮需要装 Plugins Manager然后用Stepping Thread Group或者Ultimate Thread Group。Ultimate Thread Group 可以配置多个阶段每个阶段指定起始线程数、保持时长、上升下降时间非常灵活我通常用它来做三到五档的阶梯。不用插件也能凑合就是用线程组配合 Constant Throughput Timer 限制目标吞吐但精确度差一些。比赛现场没法装插件的话这是个可用的退路。5.2 思考时间到底加不加答案是看你测什么。测系统极限容量的时候不加思考时间让线程尽可能地打测出的是理论最大吞吐。测用户真实体验的时候必须加思考时间一般设成 1 到 3 秒的随机值。赛题里如果给出的是X 个用户同时在线这种描述那就是在暗示你要模拟真实用户行为必须加思考时间。如果给的是接口需支持 X TPS那你关注的是吞吐能力思考时间可以设得小一些甚至不加。JMeter 里加思考时间用定时器。Uniform Random Timer是最常用的配一个基础延迟Constant Delay Offset加一个随机范围Random Delay Maximum比如基础 1000 毫秒加随机 0 到 2000 毫秒这样每个用户的操作间隔在 1 到 3 秒之间看起来更像是人在操作。5.3 定时器的作用域是新手最大的坑这里必须单独强调因为这个问题我见过太多人栽在上面。JMeter 里的定时器、断言、前后置处理器都是有作用域的。放在线程组下、与采样器同级的定时器会作用于该线程组下的所有采样器放在某个采样器下面作为子节点的只作用于那个采样器放在事务控制器下的作用于控制器内的所有请求。结果就是如果你把思考时间定时器放在线程组根节点上那么你脚本里的每一个请求都会等它包括登录、加载商品、下单之间的每一个跳转。如果你又用了事务控制器包住了整个流程那么事务时间会被叠加好几倍测出来的响应时间完全失真。正确的做法是把定时器放在你真正想模拟用户停顿的位置一般是事务内部各步骤之间或者是整个事务控制器的末尾。做完脚本一定要跑一遍单线程看聚合报告里的响应时间是不是合理一个登录接口跑出 5 秒响应时间八成就是定时器放错了。集合点用Synchronizing Timer。它会让指定数量的线程在某个点同时释放模拟瞬时并发。比如设置 20 个线程同时释放就形成了 20 个并发的瞬时冲击。这里有个参数必须调Timeout in milliseconds。默认是 0意味着无限等待如果线程数凑不齐脚本会一直卡住不动比赛现场这种情况非常要命。一般设成 1000 到 3000 毫秒超时就先放行。5.4 什么时候需要上分布式单台压测机的发起能力是有天花板的。粗略估算一台 4 核 8G 的机器跑 JMeter 非 GUI 模式压到 800 到 1500 TPS 之间就会出现明显的自身瓶颈。如果你的目标吞吐超过这个量级或者网络带宽打满了就该考虑分布式。分布式的基本结构是一台 master 加多台 slave。配置要点有这么几条JMeter 版本必须完全一致包括插件版本。小版本号不同都可能连不上。JDK 版本一致否则会出现各种协议兼容问题。各机器时间同步不然结果文件里的时间戳对不上报告会乱。CSV 数据文件要分发到每台 slave路径保持一致。这是最容易忘的一步。CSV Sharing mode 要重新考虑。分布式下每个 slave 独立读自己本地的文件如果用 All threads多台机器会读到同一份数据的前几行账号就撞了。解决办法是把数据按 slave 数量切分每个 slave 只放自己那份。另外分布式压测本身会引入网络延迟和调度开销不是并发越高越好。赛题环境里如果能单机扛住就别折腾分布式把时间花在分析上更划算。6. 结果数据怎么读把聚合报告读成一份证据数据出来了怎么读决定了你的报告有没有说服力。聚合报告里有十几列真正有用的是其中五列其他都容易误导。6.1 聚合报告里最容易被误读的几列列名含义常见误读Average所有请求的平均响应时间被极值拉偏长尾严重的系统均值很好看Median中位数50% 的请求低于此值和均值差距大说明分布极度不均90% Line90% 的请求低于此值这才是体验的真实代表值Throughput每秒完成的样本数它统计的是所有采样器和 TPS 不完全等价Error%失败样本占比依赖断言质量没做业务断言的话这个数没意义这里要特别提醒Throughput 和 TPS 的区别。聚合报告里的 Throughput 单位是每秒样本数如果你的脚本里每个业务事务包含 5 个 HTTP 请求那么 Throughput 显示 500 的时候实际业务 TPS 大概只有 100。报告里必须写清楚这个换算关系否则数据口径说不清分析就站不住。同理Error% 只有在断言做得足够细致的时候才可信。状态码 200 但业务返回失败的场景没加业务断言就统计不出来。6.2 为什么我坚持看 P90 和 P99举个实际场景。一个接口的响应时间分布是这样的90% 的请求在 50 毫秒内返回10% 的请求在 800 毫秒到 3 秒之间。算出来的平均值大概 150 毫秒看着还不错。但这个系统对用户的真实体验是什么每十次操作里就有一次要等一两秒。这是完全不可接受的产品体验。P90 告诉你绝大多数用户的最差体验在哪里P99 告诉你最倒霉的那批用户遇到了什么。如果赛题给出了明确的响应时间要求比如95% 的请求不超过 2 秒那你必须保证 P95 达标只报平均值是拿不到分的。6.3 TPS 曲线的拐点怎么看阶梯加压跑完之后把每一档的 TPS、平均响应时间、P95、错误率拉成一张表比对着看并发档位TPS平均响应(ms)P95(ms)错误率观察201801101800%线性增长区403551151950%线性增长区604701282600%增长放缓805101655200.3%接近拐点10052032014502.1%已过拐点12051568032008.7%性能崩溃区这张表一眼就能看出系统的最佳工作点在 60 到 80 并发之间超过 80 之后 TPS 不再增长响应时间和错误率同时恶化。这种表格往报告里一放你的分析深度立刻就有了支撑。要画出 TPS 的实时曲线可以用 Backend Listener 把数据推到 InfluxDB再用 Grafana 展示或者简单点用 jpgc - Transactions per Second 监听器。注意这些监听器同样消耗资源只在调试阶段开正式压测关掉靠 jtl 文件后处理。6.4 HTML 报告的生成与截图取舍JMeter 命令行生成 HTML 报告的命令是jmeter -n -t plan.jmx -l result.jtl -e -o ./report几个细节容易出错。result.jtl这个文件在命令执行前必须不存在或者已经存在但内容为空-o指定的输出目录也必须是空目录否则 JMeter 会直接报错退出。这是最常见的一个执行失败原因。生成的 HTML 报告里真正值得看的是三块Statistics表格对应聚合报告、Response Times Over Time曲线、Transactions Per Second曲线。如果赛题要求提交截图就截这三块。截图的技巧是浏览器缩放到合适比例让坐标轴刻度和图例都清晰可读别只截个好看的曲线而丢掉了纵轴单位。如果报告要求你自己排版那就不要直接贴 JMeter 的截图完事把关键数据整理成 Markdown 表格重新排一遍。评委在有限时间里看几十份报告结构化表格的阅读效率远高于截图。7. 指标不达标时的分层排查链路真正体现水平的环节到了。压测跑完发现响应时间超标、TPS 上不去接下来怎么办答案是按层次自上而下排查每一步都要有数据支撑而不是东一榔头西一棒子。7.1 第一步永远是排除压测机自身的问题这一步优先做因为最省事也最容易被忽略。如果压测机自己就是瓶颈后面的所有分析都是白费。看 CPU。用top或者vmstat观察压测过程中压测机的 CPU 使用率。如果 JMeter 进程长期超过 80%基本可以确定是压测机不够用。这时候要么减线程要么上分布式。看端口。大量短连接会导致 TIME_WAIT 状态堆积端口被耗尽之后新的连接建不出来报错通常是java.net.BindException: Address already in use或者Non HTTP response code: java.net.SocketException。用netstat -an | grep TIME_WAIT | wc -l看一下数量如果几万级别就是这个问题。解决办法有两个层面一是在 HTTP 请求默认值里启用 Keep-Alive让连接复用从根上减少连接建立次数二是调整操作系统的端口范围和 TIME_WAIT 回收策略。前者是首选后者是补充。看 GC 日志。JMeter 自己也是 Java 应用堆内存不够会频繁 GC。开启 GC 日志如果发现 Full GC 频繁就回去调大HEAP参数。7.2 从应用层到中间件层到数据库层压测机排除干净之后开始查被测系统。应用层首先看线程池。Web 容器比如 Tomcat的maxThreads决定了同时能处理多少请求如果压测的并发数已经超过这个值多出来的请求会排队表现为响应时间线性增长但 TPS 不变。同时看acceptCount等待队列长度队列满了之后新连接会被拒绝。然后是 GC。用jstat -gcutil pid 1000持续观察重点看老年代的占用率和 Full GC 的频率。如果 Full GC 频繁并且老年代回收效果不好说明有对象被长期持有可能是缓存设计问题或者内存泄漏。这时候可以用jmapdump 堆快照配合 MAT 或者在线分析工具看大对象。再看锁竞争。用jstack抓线程栈如果发现大量线程阻塞在同一个锁上说明有热点资源竞争。这种情况在秒杀类业务里很常见解决方向是减少锁粒度或者改用无锁结构。中间件层主要看连接池和缓存。数据库连接池大小不够的时候请求会在获取连接这一步排队日志里通常有 waiting for connection 之类的提示。连接池不是越大越好它受限于数据库自身的最大连接数盲目调大只会把压力转移到数据库。缓存方面看命中率命中率骤降意味着大量请求穿透到了数据库。数据库层看慢查询。开启慢查询日志找出执行时间最长的几条 SQL用执行计划分析有没有走索引、有没有全表扫描、有没有隐式类型转换导致索引失效。这一层的优化往往收益最大一个缺失的索引可能直接把响应时间从秒级降到毫秒级。7.3 一个内存泄漏伪装成性能瓶颈的真实案例我遇到过这样一个场景值得拿出来说因为它很典型。压测刚开始的十分钟TPS 稳定在 420 左右响应时间 130 毫秒一切正常。但二十分钟之后TPS 开始缓慢下滑半小时后掉到 300 出头响应时间涨到 400 毫秒。错误率一直很低没有明显的报错。第一反应是数据库慢查询。检查慢查询日志发现确实有几条 SQL 耗时增长但幅度不大解释不了 TPS 掉了 30%。检查数据库连接池使用率正常。转去看应用层的 GC 日志。用jstat一盯就发现了问题老年代占用率在持续上升从 40% 慢慢爬到 85%然后触发一次 Full GC占用率掉回 50%TPS 短暂恢复到 400 左右接着又开始下滑。这是一个非常典型的内存缓慢泄漏加 Full GC 周期性影响的模式。dump 堆快照分析之后定位到一个每次请求都把全量配置数据缓存一份副本的逻辑缓存没有设置过期时间也没有容量上限。跑得越久堆积的对象越多。这个案例的价值在于如果只看聚合报告里的平均值你根本发现不了这个问题。平均值是整段时间的平均前十分钟的好数据和后二十分钟的差数据混在一起看起来就是一个中等偏下的数字。必须把数据按时间切片看趋势才能看出这种渐进式劣化。提示这类问题我在报告里的写法是先给 TPS 随时间变化的曲线再给老年代占用率曲线两条曲线一对比因果关系一目了然。这种用两组数据互相印证的分析是拉开分差的关键。8. 现场时间分配与报告撰写前面所有的技术动作最后都要落到在规定时间内交出一份能拿分的报告上。这一章讲节奏。8.1 把三小时切成六块假设赛题给的现场时间是三小时我通常这样分配阶段时长做什么环境检查与需求解读20 分钟确认 JDK、JMeter 能用读懂需求定指标脚本搭建与调试45 分钟单线程跑通全流程参数化、关联、断言补齐阶梯压测找拐点25 分钟三到五档阶梯记录每档数据正式压测25 分钟在目标并发下稳态跑 10 到 15 分钟数据整理与分析30 分钟拉表格、画曲线、定位瓶颈报告撰写35 分钟按结构填内容检查数据一致性这个分配里脚本调试占的时间最长这是合理的因为脚本不对后面全废。报告撰写给了 35 分钟很多人会在这一步压缩到 10 分钟结果前面做得再好也表达不出来非常可惜。时间不够的时候优先保什么我的顺序是脚本能跑通 数据能采到 报告结构完整 分析深度。分析深度是加分项报告结构完整是及格线别为了多挖一个瓶颈把报告写得七零八落。8.2 报告结构让评分点一眼可见不建议用花哨的模板结构清楚就行。我用的是这七段式测试概述一段话说清被测系统是什么、测试目的、测试范围。测试环境压测机配置、被测机配置、网络拓扑、软件版本。这一栏必须详细因为它决定了你的数据可不可信。测试目标与指标定义把需求原文抄一遍然后写出你翻译成的具体数字。特别要写清楚响应时间指 P95TPS 指业务事务吞吐不含静态资源请求这类口径说明。场景与脚本设计业务流程图、事务划分、参数化策略、关联策略、断言策略。可以配一张脚本结构的截图。执行结果阶梯压测数据表、稳态压测数据表、趋势曲线。数据要和前面的指标定义对应上。瓶颈定位与调优建议这是拿分主战场。每个结论都要有数据支撑格式建议是现象—排查过程—根因—建议。风险与结论说明测试的局限性比如本次测试未覆盖分布式部署场景、压测机与被测机在同一网段网络延迟影响未纳入评估。第 3 段和第 7 段最容易被省略但它们恰恰体现了你的专业度。指标定义不清数据就无法解读局限性不写结论就过度外推。8.3 几个能加分的细节说几个实操中总结出来的小动作成本很低但效果明显。在 jmx 文件里写注释。JMeter 的每个组件都有 Comment 字段把每个线程组的用途、每个提取器的提取目标写清楚。评委打开你的脚本第一眼看到的是结构清晰的组件树和说明印象分直接上去。数据脱敏。CSV 里的账号密码如果是真实数据报告里要替换成占位符。这体现的是基本的职业素养。附上复现步骤。在报告最后加一小节写清楚如何用提供的脚本复现本次测试包括执行命令、参数说明、预期耗时。这一步能让评委直接把你的结果跑一遍说服力完全不同。把失败也写进去。如果某个档位的压测出现了错误不要藏起来明确写出来并给出原因分析。比如该档位错误率 2.1%经查为数据库连接池耗尽导致详见第 6 节。坦诚面对失败数据比只报漂亮数字更可信。统一图表风格。所有曲线图的坐标轴单位、颜色含义保持一致P90 用一种颜色P95 用另一种从头到尾不变。这种细节让人觉得你是认真做过的。最后再说一句我个人在准备这类比赛时的体会真正决定名次的不是你压出了多高的数字而是你能不能把数字背后的因果链条讲清楚。我见过太多脚本跑得又快又稳的同学报告里只有一堆截图和性能良好四个字最后分数平平。反而是一些脚本不算特别花哨、但每一档数据都能解释清楚为什么是这样的队伍拿到了好成绩。因为性能测试的本质不是把压力打上去而是回答这个系统在什么条件下还能撑住这个问题回答得好不好全看你的证据链搭得牢不牢。还有个小习惯值得坚持每次做完一个实例把这次的 jmx 脚本、数据文件、报告模板单独归档一份命名带上日期和场景。做到第三个实例的时候你会发现八成的工作量可以直接复用剩下的时间全都能花在分析上。这个复利效应比临时抱佛脚刷题库有用得多。
返回列表