API性能测试JMeter脚本化与Gatling代码化双方案前面咱们搞定了功能测试但接口能跑通不代表能扛住流量。今天聊性能测试——JMeter和Gatling两个主流工具什么时候用哪个怎么设计压测场景一、性能测试不是把并发调大就行很多新手做性能测试上来就设置1000并发跑10分钟然后看TPS高不高。这其实是个误区。性能测试的三种类型类型目的怎么测负载测试系统在正常负载下表现如何逐步加压到预期峰值观察指标压力测试系统的极限在哪持续加压直到系统崩溃找瓶颈稳定性测试长时间运行会不会出问题保持中等负载跑几小时/几天看内存泄漏关键指标解读┌─────────────────────────────────────────────────────────┐ │ 用户请求 ── 响应时间Latency ── 服务端处理 │ │ ├── 网络传输时间 │ │ ├── 服务端处理时间 │ │ └── 数据库/缓存查询时间 │ │ │ │ TPS每秒事务数 完成的请求数 / 时间 │ │ 并发数 同时在线的用户/连接数 │ │ 吞吐量 单位时间处理的数据量MB/s │ │ │ │ 错误率 失败请求 / 总请求 │ │ P50/P95/P99 50%/95%/99%的请求响应时间 │ └─────────────────────────────────────────────────────────┘重点关注P99而不是平均响应时间。平均数容易被少数快请求拉低P99告诉你最慢的那1%用户体验如何。二、JMeter老牌但够用的GUI工具快速上手下载解压后直接运行bin/jmeter.batWindows或bin/jmeter.shMac/Linux。一个简单的压测脚本Step 1添加线程组模拟用户右键测试计划 → 添加 → 线程用户→ 线程组 线程数100 ← 并发用户数 Ramp-Up10秒 ← 10秒内启动100个用户 循环次数10 ← 每个用户执行10次Step 2添加HTTP请求右键线程组 → 添加 → 取样器 → HTTP请求 协议https 服务器名称api.example.com 端口443 方法GET 路径/api/productsStep 3添加监听器看结果右键线程组 → 添加 → 监听器 → 聚合报告 右键线程组 → 添加 → 监听器 → 查看结果树Step 4运行点绿色三角按钮等跑完看聚合报告Label #Samples Average Median 90%Line 95%Line 99%Line Min Max Error% Throughput HTTP请求 1000 156 120 280 350 500 50 800 0.00% 450.2/secJMeter的进阶配置参数化不同用户不同数据右键线程组 → 添加 → 配置元件 → CSV数据文件设置 文件名users.csv 变量名username,passwordusers.csv内容user001,pass001 user002,pass002 user003,pass003HTTP请求中引用路径/api/login 参数 username: ${username} password: ${password}断言验证响应右键HTTP请求 → 添加 → 断言 → 响应断言 测试字段响应文本 模式匹配规则包含 测试模式success:true思考时间模拟真实用户停顿右键线程组 → 添加 → 定时器 → 高斯随机定时器 偏差1000ms ← 平均停顿1秒 固定延迟偏移500msJMeter的痛点痛点说明GUI模式资源消耗大压测时别用GUI用命令行模式脚本难版本控制.jmx是XMLdiff看不懂分布式配置麻烦主从节点要配RMI防火墙端口要开复杂逻辑难实现想做个条件分支写BeanShell吧痛苦命令行模式生产环境必须用# 非GUI模式运行jmeter-n-tapi-test.jmx-lresult.jtl-e-oreport/# -n: 非GUI模式# -t: 测试脚本# -l: 结果日志# -e: 生成报告# -o: 报告输出目录三、Gatling代码化压测的新选择Gatling用Scala DSL写压测脚本虽然要学点Scala语法但回报巨大。引入依赖dependencygroupIdio.gatling.highcharts/groupIdartifactIdgatling-charts-highcharts/artifactIdversion3.9.5/versionscopetest/scope/dependencyplugingroupIdio.gatling/groupIdartifactIdgatling-maven-plugin/artifactIdversion4.6.0/version/plugin第一个Gatling脚本// src/test/scala/com/example/ApiSimulation.scalapackagecom.exampleimportio.gatling.core.Predef._importio.gatling.http.Predef._importscala.concurrent.duration._classApiSimulationextendsSimulation{// HTTP协议配置valhttpProtocolhttp.baseUrl(https://api.example.com).acceptHeader(application/json).contentTypeHeader(application/json)// 场景定义valscnscenario(查询商品场景).exec(http(查询商品列表).get(/api/products).queryParam(page,1).queryParam(size,20).check(status.is(200)).check(jsonPath($.total).exists)).pause(1,3)// 思考时间1-3秒随机停顿.exec(http(查询商品详情).get(/api/products/${productId})// 从session中提取变量.check(status.is(200)))// 注入负载配置setUp(scn.inject(rampUsers(100).during(10.seconds),// 10秒内启动100用户constantUsersPerSec(50).during(60.seconds)// 然后保持50/s的速率跑60秒)).protocols(httpProtocol)}运行mvn gatling:test-Dgatling.simulationClasscom.example.ApiSimulationGatling的DSL有多爽复杂场景编排valbrowsescenario(浏览购买流程)// 1. 登录.exec(http(登录).post(/api/auth/login).body(StringBody({username:${username},password:${password}})).check(jsonPath($.token).saveAs(authToken))// 提取token存到session).pause(2)// 2. 浏览商品从CSV feeder读取商品ID.feed(csv(products.csv).random).exec(http(查看商品).get(/api/products/${productId}).header(Authorization,Bearer ${authToken}).check(jsonPath($.stock).saveAs(stock))).pause(1,5)// 3. 条件判断有库存才下单.doIf(sessionsession(stock).as[Int]0){exec(http(创建订单).post(/api/orders).header(Authorization,Bearer ${authToken}).body(StringBody({productId:${productId},quantity:1})).check(status.is(201)))}多种负载模型setUp(// 模型1逐步加压找拐点scn.inject(nothingFor(5.seconds),atOnceUsers(10),rampUsers(100).during(30.seconds),rampUsers(500).during(60.seconds),rampUsers(1000).during(120.seconds)),// 模型2脉冲负载模拟秒杀spikeScenario.inject(rampUsers(10000).during(10.seconds),nothingFor(30.seconds),rampUsers(10000).during(10.seconds)),// 模型3稳定性测试stabilityScenario.inject(constantUsersPerSec(100).during(2.hours))).protocols(httpProtocol)丰富的断言// 全局断言setUp(scn.inject(...)).protocols(httpProtocol).assertions(global.responseTime.max.lt(1000),// 最大响应时间 1sglobal.successfulRequests.percent.gt(99.0),// 成功率 99%global.requestsPerSec.gt(100),// TPS 100details(创建订单).responseTime.percentile(95).lt(500)// 下单P95 500ms)Gatling的报告运行完后自动生成HTML报告长这样target/gatling/apisimulation-20240101120000/ ├── index.html # 总览 ├── js/ # 图表JS └── req_*.html # 每个请求的详细统计报告包含响应时间分布图直方图 百分位曲线每秒请求数/响应数趋势活跃用户数变化错误统计和堆栈四、JMeter vs Gatling怎么选维度JMeterGatling学习曲线低GUI点点点中要学Scala DSL脚本维护难XML难diff易代码可版本控制复杂场景难BeanShell痛苦易代码灵活资源消耗较高较低Netty异步IO报告美观一般优秀自带炫酷图表团队协作测试人员为主开发人员顺手生态插件丰富社区插件多较少但够用我的建议快速验证/测试团队用→ JMeter持续集成/开发团队用→ Gatling复杂业务场景→ Gatling代码比GUI灵活太多五、性能测试的坑与最佳实践坑1在本地跑压测你的笔记本能模拟1000并发别闹了。网络带宽、CPU、内存都是瓶颈。正确做法用专门的压测机器或者云厂商的压测服务阿里云PTS、AWS Load Testing等。坑2不预热直接压JVM要预热JIT编译连接池要初始化缓存要填充。一上来就猛压结果不准。正确做法// Gatling中加个预热阶段scn.inject(rampUsers(100).during(60.seconds),// 先慢慢预热1分钟constantUsersPerSec(1000).during(300.seconds)// 再正式压测5分钟)坑3只压一个接口真实场景是多个接口混合调用比例不同。正确做法// 模拟真实用户行为比例setUp(// 80%用户只浏览browseOnly.inject(rampUsers(800).during(60.seconds)),// 15%用户浏览加购物车browseAndCart.inject(rampUsers(150).during(60.seconds)),// 5%用户完整购买流程fullPurchase.inject(rampUsers(50).during(60.seconds))).protocols(httpProtocol)坑4不看服务端指标客户端TPS高但服务端CPU 100%、内存OOM、GC频繁这不算通过。正确做法压测时同时监控服务端CPU/内存/磁盘IO数据库连接池使用率、慢查询Redis命中率、连接数JVM GC频率和耗时网络带宽坑5没有基线和对比“这次压测TPS是500”——然后呢上次是多少优化后提升了多少正确做法每次压测结果存档建立性能基线优化前后对比。六、Spring Boot Gatling 集成示例// 在Spring Boot测试里启动Gatling压测SpringBootTest(webEnvironmentSpringBootTest.WebEnvironment.RANDOM_PORT)classPerformanceTest{LocalServerPortprivateintport;TestvoidrunGatlingSimulation()throwsException{// 设置系统属性让Gatling连上随机端口System.setProperty(gatling.http.baseUrl,http://localhost:port);// 运行GatlingGatlingPropertiesBuilderpropsnewGatlingPropertiesBuilder().simulationClass(com.example.ApiSimulation).resultsDirectory(target/gatling-results);Gatling.fromMap(props.build(),List.of());}}七、小结今天咱们聊了性能测试的方方面面主题要点性能测试类型负载测试、压力测试、稳定性测试目的不同关键指标关注P99、错误率、吞吐量别只看平均数JMeterGUI易上手适合快速验证和测试团队Gatling代码化DSL适合复杂场景和CI集成常见坑本地压测、不预热、单接口压测、不看服务端指标、无基线一句话总结JMeter是瑞士军刀Gatling是精密仪器。日常快速验证用JMeter持续性能测试和复杂场景用Gatling。你们性能测试用JMeter还是Gatling遇到过什么坑欢迎聊聊。