ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Gatling 3.0.0 安装与压测环境配置实战指南

Gatling 3.0.0 安装与压测环境配置实战指南

简介:Gatling 3.0.0 是一款面向现代 Web 应用的高效性能测试工具,适合开发者与测试工程师用于模拟高并发用户负载,验证系统在压力下的响应时间、吞吐量与资源利用率。其脚本基于 Scala DSL 编写,可读性与可维护性较强,支持场景定义、执行速率设置、检查点与会话变量配置,并借助 Highcharts 生成交互式图表,直观呈现每秒请求数、平均响应时间与百分位分布,便于定位性能瓶颈。资源包共 114 个文件,以 97 个 jar 依赖库为主体,辅以 6 个 scala 脚本、3 个 conf 配置文件及 bat、sh 启动脚本等,压缩包约 57.17MB,解压后可直接运行录制与压测命令。目前已有 237 人学习下载,适合希望快速搭建性能测试环境、理解 Gatling 脚本结构与报告解读的读者参考使用。

1. 从一次压测环境翻车说起:gatling3.0.0 到底该怎么装

上周帮一个做接口压测的团队收拾残局,机器上同时躺着三个 JDK,JAVA_HOME指向的是 1.7,结果 gatling3.0.0 一启动就抛UnsupportedClassVersionError,日志刷了满屏,新人以为是包下坏了,反复重下三遍。这类环境问题在性能测试场景里太常见了:gatling3.0.0 本身是个 Scala 写的压测工具,跑起来依赖 JVM,版本对不上、路径没配好、脚本放错目录,任何一环出问题都表现为“跑不起来”,但根因各不相同。这篇笔记按我实际部署的顺序走一遍——先定版本和环境,再解压、配参数、写第一个压测脚本,最后落到怎么验证它真的在压、压得对不对。适合刚接触性能测试 gatling3.0 的开发者,也适合想把这套环境固化成团队标准的熟手。

2. 装前定版本:gatling3.0.0 的 JDK 与目录规划

2.1 为什么 gatling3.0.0 对 JDK 这么挑

gatling3.0.0 是 2016 年前后的版本,编译目标是 Java 8,运行在 JDK 8 上最稳。它内部用 Scala 2.11 编译,Scala 2.11 的字节码在 JDK 7 上跑不起来,在 JDK 9 及以上又会碰到模块化带来的反射限制,常见表现是启动时报IllegalAccessError或者类加载失败。所以版本选择上没有太多余地:JDK 8,且建议 8u151 之后的更新版本,早期 8u 版本在 TLS 和字符集上有过坑。我一般会固定用某个 8u 小版本,团队里所有人统一,避免“我这能跑你那不能跑”的玄学问题。

先确认机器上现有的 Java 情况,别急着装:

# 查看当前 java 版本和实际路径 java -version which java readlink -f $(which java) # 列出系统里所有已安装的 JDK(Debian/Ubuntu 系) update-alternatives --list java 2>/dev/null || ls -l /usr/lib/jvm/

java -version输出里重点看第二行的版本号,1.8.0_xxx才是我们要的。readlink -f是为了看穿软链,很多机器上which java指向/usr/bin/java,实际是/etc/alternatives/java再链到具体 JDK,只看第一层会误判。如果输出是1.7或11,别在这台机器上硬装,先决定是切JAVA_HOME还是换机器。

提示:不要用系统包管理器装完 JDK 就直接开跑,JAVA_HOME和PATH两处都要指向同一个 JDK,否则 gatling 启动脚本读的是JAVA_HOME,你终端里java -version读的是PATH,两边不一致时排查会非常痛苦。

2.2 目录结构怎么摆才不给自己挖坑

gatling3.0.0 是绿色包,解压即用,但目录规划决定了后面脚本、报告、依赖 jar 好不好管。我习惯的布局是这样:

# 统一放在 /opt 下,按工具名+版本建目录 sudo mkdir -p /opt/gatling # 假设安装包已下载到当前目录 sudo tar -zxvf gatling-charts-highcharts-bundle-3.0.0-bundle.tar.gz -C /opt/gatling # 建一个不带版本号的软链,脚本和文档里统一引用这个路径 sudo ln -sfn /opt/gatling/gatling-charts-highcharts-bundle-3.0.0 /opt/gatling/current # 确认目录内容 ls /opt/gatling/current

解压出来的目录里,bin/放启动脚本,conf/放gatling.conf和日志配置,lib/是全部依赖 jar,user-files/才是我们写压测脚本和放数据文件的地方,results/是每次运行生成的报告。用软链current指向具体版本,好处是升级时只改软链,脚本里的路径不用动;坏处是有人cd进去后pwd看到的是软链路径,写相对路径时容易绕晕,所以团队里约定一律用绝对路径/opt/gatling/current/...。

user-files/下面还有simulations/和resources/两个子目录,前者放 Scala 压测脚本,后者放 CSV 参数文件。这两个目录名是 gatling 启动时按约定扫描的,改名字会导致脚本找不到,这是新手最容易踩的第一个坑。

2.3 环境变量与启动脚本的取舍

环境变量只需要两个:JAVA_HOME和可选的GATLING_HOME。GATLING_HOME不是必须的,因为bin/gatling.sh会自己推断所在目录,但显式设置后,从任意路径调用都不会出问题。

# 写入当前用户的 shell 配置,按实际 shell 选 .bashrc 或 .zshrc cat >> ~/.bashrc <<'EOF' export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 export PATH=$JAVA_HOME/bin:$PATH export GATLING_HOME=/opt/gatling/current export PATH=$GATLING_HOME/bin:$PATH EOF # 立即生效 source ~/.bashrc # 验证 echo $JAVA_HOME gatling.sh --help 2>&1 | head -5

这里JAVA_HOME的路径要换成你机器上真实的 JDK 8 路径,前面readlink -f查到的结果去掉/bin/java就是。PATH里把$JAVA_HOME/bin放最前面,保证java命令优先用我们指定的这个。gatling.sh --help能打出用法说明,就说明脚本本身能跑、JAVA_HOME被正确读取了;如果报JAVA_HOME is not set,说明 shell 配置没生效或者路径写错,回到上一步核对。

注意:gatling.sh在 3.0.0 里对JAVA_HOME的检查比较严格,路径末尾带斜杠、路径里有空格、软链指向不存在的目录,都会导致它判定失败。路径里不要有空格,这是硬要求。

3. 解压安装与第一个压测脚本:从零到能跑

3.1 安装包获取与校验

gatling3.0.0 的发行包命名是gatling-charts-highcharts-bundle-3.0.0-bundle.tar.gz,从官方发行渠道拿到后,先做完整性校验再解压。压缩包损坏的表现是解压到一半报unexpected end of file,或者解压成功但lib/里少 jar,启动时报ClassNotFoundException。

# 计算校验和,和发布页给出的值比对 sha256sum gatling-charts-highcharts-bundle-3.0.0-bundle.tar.gz # 解压前先看包内结构,确认顶层目录名 tar -tzf gatling-charts-highcharts-bundle-3.0.0-bundle.tar.gz | head -5

sha256sum的输出是一串十六进制,和来源页面的值逐位比对,不一致就重下,不要抱侥幸心理。tar -tzf只列目录不解压,用来确认顶层目录名是不是gatling-charts-highcharts-bundle-3.0.0,因为后面建软链要用到这个准确名字,名字对不上软链就是断的。

解压命令前面已经给过,这里补一个细节:-C /opt/gatling指定解压目标,-z是 gzip,-x解压,-v显示过程,-f指定文件。顺序上f必须紧跟文件名,写成-zxfv在某些老版本 tar 上会解析错,稳妥写法是-zxvf。

3.2 用 recorder 还是手写脚本

gatling3.0.0 自带一个 Recorder,可以抓浏览器请求生成脚本,但实际压测里我很少用它生成最终脚本——录出来的代码冗余多、参数写死、断言缺失,改起来比手写还费劲。Recorder 的正确用法是拿它生成骨架,再手工改。启动方式:

# 启动 recorder,默认监听 8000 端口作为代理 $GATLING_HOME/bin/recorder.sh

启动后界面里配置目标地址、选择输出目录到user-files/simulations/,浏览器代理指到本机 8000 端口,操作一遍业务流程,停止录制就会生成一个.scala文件。这个文件能直接跑,但里面的 URL、请求体、思考时间都是录制时的原样,压测前必须改。

手写脚本的话,放在user-files/simulations/下,文件名任意,但类名要和文件名一致。下面是一个最小可用的压测脚本,压一个查询接口:

package computerdatabase import io.gatling.core.Predef._ import io.gatling.http.Predef._ import scala.concurrent.duration._ class BasicSimulation extends Simulation { // 1. 定义 HTTP 协议基础配置 val httpConf = http .baseUrl("http://127.0.0.1:8080") // 被测服务地址,不要带末尾斜杠 .acceptHeader("application/json") // 默认 Accept 头 .userAgentHeader("gatling-3.0.0-loadtest") // 2. 定义请求场景 val scn = scenario("Query API") .exec( http("query_item") .get("/api/item/1") // 相对路径,拼在 baseUrl 后 .check(status.is(200)) // 断言状态码,失败会计入错误率 .check(jsonPath("$.id").is("1")) // 断言响应体字段 ) .pause(1.second, 2.second) // 每次请求后随机停 1~2 秒 // 3. 注入策略:10 秒内爬升到 20 个并发用户 setUp( scn.inject(rampUsers(20) over (10.seconds)) ).protocols(httpConf) }

逐段说:baseUrl是 3.0.0 里推荐的写法,替代了老版本的baseURL,写错大小写会编译不过;check(status.is(200))是断言,没有断言的压测等于没压,因为服务返回 500 你也看不出来;jsonPath断言依赖响应是合法 JSON,如果接口返回的是纯文本,这条会报解析错误,要换成bodyString匹配。rampUsers(20) over (10.seconds)是爬升注入,10 秒内逐步加到 20 个用户,比atOnceUsers更接近真实流量,避免瞬间打满把服务打挂导致数据失真。pause里的随机区间用1.second, 2.second两个参数,表示在区间内随机取值,写死一个值会让所有虚拟用户节奏一致,压出来的曲线不真实。

3.3 编译与运行:第一次跑通要看什么

脚本放好后,用gatling.sh启动。3.0.0 支持交互式选择 simulation,也支持命令行直接指定:

# 交互式:列出所有 simulation 让你选 $GATLING_HOME/bin/gatling.sh # 命令行直接指定类名,适合 CI 里跑 $GATLING_HOME/bin/gatling.sh -s computerdatabase.BasicSimulation

第一次跑建议用交互式,因为编译错误会直接打在终端里,看得清楚。启动后它会先编译user-files/simulations/下的所有 Scala 文件,编译阶段报错通常是语法问题或者 import 缺失,错误信息里带行号,照着改就行。编译通过后进入运行,终端会滚动输出请求统计,跑完在results/下生成一个以时间戳命名的目录,里面index.html就是报告。

运行阶段重点看三个数:请求总数、成功率和响应时间分布。如果成功率不是 100%,先别急着调注入策略,回到脚本里看是哪条断言挂了;如果响应时间 P99 高得离谱但服务端监控正常,多半是压测机自己成了瓶颈,用top看下 gatling 进程的 CPU,单机压测时 gatling 自身吃满一个核很常见。

提示:3.0.0 默认的 JVM 堆是 1G 左右,压测规模大时要在gatling.sh里调JAVA_OPTS,把-Xmx提到 2G 以上,否则会看到OutOfMemoryError或者 GC 频繁导致的数据抖动。

4. 配置调优与参数取舍:gatling.conf 里改什么

4.1 gatling.conf 的分层结构

conf/gatling.conf是 HOCON 格式,分gatling、http、data、charting几大块。3.0.0 的默认配置里,大部分项是注释掉的,取消注释才生效。改之前先备份:

cp $GATLING_HOME/conf/gatling.conf $GATLING_HOME/conf/gatling.conf.bak

常改的几项和取舍:

配置项默认值建议值说明
gatling.core.runDescription空项目名+日期报告标题,方便回溯
gatling.core.outputDirectoryBaseName空固定前缀报告目录名前缀,便于归档
http.ahc.allowPoolingConnectionstruetrue连接池复用,压测必开
http.ahc.maxConnectionPoolSize0(不限)按并发数设设太小会排队,设太大占文件句柄
data.writers.console.period51~2终端刷新间隔,秒
charting.indicators.percentile19595报告里展示的百分位

maxConnectionPoolSize是最容易设错的一项。默认 0 表示不限制,但操作系统对单进程文件句柄有上限,并发上千时会出现Too many open files。我一般按“并发用户数 × 1.2”来设,留一点余量,同时把ulimit -n调到 65535 以上。

4.2 JVM 参数怎么调

gatling.sh里通过JAVA_OPTS传 JVM 参数,直接改脚本或者在调用时覆盖:

# 临时覆盖,不改脚本 JAVA_OPTS="-Xms2g -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=200" \ $GATLING_HOME/bin/gatling.sh -s computerdatabase.BasicSimulation

-Xms和-Xmx设成一样大,避免堆动态扩缩带来的停顿;UseG1GC在 JDK 8 上可用,对大堆更友好;MaxGCPauseMillis是 G1 的目标停顿时间,设 200ms 是压测场景的常见折中,设太小会让 GC 更频繁反而拖慢。如果压测机内存紧张,-Xmx不要超过物理内存的一半,留空间给操作系统缓存。

4.3 参数化与数据供给

压测里经常要用不同参数打接口,3.0.0 用 feeder 从 CSV 读数据:

// 读取 resources 目录下的 CSV,文件放在 user-files/resources/ val userFeeder = csv("users.csv").circular val scn = scenario("Param Query") .feed(userFeeder) // 每次迭代取一行 .exec( http("query_by_id") .get("/api/item/${id}") // ${id} 引用 CSV 里的列名 .check(status.is(200)) )

csv("users.csv")的路径是相对user-files/resources/的,文件第一行是列名,circular表示循环读取,读完了从头再来;另一种random是随机取,适合模拟无序流量。${id}是 Gatling 的表达式语法,列名必须和 CSV 表头完全一致,大小写敏感。CSV 文件编码用 UTF-8,带 BOM 的话第一列列名会多出不可见字符,导致${id}取不到值,这是很隐蔽的一个坑。

5. 常见问题排查:五条踩坑记录

5.1 启动报 UnsupportedClassVersionError

现象:执行gatling.sh后立刻抛java.lang.UnsupportedClassVersionError: ... Unsupported major.minor version 52.0。

原因:运行时的 JDK 版本低于编译目标。52.0 对应 Java 8,说明 gatling 的 class 是 Java 8 编译的,但当前JAVA_HOME指向的是 JDK 7 或更低。

解决:echo $JAVA_HOME确认路径,$JAVA_HOME/bin/java -version确认版本,改 shell 配置指向 JDK 8 后重新source。注意PATH里的java和JAVA_HOME可能不是同一个,两处都要改。

5.2 脚本编译通过但运行时报 NoSuchMethodError

现象:编译阶段正常,运行到某个请求时抛NoSuchMethodError或NoClassDefFoundError。

原因:lib/目录里混入了版本冲突的 jar,常见于有人往lib/里手动丢了自己的依赖包,和 gatling 自带的 Scala 库版本打架。

解决:ls $GATLING_HOME/lib | grep scala看 Scala 库版本,3.0.0 对应 2.11.x,多出来的 jar 移走。自定义依赖不要放lib/,放到单独的目录用--class-path引入。

5.3 报告里成功率 100% 但业务数据不对

现象:压测报告显示全部成功,但查数据库发现请求根本没落库,或者参数全是同一个值。

原因:脚本里没有断言业务字段,只断言了 HTTP 200;或者 feeder 没生效,${id}被当成了字面量。

解决:加jsonPath或bodyString断言关键字段;检查 feeder 是否在exec之前feed,以及 CSV 列名是否匹配。压测脚本的断言要覆盖业务语义,不能只看状态码。

5.4 并发上不去,响应时间却很低

现象:设了 500 并发,但报告里实际并发只有几十,响应时间还很漂亮。

原因:注入策略写成了atOnceUsers但被pause拉长了单用户周期,或者maxConnectionPoolSize设得太小导致请求排队。

解决:用rampUsers或constantUsersPerSec控制到达率;检查gatling.conf里的连接池配置;用top看压测机 CPU 是否已满,单机压测有上限,超过就得上分布式。

5.5 报告目录越积越多,磁盘被撑爆

现象:跑了一段时间后results/下几百个目录,磁盘告警。

原因:每次运行都生成完整报告,默认不清理。

解决:在gatling.conf里设outputDirectoryBaseName加日期前缀,配合定时任务清理超过 N 天的目录;或者把results/软链到大容量分区。我一般写个 cron,每周清一次 7 天前的报告,保留最近几次用于对比。

6. 装完怎么验证:三条命令确认环境真的可用

环境装完不等于能用,我习惯用三条命令做验收,缺一条都不算过。

第一条,确认版本链路一致:

$GATLING_HOME/bin/gatling.sh --version 2>&1 | head -3 $JAVA_HOME/bin/java -version 2>&1 | head -1

两条输出里的版本要能对上——gatling 3.0.0 配 Java 8。如果gatling.sh --version报错,说明脚本本身或JAVA_HOME有问题,后面的都不用试了。

第二条,跑一个内置的最小场景,确认编译、注入、报告生成整条链路通:

# 用官方自带的示例 simulation 跑一遍,不依赖自己写的脚本 $GATLING_HOME/bin/gatling.sh -s computerdatabase.ComputerDatabaseSimulation -nr

-nr表示不生成报告,只跑请求,适合快速冒烟。这条能跑完且终端最后打印出Simulation computerdatabase.ComputerDatabaseSimulation completed,说明运行时没问题。如果报找不到 simulation,检查user-files/simulations/下示例文件是否被误删。

第三条,验证报告能正常打开且数据自洽:

# 跑一次带报告的,然后看结果目录 $GATLING_HOME/bin/gatling.sh -s computerdatabase.BasicSimulation ls -lt $GATLING_HOME/results/ | head -3

打开最新目录里的index.html,重点核对三处:请求总数是否等于“并发数 × 每用户请求数”的量级、成功率是否 100%、响应时间分布里 P95 和均值是否在合理比例(P95 远高于均值说明有长尾,可能是 GC 或连接池排队)。这三处对不上,说明注入策略或断言有问题,回到脚本里查。

我一般还会加一步:把同一脚本连跑三次,看三次的成功率和 P95 波动是否在 10% 以内。波动过大说明压测机本身不稳定,或者被测服务有缓存预热效应,这样的数据不能作为容量结论。从那以后我每次交付压测环境,都强制走一遍这三条命令加连跑三次的流程,宁可多花十分钟,也不在数据失真上返工。希望帮到你。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进