☰
Gatling 3.0.0 压测实战:从环境搭建到脚本编写与避坑指南
2026/10/29 11:44:21 网站建设 项目流程

简介:Gatling 3.0.0 是一款面向现代 Web 应用的性能测试工具,适合开发者与测试工程师用来模拟高并发用户负载,验证系统在压力下的响应时间、吞吐量与资源利用率。其脚本基于 Scala 的 DSL 语法编写,可读性与可维护性较强,并支持场景录制回放、检查点设置与执行速率配置,便于构建贴近真实的用户行为模型。资源包共 114 个文件,以 97 个 jar 依赖库为主体,辅以 6 个 scala 脚本、3 个 conf 配置、2 个 bat 与 2 个 sh 启动脚本,以及 csv 数据样例和 xml 等文件,压缩包约 57.17MB,开箱即可运行测试并生成图表化报告。目前已有 237 人学习下载。借助 Highcharts 图表可直观查看每秒请求数、平均响应时间与百分位分布,配合命令行工具与持续集成能力,帮助读者快速定位性能瓶颈、沉淀可复用的压测脚本与排错思路。

1. Gatling 3.0.0 拆包:一份能直接跑起来的压测工具包

压测这件事,最怕的不是压不出问题,而是压出来的数字自己都不信。用 JMeter 图形界面点几下就能发压,但脚本一多、并发一高,GUI 本身就成了瓶颈,报告还得靠插件拼。Gatling 3.0.0 这个版本走的是另一条路:Scala DSL 写脚本、异步非阻塞模型发压、压完直接吐一份带百分位和响应分布图的 HTML 报告。它解决的核心问题就一个——让压测脚本像代码一样进版本库、可 review、可 CI 跑,而不是躺在某个人的桌面文件夹里。适合谁?已经会用命令行、愿意花半天学一套 DSL 的后端和测试从业者;只想点鼠标出个并发数的,这套东西前期投入不划算。下面按「它是什么 → 怎么装怎么跑 → 脚本怎么写 → 坑在哪 → 怎么验证」的顺序拆。

2. 环境落地:从解压到第一条压测命令

Gatling 3.0.0 是绿色包,解压即用,但它对 JDK 版本有硬要求——这个版本跑在 Scala 2.12 上,需要 JDK 8,JDK 11 及以上会在启动阶段直接抛错。很多人第一次翻车就翻在这里:机器上默认是 JDK 17,gatling.sh一执行就报Unsupported class file major version,还以为是包坏了。

2.1 目录结构与启动脚本

解压后先别急着跑,花两分钟认清目录,后面改配置、放脚本、找报告都靠它。

路径作用是否常改
bin/gatling.shLinux/macOS 启动入口否
bin/gatling.batWindows 启动入口否
conf/gatling.conf全局配置:编码、报告、数据写入偶尔
conf/logback.xml日志级别排错时改
user-files/simulations/放 Scala 压测脚本高频
user-files/resources/放请求体、CSV 参数文件高频
user-files/bodies/放二进制请求体按需
results/每次运行的报告输出只读

启动脚本干的事其实很简单:拼 classpath、找user-files/simulations下的编译产物、调io.gatling.app.Gatling主类。所以脚本放错目录、或者文件名和类名对不上,都会表现为「找不到 simulation」。

2.2 JDK 与内存参数

先确认 JDK 版本,再决定要不要动内存。Gatling 默认堆不大,几千并发下容易 GC 抖动,压出来的 P99 会虚高。

# 确认 JDK 版本,必须是 8 java -version # 临时指定 JDK 8 启动(机器上多版本共存时) export JAVA_HOME=/path/to/jdk8 export PATH=$JAVA_HOME/bin:$PATH # 调大堆和直接内存,再启动 JAVA_OPTS="-Xms2g -Xmx2g -XX:+UseG1GC" ./bin/gatling.sh

-Xms和-Xmx设成一样,避免运行期堆伸缩带来的停顿;UseG1GC在 3.0.0 这个年代已经是压测场景的稳妥选择。直接内存不用手动设,Gatling 的 Netty 底层会按需申请,但如果机器内存紧张,记得留出余量——压测机自己 OOM 了,报告里的错误率就没有参考价值。启动后如果看到交互式菜单列出可选 simulation,说明环境通了;如果直接报错退出,先看results/同级有没有生成日志,再看conf/logback.xml把级别调到 DEBUG 重跑一次。

2.3 用官方示例验证链路

包里自带几个示例 simulation,第一次跑建议先跑自带的,确认「启动 → 发压 → 出报告」整条链路没问题,再换自己的脚本。

# 交互式启动,按提示选择 BasicSimulation ./bin/gatling.sh # 或者直接指定 simulation 类名,适合 CI 里非交互执行 ./bin/gatling.sh -s computerdatabase.BasicSimulation

跑完终端会打印报告路径,形如results/basicsimulation-<时间戳>/index.html。打开看三块:全局的请求数/成功率、响应时间分位数(50/75/95/99)、以及按请求拆分的响应时间分布。这一步的意义是建立基线——后面自己写的脚本跑出来数字离谱时,你能判断是脚本问题还是环境问题。常见做法是把这个示例报告留一份,作为「环境正常」的对照。

3. 脚本编写:Scala DSL 的四个核心构件

Gatling 脚本本质是一个继承Simulation的 Scala 类,里面用链式 DSL 描述「谁、在多久内、发什么请求、怎么断言」。3.0.0 的 DSL 和后续版本有差异,网上抄来的新版本写法直接扔进来会编译不过,这点要有心理准备。

3.1 场景、注入模型与协议配置

一个最小可用的脚本包含三部分:HTTP 协议配置、场景定义、注入策略。注入模型决定了并发曲线长什么样,选错了压出来的数字没有业务意义。

import io.gatling.core.Predef._ import io.gatling.http.Predef._ import scala.concurrent.duration._ class OrderSimulation extends Simulation { // 协议层:baseUrl、公共头、超时 val httpConf = http .baseURL("http://127.0.0.1:8080") .acceptHeader("application/json") .connection("keep-alive") .requestTimeout(10.seconds) // 场景:一个虚拟用户的行为链 val scn = scenario("下单链路") .exec(http("首页").get("/")) .pause(1) .exec(http("查商品").get("/api/item/1").check(status.is(200))) // 注入:3.0.0 用 setUp + 注入步骤 setUp( scn.inject( rampUsers(50) over (30.seconds), // 30 秒内线性加到 50 用户 constantUsersPerSec(5) during (60.seconds) // 之后稳定每秒 5 个新用户 ) ).protocols(httpConf) .assertions(global.responseTime.percentile3.lt(800)) }

rampUsers是「总用户数在时间窗内爬坡」,constantUsersPerSec是「每秒新增用户速率恒定」,两者语义不同,别混用。percentile3对应 P95,断言写在这里,压测不达标时进程会以非零码退出,CI 里能直接卡住。requestTimeout一定要设,默认值偏长,遇到慢接口会把整个压测拖成「假低并发」。

3.2 检查点、参数化与请求体

只发请求不校验,等于只测了「服务没挂」。检查点(check)负责从响应里提取值、做断言,参数化负责让每个虚拟用户发不同的数据。

import io.gatling.core.Predef._ import io.gatling.http.Predef._ class LoginSimulation extends Simulation { val httpConf = http.baseURL("http://127.0.0.1:8080") // 从 CSV 读账号,circular 表示循环复用 val users = csv("users.csv").circular val scn = scenario("登录并下单") .feed(users) .exec( http("登录") .post("/api/login") .body(StringBody("""{"user":"${username}","pwd":"${password}"}""")).asJSON .check(status.is(200)) .check(jsonPath("$.token").saveAs("token")) // 提取 token 供后续用 ) .exec( http("下单") .post("/api/order") .header("Authorization", "Bearer ${token}") .body(StringBody("""{"itemId":1,"qty":1}""")).asJSON .check(status.in(200, 201)) ) setUp(scn.inject(atOnceUsers(20))).protocols(httpConf) }

feed把 CSV 的一行注入会话变量,${username}在字符串里做插值;saveAs("token")把提取值存进会话,后续请求用${token}引用。asJSON会顺带设Content-Type,漏了它服务端可能按表单解析,返回 415。CSV 文件放user-files/resources/,编码用 UTF-8,带 BOM 的话第一列字段名会带上不可见字符,表现为「变量取不到值」——这个坑很隐蔽。

3.3 用 Maven 工程管理脚本

绿色包适合试水,真要在团队里用,脚本得进版本库、能编译、能打包。3.0.0 官方提供 Maven 原型,把脚本变成标准 Java 工程的一部分。

<!-- pom.xml 关键片段 --> <properties> <gatling.version>3.0.0</gatling.version> <scala.version>2.12</scala.version> </properties> <dependencies> <dependency> <groupId>io.gatling.highcharts</groupId> <artifactId>gatling-charts-highcharts</artifactId> <version>${gatling.version}</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>io.gatling</groupId> <artifactId>gatling-maven-plugin</artifactId> <version>3.0.0</version> <configuration> <simulationClass>simulations.OrderSimulation</simulationClass> </configuration> </plugin> </plugins> </build>

脚本放src/test/scala/simulations/,mvn gatling:test触发压测,报告落在target/gatling/。simulationClass写全限定类名,包名和目录结构必须一致,否则插件找不到类。这套结构的好处是依赖版本锁死、CI 里一条命令跑完,坏处是首次拉依赖比较慢,内网环境要配好镜像。

4. 避坑与排查:五个真实翻车现场

4.1 启动即报 Unsupported class file major version

现象:执行gatling.sh立刻退出,日志里出现Unsupported class file major version 55之类的字样。原因:机器默认 JDK 是 11 或更高,而 3.0.0 编译目标是 JDK 8。解决:显式export JAVA_HOME指向 JDK 8,再启动;多版本共存时别改全局默认,用脚本里临时设环境变量的方式,避免影响其他工具。

4.2 报告里响应时间漂亮但业务侧说服务被打挂

现象:Gatling 报告成功率 100%、P99 很低,但同一时段服务监控显示线程池打满。原因:connection("keep-alive")加连接复用,压测机和服务端之间连接数远小于虚拟用户数,服务端看到的并发被低估;同时requestTimeout设得太长,慢请求被算成「成功但慢」。解决:核对服务端连接数与虚拟用户数的比例,必要时在协议层关掉连接复用做对照;把超时收紧到业务可接受上限,让超时请求计入失败而不是拉高均值。

4.3 CSV 参数化取不到值

现象:请求体里出现字面量${username},或者服务端报参数为空。原因:CSV 文件不在user-files/resources/下、文件名大小写不符、或者文件带 BOM 导致首列字段名异常。解决:确认路径和文件名完全一致;用十六进制工具看一眼文件头有没有EF BB BF,有就转成无 BOM 的 UTF-8;feed放在exec之前,顺序错了变量还没注入就发请求了。

4.4 断言不生效,压测不达标也返回 0

现象:P95 明显超过阈值,但命令退出码是 0,CI 没拦住。原因:assertions写在了setUp(...)链之外,或者用了 3.0.0 不支持的断言 API(从新版本文档抄来的写法)。解决:断言必须挂在setUp(...).protocols(...).assertions(...)这条链上;percentile3是 P95,percentile4是 P99,别记反;改完先故意把阈值调到不可能达到的值,确认进程真的以非零码退出,再调回正常值。

4.5 高并发下压测机自己先崩

现象:并发上到几千,Gatling 进程 OOM 或 GC 停顿严重,报告里出现大量连接超时。原因:默认堆太小,加上每个虚拟用户的会话对象都占内存,压测机成了瓶颈。解决:JAVA_OPTS里把-Xms/-Xmx调到物理内存的合理比例(留出系统余量),换 G1;并发再高就上分布式,多台压测机分片,别指望单机扛一切。判断是不是压测机瓶颈的方法:看压测机 CPU 和 GC 日志,如果压测机先饱和,报告数字全部作废。

5. 验证与进阶:让压测结果经得起追问

压测报告最容易被质疑的一点是「你这个数字是怎么来的」。要让结果站得住,得做两件事:一是用受控实验验证工具本身没骗你,二是把压测纳入可重复的流程。

先做工具自证。拿一个已知性能的本地接口(比如一个只做字符串拼接的端点),用固定并发跑三轮,看三轮的 P95 波动是否在可接受范围;再人为在服务端加 100ms 延迟,确认报告里的 P50 相应上移约 100ms。这两步做完,你对这套工具在当前环境下的测量误差就有了底数,后面看到异常数字能快速判断是业务问题还是测量问题。

再做流程固化。把mvn gatling:test挂到 CI 的固定阶段,每次发版前跑同一套脚本、同一套注入模型,报告归档对比。对比时只看三个指标:P95、错误率、吞吐量,其余指标波动大、参考价值有限。注入模型不要每次改,改了就没有可比性;要试新模型,单独开一条流水线。

验证项做法通过标准
工具测量准确性已知延迟接口对照报告分位数偏移与人为延迟一致
结果可重复性同脚本连跑三轮P95 波动在 ±10% 内
断言有效性故意设不可达阈值进程非零退出
压测机余量观察压测机 CPU/GC压测机不先饱和

还有一个容易忽略的点:报告里的「响应时间」是从发出请求到收完响应的全链路时间,包含网络和序列化,不等于服务端处理时间。跟研发对数字时先说清口径,否则容易各说各话。我一般会在报告旁边附一句「本报告为端到端时间,服务端耗时请对照 APM」,省掉很多来回。

从那以后我每次压测前都强制走一遍「JDK 版本确认 + 示例脚本跑通 + 断言故意失败验证」这三步,宁可多花十分钟,也不让一份数字可疑的报告流到决策桌上。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询