此文将围绕gatling的讲解,之前讲解过locust(基于python语言的性能测试框架),gatling是基于Scala语言开发的性能测试框架,可以使用Scala 语言、Kotlin语言、Java 语言进行开发和调试。
一、常用性能测试工具区别
首先说下jmeter、locust、gatling区别:JMeter使用的是多线程模型,Locust使用的是事件循环模型,而Gatling使用的是Actor模型:
jmeter多线程切换的时候资源消耗比较多,与locust、gatling相比同等资源的情况下,产生的有效并发数量会小很多,且多线程也相对容易产生错误,比如共享数据错乱,死锁等。下图为多线程并发模型图:
locust可在一个线程里面完成大量的并发,避免了多线程带来的各种问题。自身缺陷存在无法同时使用多核处理器,不过可以通过Locust提供了分布式的方法来使用多核。下图为消息循环并发模型图:
Gatling使用Actor模型的轻量和高并发性,加上Scala语言基于JVM,所以Gatling的并发模型结合了JMeter和Locust的优势,Actor模型核心是基于消息传递的,并且使用每个虚拟用户基于一个Actor就可以做到相对独立,并通过消息传递进行通信。下图为Actor并发模型图:
因此在实际情况中我们尽量有限选择gatling来开展性能测试工作。
二、gatling的配置及脚本编写
1、文件目录介绍
这里我们介绍使用开源代码,直接去下载。我这下载的为3.9.3版本,解压出来即可使用。
解压后为:
其中文件user_files:存放被测试的脚本
results:测试报告文件
lib:Gatling自身依赖库
conf:配置文件
bin:启动脚本,其中一个运行测试、一个启动录制脚本UI页面;区分windows、linux环境脚本
其中,user_files是使用最频繁的,user_files下分为lib文件、resources文件和simulations文件,lib文件存放被测脚本可能需要的三方库,resources文件存放测试时需要的数据;simulations文件下存放被测脚本,
可在simulations文件目录下再以项目/模块维度创建文件夹/包,在运行时会查出simulations文件下所有的内容,其中如下图:computerdatabase为包名(文件名),ComputerDatabaseSimulation为脚本名;zverencode包(文件夹)下有多个被测脚本。
其次就是results文件的查看,每次执行性能测试成功后都会自动生成测试报告,并存放至此,报告的文件名会以被测脚本的名称+时间命名,如下:
具体的报告是以html文件展示,报告中的具体内容最后讲解
2、被测脚本编写
脚本代码主要分为三块:1、请求基本信息,2、请求的具体数据,3、并发配置。编程语言可用Java、Kotlin、Scala。
请求的基本信息,header数据可单独写个hashmap往里面写入需要的数据
HttpProtocolBuilder httpProtocol = http
.baseUrl("http://zveren.zv.com")
.inferHtmlResources()
.acceptEncodingHeader("gzip, deflate")
.contentTypeHeader("application/json")
.userAgentHeader("Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36 MicroMessenger/7.0.9.501 NetType/WIFI MiniProgramEnv/Windows WindowsWechat");
请求的具体数据,可在同一个ChainBuilder内写入多个请求,也可在不同ChainBuilder中写入多个
ChainBuilder test1 =
exec(http("findById").post("/zveren/findById")
.body(StringBody("{\"token\":\"" + token + "\",\"appType\":10}")).asJson()
.check(jsonPath("$.code").is("00000")))
;
并发配置,此配置是配置一共多少用户数,在指定时间段内运行完。injectOpen中的配置可(rampUsers(10).during(10)), rampUsers为一共的用户数,during为运行的时间,即在during时间内请求rampUsers次,during中指定时间单位分钟Duration.ofMinutes(20)或小时Duration.ofHours(2)
setUp(scn.injectOpen(rampUsers(10).during(10))).protocols(httpProtocol);
运行环境还需要scala,所以需要在idea工具中安装scala插件,
三、录制及运行
运行bin/recorder文件、会打开一个UI界面,其中配置Listening port监听端口,端口没有冲突都可以,package为包名(会在user_files/simulations在生成一个此名称的包(文件夹)),class name为脚本名称,配置好后,点击右下角start按钮进入running页面,抓取到的接口数据会在Executed Events中显示,获取到数据后点击左上角stop&save按钮保存录制的数据,此时simulations文件中就会多一个zverencode的文件里面并且有一个RecordedSimulation1的脚本文件。
可直接用此录制的脚本文件进行压力测试。
启动运行,执行bin文件下的gatling.bat或者gatling.sh文件
选择菜单,选1就行了,运行本地的文件,输入1直接回车
GATLING_HOME is set to D:\zveren\Gatling\gatling-charts-highcharts-bundle-3.9.3
Do you want to run the simulation locally, on Gatling Enterprise, or just package it?
Type the number corresponding to your choice and press enter
[0] <Quit>
[1] Run the Simulation locally
[2] Package and upload the Simulation to Gatling Enterprise Cloud, and run it there
[3] Package the Simulation for Gatling Enterprise
[4] Show help and exit
1
选择被测脚本,上面录制的4也在此列表中,输入数值回车即可,等待运行结束
Choose a simulation number:
[0] computerdatabase.ComputerDatabaseSimulation
[1] zverencode.ComputerDatabase1
[2] zverencode.ZverenRecorded
[3] zverencode.ZverenRecordedSimulation
[4] zverencode.RecordedSimulation1
运行日志,各种异常都会在此反应出来,记录了详细的运行结果,测试报告也在此生成
Select run description (optional)
Simulation computerdatabase.ComputerDatabaseSimulation started...
================================================================================
2023-04-27 17:07:34 5s elapsed
---- Requests ------------------------------------------------------------------
> Global (OK=0 KO=14 )
> Home (OK=0 KO=6 )
> Search (OK=0 KO=5 )
> Page 0 (OK=0 KO=3 )
---- Errors --------------------------------------------------------------------
> j.l.IllegalArgumentException: Protocol TLSv1.3 is not supporte 14 (77.78%)
d.
> Select: Failed to build request: No attribute named 'computerU 4 (22.22%)
rl' is defined
---- Users ---------------------------------------------------------------------
[--------------------------------------------- ] 0%
waiting: 4 / active: 6 / done: 0
---- Admins --------------------------------------------------------------------
[--------------------------------------------------------------------------] 0%
waiting: 0 / active: 2 / done: 0
================================================================================
================================================================================
2023-04-27 17:07:39 10s elapsed
---- Requests ------------------------------------------------------------------
> Global (OK=0 KO=50 )
> Home (OK=0 KO=12 )
> Search (OK=0 KO=11 )
> Page 0 (OK=0 KO=9 )
> Page 1 (OK=0 KO=7 )
> Page 2 (OK=0 KO=5 )
> Page 3 (OK=0 KO=4 )
> Form (OK=0 KO=2 )
---- Errors --------------------------------------------------------------------
> j.l.IllegalArgumentException: Protocol TLSv1.3 is not supporte 50 (83.33%)
d.
> Select: Failed to build request: No attribute named 'computerU 10 (16.67%)
rl' is defined
---- Users ---------------------------------------------------------------------
[##############------------------------------------------------------------] 20%
waiting: 0 / active: 8 / done: 2
---- Admins --------------------------------------------------------------------
[#####################################-------------------------------------] 50%
waiting: 0 / active: 1 / done: 1
================================================================================
17:07:40.046 [ERROR] i.g.h.a.HttpRequestAction - 'Select' failed to execute: No attribute named 'computerUrl' is defined
17:07:41.074 [ERROR] i.g.h.a.HttpRequestAction - 'Select' failed to execute: No attribute named 'computerUrl' is defined
================================================================================
2023-04-27 17:07:44 15s elapsed
---- Requests ------------------------------------------------------------------
> Global (OK=0 KO=73 )
> Home (OK=0 KO=12 )
> Search (OK=0 KO=12 )
> Page 0 (OK=0 KO=12 )
> Page 1 (OK=0 KO=12 )
> Page 2 (OK=0 KO=11 )
> Page 3 (OK=0 KO=10 )
> Form (OK=0 KO=4 )
---- Errors --------------------------------------------------------------------
> j.l.IllegalArgumentException: Protocol TLSv1.3 is not supporte 73 (85.88%)
d.
---- Users ---------------------------------------------------------------------
[###################################################-----------------------] 70%
waiting: 0 / active: 3 / done: 7
---- Admins --------------------------------------------------------------------
[##########################################################################]100%
waiting: 0 / active: 0 / done: 2
================================================================================
================================================================================
2023-04-27 17:07:47 17s elapsed
---- Requests ------------------------------------------------------------------
> Global (OK=0 KO=76 )
> Home (OK=0 KO=12 )
> Search (OK=0 KO=12 )
> Page 0 (OK=0 KO=12 )
> Page 1 (OK=0 KO=12 )
> Page 2 (OK=0 KO=12 )
> Page 3 (OK=0 KO=12 )
> Form (OK=0 KO=4 )
---- Errors --------------------------------------------------------------------
> j.l.IllegalArgumentException: Protocol TLSv1.3 is not supporte 76 (86.36%)
d.
> Select: Failed to build request: No attribute named 'computerU 12 (13.64%)
rl' is defined
---- Users ---------------------------------------------------------------------
[##########################################################################]100%
waiting: 0 / active: 0 / done: 10
---- Admins --------------------------------------------------------------------
[##########################################################################]100%
waiting: 0 / active: 0 / done: 2
================================================================================
Simulation computerdatabase.ComputerDatabaseSimulation completed in 17 seconds
Parsing log file(s)...
Parsing log file(s) done
Generating reports...
================================================================================
---- Global Information --------------------------------------------------------
> request count 76 (OK=0 KO=76 )
> min response time 238 (OK=- KO=238 )
> max response time 1261 (OK=- KO=1261 )
> mean response time 277 (OK=- KO=277 )
> std deviation 123 (OK=- KO=123 )
> response time 50th percentile 257 (OK=- KO=257 )
> response time 75th percentile 264 (OK=- KO=264 )
> response time 95th percentile 281 (OK=- KO=281 )
> response time 99th percentile 720 (OK=- KO=720 )
> mean requests/sec 4.222 (OK=- KO=4.222 )
---- Response Time Distribution ------------------------------------------------
> t < 800 ms 0 ( 0%)
> 800 ms <= t < 1200 ms 0 ( 0%)
> t >= 1200 ms 0 ( 0%)
> failed 76 (100%)
---- Errors --------------------------------------------------------------------
> j.l.IllegalArgumentException: Protocol TLSv1.3 is not supporte 76 (86.36%)
d.
> Select: Failed to build request: No attribute named 'computerU 12 (13.64%)
rl' is defined
================================================================================
Reports generated in 0s.
Please open the following file: file:///D:/zveren/Gatling/gatling-charts-highcharts-bundle-3.9.3/results/computerdatabasesimulation-20230427090727829/index.ht
ml
四、高级配置
1、按场景配置并发
按场景配置并发通过标注不同的ChainBuilder来实现,将需要差异并发的接口写入不同的ChainBuilder中,在通过配置scenario来对不同的ChainBuilder配置不同的并发值,针对users、admins配置不同的rampusers和during值。此外protocols是配置使用的协议,协议配置可单独针对性的配置到injectOpen下,也可针对全局配置到setUp下。
/** test1 */
ChainBuilder test1 =
exec(http("findById").post("/zveren/findById")
.body(StringBody("{\"token\":\"" + token + "\",\"appType\":100}")));
/** test2 */
ChainBuilder test2 =
exec(http("bannerFetch").post("/zveren/fetch")
.body(StringBody("{\"token\":\"" + token + "\",\"appType\":100}")));
/** 配置不同的并发场景 */
ScenarioBuilder users = scenario("Users").exec(test1);
ScenarioBuilder admins = scenario("Admins").exec(test2);
/** during秒内 增加rampUsers用户数 */
{setUp(users.injectOpen(rampUsers(15000).during(600)),
admins.injectOpen(rampUsers(15000).during(600))
).protocols(httpProtocol);}
2、参数化
读取resources文件夹中准备的数据,searchCriterion为数据中的字段名
FeederBuilder<String> feeder = csv("search.csv").random(); //随机赋值入参
exec(http("Home").get("/"))
.feed(feeder)
.exec(
http("Search")
.get("/computers?f=#{searchCriterion}")
3、循环请求同一个接口
repeat:重复循环指定的次数,包含两个参数
times
:循环内容的重复次数,可以是整数、加特林EL字符串或函数counterName
(可选):将循环计数器存储在 中的键,从 0 开始Session
repeat(5, "counter").on(
exec(session -> {
System.out.println(session.getInt("counter"));
return session;}));
foreach:
对指定序列中的每个元素重复循环,包含三个参数
seq
:要迭代的元素列表,可以是列表、加特林 EL 字符串或函数elementName
:当前元素的键Session
counterName
(可选):将循环计数器存储在 中的键,从 0 开始Session
foreach(Arrays.asList("zveren1", "zveren2"), "zveren", "counter").on(
exec(session -> {
System.out.println(session.getString("zveren2"));
return session;}));
during:
在指定的时间内循环访问循环,包含三个参数
duration
:可以是持续时间(以秒为单位)、持续时间、加特林 EL 字符串或函数的 IntcounterName
(可选):将循环计数器存储在 中的键,从 0 开始Session
exitASAP
(可选,默认值 true):如果为 true,则将为循环中的每个元素评估条件,这可能会导致在到达迭代结束之前退出循环。
during(5, "counter", false).on(
exec(http("name").get("/")));
asLongAs:
只要满足条件,就遍历循环,包含三个参数
condition
:可以是布尔值、加特林 EL 字符串解析布尔值或函数counterName
(可选):将循环计数器存储在 中的键,从 0 开始Session
exitASAP
(可选,默认值 true):如果为 true,则将为循环中的每个元素评估条件,这可能会导致在到达迭代结束之前退出循环。
asLongAs("#{condition}", "counter", false).on(
exec(http("name").get("/")));
doWhile:
但条件是在循环后评估的
asLongAsDuring:
只要满足条件且未达到持续时间,就循环访问
doWhileDuring:
条件是在循环后评估的
forever:
永久循环循环内容
4、条件判断
doIf
:用于仅在满足某些条件时执行特定的操作链,参数为
condition
可以是布尔值、加特林 EL 字符串解析布尔值或函数
doIf("#{condition}").then(
exec(http("name").get("/"))
);
doIfEquals:
如果您的测试条件只是比较两个值,则可以简单地使用,包含两个参数
actual
可以是静态值、加特林 EL 字符串或函数expected
可以是静态值、加特林 EL 字符串或函数
doIfEquals("#{actual}", "expectedValue").then(
exec(http("name").get("/")));
doIfOrElse:
但如果条件计算结果为 false,则回退
doIfEqualsOrElse:
但如果条件计算结果为 false,则回退
doSwitch:
在链中添加开关。每个可能的子链都用一个键定义。 通过将键与传递表达式的计算进行匹配来选择开关。 如果未选择开关,则会旁路交换机
doSwitchOrElse:
但如果未选择开关,则具有回退功能
randomSwitch:
可用于模拟简单的马尔可夫链。 简单意味着当前不支持循环图
randomSwitchOrElse:
但如果未选择开关(即:随机数超过百分比总和),则回退
uniformRandomSwitch:
但在链间分布均匀
roundRobinSwitch:
但调度使用循环策略
5、body中json格式的转化,exec.asjson()
ChainBuilder test1 =
exec(http("findById").post("/zveren/findById")
.body(StringBody("{\"token\":\"" + token + "\",\"appType\":100}"))
.asJson()
6、断言
可以直接对返回状态status断言,
可以对指定的返回数据断言,jsonPath("$.code")
/** test1 */
ChainBuilder test1 =
exec(http("findById").post("/zveren/findById")
.body(StringBody("{\"token\":\"" + token + "\",\"appType\":100}"))
.check(jsonPath("$.code").is("00000")));
/** test2 */
ChainBuilder test2 =
exec(http("bannerFetch").post("/zveren/fetch")
.body(StringBody("{\"token\":\"" + token + "\",\"appType\":100}"))
.check(status().is(session -> 200)));
也可在sutup下配置断言
setUp().assertions(global().failedRequests().count().is(0L));
7、全局暂停配置
不太明白全局暂停的意义,但是官网确实给了全局暂停的配置
// 全局配置的暂停配置
setUp(scn.injectOpen(atOnceUsers(1)))
// 禁用模拟的暂停
.disablePauses()
// 指定每次暂停的持续时间
.constantPauses()
// 使暂停遵循均匀分布
.uniformPauses(0.5)
.uniformPauses(Duration.ofSeconds(2))
// 暂停持续时间遵循标准偏差
.normalPausesWithStdDevDuration(Duration.ofSeconds(2))
// 暂停持续时间遵循正态分布
.normalPausesWithPercentageDuration(20)
// 暂停持续时间遵循指数分布
.exponentialPauses()
// 暂停时间可单独提供指定函数计算(以毫秒为单位)
.customPauses(session -> 5L);
// 可在每个injectOpen上配置不同的暂停规则
setUp(
scn1.injectOpen(atOnceUsers(1))
.disablePauses(),
scn2.injectOpen(atOnceUsers(1))
.exponentialPauses()
);
8、并发模型及吞吐量配置
a、可按使用场景配置配置需要的并发模型,
//封闭模型
setUp(
scn.injectOpen(
// 暂停给定的持续时间
nothingFor(4),
// 一次注入给定数量的用户
atOnceUsers(10),
// 注入给定数量的用户,在给定持续时间的时间窗口上均匀分布
rampUsers(10).during(5),
// 在给定持续时间内以恒定速率(以每秒用户数定义)注入用户。用户将定期注射
constantUsersPerSec(20).during(15),
// 在给定持续时间内以恒定速率(以每秒用户数定义)注入用户。用户将以随机间隔注射
constantUsersPerSec(20).during(15).randomized(),
// 在给定持续时间内将用户从起始速率注入目标速率(以每秒用户数定义)。用户将定期注射
rampUsersPerSec(10).to(20).during(10),
// 在给定持续时间内将用户从起始速率注入目标速率(以每秒用户数定义)。用户将以随机间隔注射
rampUsersPerSec(10).to(20).during(10).randomized(),
// 在拉伸到给定持续时间的重边阶跃函数的平滑近似之后注入给定数量的用户
stressPeakUsers(1000).during(20)
).protocols(httpProtocol)
);
//打开模型
setUp(
scn.injectClosed(
// 使系统中的并发用户数保持不变
constantConcurrentUsers(10).during(10),
// 使系统中的并发用户数从一个数字线性增加到另一个数字
rampConcurrentUsers(10).to(20).during(10)
)
);
incrementUsersPerSec
开放工作负载与incrementConcurrentUsers封闭
工作负载(用户/秒与并发用户),times:递进次数,startingFrom:起始数,eachLevelLasting:每级持续10秒,separatedByRampsLasting:由持续10秒的线性斜坡分隔,具有10、15、20、25和30个并发用户
// incrementUsersPerSec
setUp(
scn.injectOpen(
incrementUsersPerSec(5.0)
.times(5)
.eachLevelLasting(10)
.separatedByRampsLasting(10)
.startingFrom(10)));
//incrementConcurrentUsers
setUp(
scn.injectClosed(
incrementConcurrentUsers(5)
.times(5)
.eachLevelLasting(10)
.separatedByRampsLasting(10)
.startingFrom(10)));
b、指定吞吐量
reachRps(target).in(duration)
:在给定持续时间内以斜坡为目标吞吐量。jumpToRps(target)
:立即跳转到给定的目标吞吐量。holdFor(duration)
:在给定的持续时间内保持当前吞吐量。
// 此模拟以100秒的斜坡达到80req/s,保持此吞吐量5分钟,之后立即跳到50req/s,再保持此吞吐量2小时。
setUp(scn.injectOpen(constantUsersPerSec(100).during(Duration.ofMinutes(30))))
.throttle(
reachRps(100).in(80),
holdFor(Duration.ofMinutes(5)),
jumpToRps(50),
holdFor(Duration.ofHours(2))
);
// 在不同的injectOpen中单独配置并发模型
setUp(
scn1.injectOpen(atOnceUsers(1))
.throttle(reachRps(100).in(10)),
scn2.injectOpen(atOnceUsers(1))
.throttle(reachRps(20).in(10))
);
9、顺序执行
启动顺序如下
zveren1和zveren2将在最后一个parent启动时同时启动;
grandZveren将在最后一个zveren1用户终止时启动;
zveren3将在最后一个grandZveren和zveren2用户终止时启动
setUp(
parent.injectClosed(injectionProfile)
.andThen(
zveren1.injectClosed(injectionProfile)
.andThen(
grandZveren.injectClosed(injectionProfile)),
zveren2.injectClosed(injectionProfile))
.andThen(
zveren3.injectClosed(injectionProfile)));
10、禁止并发模型运行
noShard方法屏蔽此模块运行,parent将不会执行,直接执行zveren1
setUp(
parent.injectOpen(atOnceUsers(1)).noShard()
.andThen(
zveren1.injectClosed(injectionProfile)));
还有更多的功能配置,可在官网查看。
另外官网案列中讲到了一个失败重试,我没有想到做性能测试时失败重试的意义是什么,是否有必要。有知道的大佬可讨论讨论。
五、报告说明
报告整体结构:两大子页面 Global和Details,下图为Global,左侧为大纲点击可使页面上下滑动锚定到指定位置,详细信息有:总的请求时间图、每个接口的请求数据、请求断言错误数据、请求用户数和时间的图表、响应时间分布图表、响应时间百分位分布图表、每秒请求数、每秒响应数。
详细信息页面,是以每个接口的数据显示,左侧为被测接口,点击可查看指定接口数据:响应时间、统计数据、断言错误数据、响应时间分布图、响应时间百分位图、每秒请求数、每秒响应数、吞吐量与时间关系图表。
参考文档: