当前位置:网站首页>Gatling 性能测试
Gatling 性能测试
2022-06-24 12:54:00 【还是转转】
Gatling是一个基于AKKA和Netty开发的高性能压测工具,使用非常简单。
概述和使用
Gatling可以直接下载使用,也可以通过maven插件在项目中集成,通过命令行执行。
直接使用参见官方网站(见参考资料1)。这里介绍通过maven插件来使用。
gatling-maven-plugin
首先引入依赖包,这个是测试脚本需要的:
<dependency>
<groupId>io.gatling.highcharts</groupId>
<artifactId>gatling-charts-highcharts</artifactId>
<version>MANUALLY_REPLACE_WITH_LATEST_VERSION</version>
<scope>test</scope>
</dependency>
然后引入依赖插件:
<plugin>
<groupId>io.gatling</groupId>
<artifactId>gatling-maven-plugin</artifactId>
<version>MANUALLY_REPLACE_WITH_LATEST_VERSION</version>
</plugin>
最好指定明确的插件版本。如果不指定插件版本,系统会自动查找最新的版本,这样可能无法保证构建可重复。因为无法保证插件将来的行为和当前是一致的。
对于Scala开发来说,从4.x版本开始,该插件不再编译Scala代码,如果测试类是用scala来写的,则必须要用scala-maven-plugin插件来代替。
为了方便,本文以前文创建好的项目代码为基础。新增一个hello-world-test-perform子模块,专门用来做负载测试。
在test包下创建测试类BasicSimulation:
import io.gatling.javaapi.core.*;
import io.gatling.javaapi.http.*;
import static io.gatling.javaapi.core.CoreDsl.*;
import static io.gatling.javaapi.http.HttpDsl.*;
public class BasicSimulation extends Simulation {
public final String hostname = System.getProperty("url");
HttpProtocolBuilder httpProtocol = http
.baseUrl(hostname)
.acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
.doNotTrackHeader("1")
.acceptLanguageHeader("en-US,en;q=0.5")
.acceptEncodingHeader("gzip, deflate")
.userAgentHeader("Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.0");
ScenarioBuilder scn = scenario("HelloWorldSimulation")
.exec(http("request_1").get("/data/hello"))
.pause(5);
{
//注入用户,刚开始就一个,协议是http
setUp(scn.injectOpen(atOnceUsers(1))).protocols(httpProtocol);
}
}
其中,hostname是从系统变量中获取的,这个是在plugin中配置的:
<plugin>
<groupId>io.gatling</groupId>
<artifactId>gatling-maven-plugin</artifactId>
<configuration>
<skip>${skipLTandPTs}</skip>
<jvmArgs>
<jvmArg>-Durl=${testTarget}</jvmArg>
</jvmArgs>
</configuration>
<executions>
<execution>
<phase>test</phase>
<goals>
<goal>test</goal>
</goals>
</execution>
</executions>
</plugin>
这个插件中skipLTandPTs和testTarget参数是从父文件中继承过来的。父文件pom.xml的配置如下:
...
<profiles>
<profile>
<id>local</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<environment>dev</environment>
<testTarget>http://localhost:8080/tt</testTarget>
</properties>
</profile>
<profile>
<id>pt</id>
<properties>
<skipTests>true</skipTests>
<skipLTandPTs>false</skipLTandPTs>
</properties>
</profile>
</profiles>
这样,运行hello-world项目后,在命令行执行:
mvn clean test -P local,pt
就能进行负载测试了。生成的结果默认放在target/gatling目录下,在浏览器中访问index.html如下:
基本概念
gatling中主要的对象包括:Simulation,Injection,Scenario,Session等。
Simulation
setUp
setUp是Simulation中的必要部分:
ScenarioBuilder scn = scenario("scn"); // etc...
{
setUp(
scn.injectOpen(atOnceUsers(1))
);
}
协议配置
协议配置可以写在setUp方法外面,表示对所有的场景生效;也可以写在每个场景上,表示对当前场景生效。如下所示:
// HttpProtocol configured globally
setUp(
scn1.injectOpen(atOnceUsers(1)),
scn2.injectOpen(atOnceUsers(1))
).protocols(httpProtocol);
// different HttpProtocols configured on each population
setUp(
scn1.injectOpen(atOnceUsers(1))
.protocols(httpProtocol1),
scn2.injectOpen(atOnceUsers(1))
.protocols(httpProtocol2)
);
验收条件
验收条件决定了本次负载测试能否通过,在setUp方法上配置。
setUp(scn.injectOpen(atOnceUsers(1)))
.assertions(global().failedRequests().count().is(0L));
setUp(scn.injectOpen(atOnceUsers(1)))
.assertions(
global().responseTime().mean().lt(2000),
global().successfulRequests().percent().gt(99.0)
);
Scenario
场景名可以是除了\t之外的任何字符:
ScenarioBuilder scn = scenario();
exec
exec方法用于执行模拟的接口调用,支持HTTP,LDAP,POP,IMAP等协议。
下面是HTTP协议的模拟请求示例:
// 绑定到scenario
scenario("Scenario")
.exec(http("Home").get("https://gatling.io"));
// 直接创建以便于后续引用
ChainBuilder chain = exec(http("Home").get("https://gatling.io"));
// 绑定到其他
exec(http("Home").get("https://gatling.io"))
.exec(http("Enterprise").get("https://gatling.io/enterprise"));
exec也可用于传递函数。通过这一特性可方便地设置和调试Session:
exec(session -> {
// displays the content of the session in the console (debugging only)
System.out.println(session);
// return the original session
return session;
});
exec(session ->
// return a new session instance
// with a new "foo" attribute whose value is "bar"
session.set("foo", "bar")
);
停顿
通常,当一个用户浏览页面时,两次操作之间会有时间间隔。为了模拟这一行为,gatling提高了停顿方法。
下面是一些使用方式:
暂停时间固定
pause(10); // with a number of seconds
pause(Duration.ofMillis(100)); // with a java.time.Duration
pause("#{pause}"); // with a Gatling EL string resolving to a number of seconds or a java.time.Duration
pause(session -> Duration.ofMillis(100)); // with a function that returns a java.time.Duration
暂时时间随机
pause(10, 20); // with a number of seconds
pause(Duration.ofMillis(100), Duration.ofMillis(200)); // with a java.time.Duration
pause("#{min}", "#{max}"); // // with a Gatling EL strings
pause(session -> Duration.ofMillis(100), session -> Duration.ofMillis(200)); // with a function that returns a java.time.Duration
循环
重复
如果某个请求重复发生,可以通过repeat来模拟。
// with an Int times
repeat(5).on(
exec(http("name").get("/"))
);
// with a Gatling EL string resolving an Int
repeat("#{times}").on(
exec(http("name").get("/"))
);
// with a function times
repeat(session -> 5).on(
exec(http("name").get("/"))
);
// with a counter name
repeat(5, "counter").on(
exec(session -> {
System.out.println(session.getInt("counter"));
return session;
})
);
遍历
可以按照指定顺序对列表中的每个元素依次执行action。主要有三个参数:
- seq:需要遍历的元素序列,可以是列表或Gatling EL表达式或函数
- elementName:key
- counterName(可选):循环计数器,从0开始
// with a static List
foreach(Arrays.asList("elt1", "elt2"), "elt").on(
exec(http("name").get("/"))
);
// with a Gatling EL string
foreach("#{elts}", "elt").on(
exec(http("name").get("/"))
);
// with a function
foreach(session -> Arrays.asList("elt1", "elt2"), "elt").on(
exec(http("name").get("/"))
);
// with a counter name
foreach(Arrays.asList("elt1", "elt2"), "elt", "counter").on(
exec(session -> {
System.out.println(session.getString("elt2"));
return session;
})
);
还有其他的一些循环操作,这里就不一一列举了。
错误处理
tryMax
tryMax可以指定重试次数,当action执行失败时,会进行重试。
tryMax(5).on(
exec(http("name").get("/"))
);
// with a counter name
tryMax(5, "counter").on(
exec(http("name").get("/"))
);
exitBlockOnFail
失败立即退出:
exitBlockOnFail(
exec(http("name").get("/"))
);
exitHere
指定虚拟用户从scenario退出:
exitHere();
exitHereIf
根据条件退出:
exitHereIf("#{myBoolean}");
exitHereIf(session -> true);
Injection
使用injectOpen和injectClosed方法来定义用户的注入配置信息(与Scala中的inject作用相同),该方法参数为一系列的注入步骤,处理时也按顺序处理。
Open和Closed工作负载模型
当谈到负载模型时,通常有两种类型的系统:
- Closed系统,可以用来控制并发用户数量
- Open系统,可以用来控制用户的到达率
封闭系统中并发用户数是有上限的,当并发数达到上限时,只有当有用户退出时,新的用户才能进入系统。
这与线程池工作模式是类似的,当工作线程占用满了的情况下,新的请求进入任务队列,等待有线程空闲下来才能继续处理。
售票业务系统一般需要采用封闭模型。
封闭模型适用于可以异步获取结果的系统
而开发系统与之相反,在开放系统中无法控制并发用户数量,即使业务系统已经不能处理多余的请求了,新的用户还是会持续不断地到来并发起请求。
大部分业务系统均是这种情况。
注意:请根据系统业务类型来决定采用哪一种测试模型。如果实际业务类型与测试的模型不匹配,就无法达到预期效果。
开放模型与封闭模型具有相反的含义,不要在同一个注入配置中混用。
开放模型
下面是一个开放模型的例子(其他语言见参考资料2):
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) // 在指定时间段内,按照 heaviside step 函数的平滑近似值注入指定数量的用户
).protocols(httpProtocol)
);
封闭模型
下面是一个封闭模型的例子:
setUp(
scn.injectClosed(
constantConcurrentUsers(10).during(10), // 在指定时间段内保持恒定的虚拟用户数
rampConcurrentUsers(10).to(20).during(10) // 在指定时间段内,虚拟用户数从一个值线性增长到另一个值
)
);
Meta DSL
在测试之前,通常我们并不知道系统吞吐量是多少。为了测试瓶颈值,可能会用不同的数值去做重复的操作来尝试,例如:
rampUsersPerSec(10).to(20).during(10),
rampUsersPerSec(20).to(30).during(10),
rampUsersPerSec(30).to(50).during(10),
rampUsersPerSec(50).to(70).during(10),
rampUsersPerSec(70).to(100).during(10),
);
为了解决这一问题,Gatling在3.0中增加了一种Meta DSL新的方法来方便我们操作。
incrementUsersPerSec(usersPerSecAddedByStage)
setUp(
// generate an open workload injection profile
// with levels of 10, 15, 20, 25 and 30 arriving users per second
// each level lasting 10 seconds
// separated by linear ramps lasting 10 seconds
scn.injectOpen(
incrementUsersPerSec(5.0)
.times(5)
.eachLevelLasting(10)
.separatedByRampsLasting(10)
.startingFrom(10) // Double
)
);
incrementConcurrentUsers(concurrentUsersAddedByStage)
setUp(
// generate a closed workload injection profile
// with levels of 10, 15, 20, 25 and 30 concurrent users
// each level lasting 10 seconds
// separated by linear ramps lasting 10 seconds
scn.injectClosed(
incrementConcurrentUsers(5)
.times(5)
.eachLevelLasting(10)
.separatedByRampsLasting(10)
.startingFrom(10) // Int
)
);
incrementUsersPerSec用于开放模型负载测试,incrementConcurrentUsers用于封闭模型负载测试。separatedByRampsLasting和startingFrom 都是可选的。
如果未指定坡度,则虚拟用户增长方式是跳跃的。如果未指定起始用户数,将从0开始。
并发场景
在同一个setUp中可以同时设置多个场景注入,然后同时并发执行
setUp(
scenario1.injectOpen(injectionProfile1),
scenario2.injectOpen(injectionProfile2)
);
有序场景
除了并发场景外,有的场景是有序的,可以通过andThen来设置有序场景。有序场景中,只有当父场景执行完成后,子场景才开始执行。
setUp(
parent.injectClosed(injectionProfile)
// child1 and child2 will start at the same time when last parent user will terminate
.andThen(
child1.injectClosed(injectionProfile)
// grandChild will start when last child1 user will terminate
.andThen(grandChild.injectClosed(injectionProfile)),
child2.injectClosed(injectionProfile)
)
);
Session
Session API
Session API可以编程式地处理用户数据。
大多数情况下,负载测试时有一点比较重要,那就是要保证虚拟用户的请求参数是不一样的。如果每个虚拟用户都使用相同的参数,那可能是在测试缓存,而不是测试实际系统负载。
更甚者,当你在Java虚拟机上执行测试用例时,JVM本身通过即时编译器(JIT)对代码进行了优化,从而导致得到了与实际生产环境中不同的性能结果。
Session
Session是虚拟用户的状态。
通常来说,session是一个Map<String, Object>结构。在Gatling中,session中的所有的键值对都是Session属性。
Gatling中的scenario是一个工作流,工作流中的每一步是一个Action,Session可以在工作流中传递数据。
设置属性
// set one single attribute
Session newSession1 = session.set("key", "whateverValue");
// set multiple attributes
Session newSession2 = session.setAll(Collections.singletonMap("key", "value"));
// remove one single attribute
Session newSession3 = session.remove("key");
// remove multiple attributes
Session newSession4 = session.removeAll("key1", "key2");
// remove all non Gatling internal attributes
Session newSession5 = session.reset();
Session 是不可变类,这意味着调用set方法后,将返回一个新的实例,而不是原来的实例。
// 错误用法: result from Session#set is discarded
exec(session -> {
session.set("foo", "bar");
System.out.println(session);
return session;
});
// 正确用法
exec(session -> {
Session newSession = session.set("foo", "bar");
System.out.println(newSession);
return newSession;
});
从session中获取用户属性
// the unique id of this virtual user
long userId = session.userId();
// the name of the scenario this virtual user executes
String scenario = session.scenario();
// the groups this virtual user is currently in
List<String> groups = session.groups();
函数
使用函数可以生成动态参数。如下所示:
// inline usage with a Java lamdba
exec(http("name")
.get(session -> "/foo/" + session.getString("param").toLowerCase(Locale.getDefault())));
// passing a reference to a function
Function<Session, String> f =
session -> "/foo/" + session.getString("param").toLowerCase(Locale.getDefault());
exec(http("name").get(f));
如果要使用随机生成的参数,必须要在函数中生成,如
.header("uuid", x -> RandomStringUtils.randomAlphanumeric(5)),而不能直接使用.header("uuid", RandomStringUtils.randomAlphanumeric(5)),这样只有第一次请求是随机值,后面的请求都使用第一次生成的值。
Feeders
在gatling中,可以通过外部方式如csv文件,向虚拟用户注入数据,此时需要用到Feeder。
Feeder实际上是迭代器Iterator<Map<String, T>>的别称。
下面是一个构造feeder的例子:
// import org.apache.commons.lang3.RandomStringUtils
Iterator<Map<String, Object>> feeder =
Stream.generate((Supplier<Map<String, Object>>) () -> {
String email = RandomStringUtils.randomAlphanumeric(20) + "@foo.com";
return Collections.singletonMap("email", email);
}
).iterator();
外部数据源使用策略
外部数据源的使用策略有多种:
// 默认: 对基础系列使用迭代器
csv("foo").queue();
// 随机选择序列中的记录
csv("foo").random();
// 按打乱之后的顺序取记录
csv("foo").shuffle();
// 当数据(从头到尾)取完后又从头开始
csv("foo").circular();
当使用queue和shuffle策略时,请保证你的数据量是足够的,一旦数据用完,gatling将会自动关闭。
使用列表和数组
当然,也可以使用内存中的列表或数组来给虚拟用户注入数据:
// using an array
arrayFeeder(new Map[] {
Collections.singletonMap("foo", "foo1"),
Collections.singletonMap("foo", "foo2"),
Collections.singletonMap("foo", "foo3")
}).random();
// using a List
listFeeder(Arrays.asList(
Collections.singletonMap("foo", "foo1"),
Collections.singletonMap("foo", "foo2"),
Collections.singletonMap("foo", "foo3")
)).random();
基于文件的Feeders
上面说到了外部数据注入时取数据的策略,如csv("foo").queue()。那这个csv文件应该放到哪里呢?
当使用构建工具如maven,gradle或sbt时,文件必须放在src/main/resourcese或者src/test/resources目录下。
文件路径不要使用相对路径
src/main/resources/data/file.csv,应该使用类路径:data/file.csv。
除了csv文件外,还有tsv/ssv/jsonFile/jsonUrl/jdbc/redis几种方式导入数据,具体见参考资料Session->Feeders章节。
Checks
Checks可以用来验证请求结果,并且可以提取返回结果信息以便复用。
Checks一般通过在父对象上调用check方法来实现,如下所示是一个http请求的checks:
http("Gatling").get("https://gatling.io")
.check(status().is(200))
当然,也可以一次定义多个checks:
http("Gatling").get("https://gatling.io")
.check(
status().not(404),
status().not(500)
)
check API提供了一个专用DSL,可以链接以下多个操作:
- 定义check类型
- 提取
- 转换
- 验证
- 命名
- 保存
常规检查类型
下面是一些常规的检查类型,并被大多数gatling支持的官方协议所实现。
responseTimeInMillis
.check(responseTimeInMillis().lte(100)) // 响应时间不大于100ms
bodyString
.check(
bodyString().is("{\"foo\": \"bar\"}"),
bodyString().is(ElFileBody("expected-template.json"))
)
bodyBytes
.check(
bodyBytes().is("{\"foo\": \"bar\"}".getBytes(StandardCharsets.UTF_8)),
bodyBytes().is(RawFileBody("expected.json"))
)
bodyLength
.check(bodyLength().is(1024))
bodyStream
返回完整响应体数据字节的输入流。某些情况下,当需要对返回数据在数据处理之前进行格式转换时使用。
.check(bodyStream().transform(is -> {
// 将Base64格式转换成String
try (InputStream base64Is = Base64.getDecoder().wrap(is)) {
return org.apache.commons.io.IOUtils.toString(base64Is, StandardCharsets.UTF_8.name());
} catch (IOException e) {
throw new RuntimeException("Impossible to decode Base64 stream");
}
}))
subString
该检查返回指定子字符串在响应文本中出现的索引位置。
通常用于检查子字符串是否存在,它比正则表达式的CPU效率更高。
.check(
// with a static value
// (identical to substring("expected").find().exists())
substring("expected"),
// with a Gatling EL
substring("#{expectedKey}"),
// with a function
substring(session -> "expectedValue"),
substring("Error:").notExists(),
// this will save a List<Int>
substring("foo").findAll().saveAs("indices"),
// this will save the number of occurrences of foo
substring("foo").count().saveAs("counts")
)
regex
.check(
// with a static value without capture groups
regex("<td class=\"number\">"),
// with a Gatling EL without capture groups
regex("<td class=\"number\">ACC#{account_id}</td>"),
// with a static value with one single capture group
regex("/private/bank/account/(ACC[0-9]*)/operations.html")
)
在Java15+,Scala和Kotlin中,你可以使用这种转移字符串:“”“my “non-escaped” string”“”,而无需用’’
XPath
该检查对XML响应体生效
.check(
// simple expression for a document that doesn't use namespaces
xpath("//input[@id='text1']/@value"),
// mandatory namespaces parameter for a document that uses namespaces
xpath("//foo:input[@id='text1']/@value", Collections.singletonMap("foo", "http://foo.com"))
)
更多Checks具体见参考资料Checks章节。
提取
提取操作可以让你过滤出期望的结果,然后就可以在后续的步骤中对结果进行处理了。
如果未显式定义提取操作,Gatling会默认执行find。
find
find可以过滤出单个元素。如果目标多次出现,find等同于find(0)。
.check(
// 下面两个是等效的。因为jjmesPath只返回一个值,所以find可以省略
jmesPath("foo"),
jmesPath("foo").find(),
// jsonPath可能返回多个值
// 下面三个是等效的,所以find可以省略
jsonPath("$.foo"),
jsonPath("$.foo").find(),
jsonPath("$.foo").find(0),
// 捕获第二次出现的元素
jsonPath("$.foo").find(1)
)
findAll
返回值有多个时生效
.check(
jsonPath("$.foo").findAll()
)
findRandom
.check(
// identical to findRandom(1, false)
jsonPath("$.foo").findRandom(),
// identical to findRandom(1, false)
jsonPath("$.foo").findRandom(1),
// identical to findRandom(3, false)
// best effort to pick 3 entries, less if not enough
jsonPath("$.foo").findRandom(3),
// fail if less than 3 overall captured values
jsonPath("$.foo").findRandom(3, true)
)
count
.check(
jsonPath("$.foo").count()
)
转换
转换是一个可选步骤。在上面的提取步骤之后,我们得到了相应的结果,在对结果进行匹配或者保存之前,你可能希望对结果进行格式转换,此时就要用到转换了。
withDefault
如果在上一步(提取)中没有获取到值,那么可以通过withDefault设置默认值。
.check(
jsonPath("$.foo") // 省略了find()
.withDefault("defaultValue")
)
transform
transform的参数是一个函数,该函数用于对提取到的值进行转换,要求上一步结果不能为空。
.check(
jsonPath("$.foo")
// append "bar" to the value captured in the previous step
.transform(string -> string + "bar")
)
transformWithSession
此步骤实际上是transform的一个变种,可以访问Session。
.check(
jsonPath("$.foo")
// append the value of the "bar" attribute
// to the value captured in the previous step
.transformWithSession((string, session) -> string + session.getString("bar"))
)
transformOption
与transfrom相反的是,该操作即使在上一步没有获取到结果也能执行。
.check(
jmesPath("foo")
// extract can be null
.transform(extract -> Optional.of(extract).orElse("default"))
)
当然,如果你的目的仅仅是设置一个默认值,那直接使用
withDefault可能更方便。
transformOptionWithSession
.check(
jmesPath("foo")
// extract can be null
.transformWithSession((extract, session) ->
Optional.of(extract).orElse(session.getString("default"))
)
)
验证
同提取一样,如果没有显式指定,gatling会默认执行exists。
is和not
// is
.check(
// with a static value
jmesPath("foo").is("expected"),
// with a Gatling EL String (BEWARE DIFFERENT METHOD)
jmesPath("foo").isEL("#{expected}"),
// with a function
jmesPath("foo").is(session -> session.getString("expected"))
)
// not
.check(
// with a static value
jmesPath("foo").not("unexpected"),
// with a Gatling EL String (BEWARE DIFFERENT METHOD)
jmesPath("foo").notEL("#{unexpected}"),
// with a function
jmesPath("foo").not(session -> session.getString("unexpected"))
)
isNull和notNull
// isNull
.check(
jmesPath("foo")
.isNull()
)
// notNull
.check(
jmesPath("foo").notNull()
)
exists和notExists
.check(
jmesPath("foo").exists()
)
// not exists
.check(
jmesPath("foo").notExists()
)
in
.check(
// with a static values varargs
jmesPath("foo").in("value1", "value2"),
// with a static values List
jmesPath("foo").in(Arrays.asList("value1", "value2")),
// with a Gatling EL String that points to a List in Session (BEWARE DIFFERENT METHOD)
jmesPath("foo").inEL("#{expectedValues}"),
// with a function
jmesPath("foo").in(session -> Arrays.asList("value1", "value2"))
)
validate
.check(
jmesPath("foo")
.validate(
"MyCustomValidator",
(actual, session) -> {
String prefix = session.getString("prefix");
if (actual == null) {
throw new NullPointerException("Value is missing");
} else if (!actual.startsWith(prefix)) {
throw new IllegalArgumentException("Value " + actual + " should start with " + prefix);
}
return actual;
})
)
命名
命名主要是为了防止万一出现错误,就可以在错误消息里显示check名称了。
.check(
jmesPath("foo").name("My custom error message")
)
保存
保存也是一个可选操作,用于将前一步提取或转换的结果保存到虚拟用户的Session中,以便后续复用。
saveAs
.check(
jmesPath("foo").saveAs("key")
)
条件检查
checkIf
// with a Gatling EL String condition that resolves a Boolean
.checkIf("#{bool}").then(
jmesPath("foo")
)
// with a function
.checkIf(session -> session.getString("key").equals("executeCheck")).then(
jmesPath("foo")
)
完整Check
以上的所有步骤: 确定检查类型,提取,转换,验证,保存,都是Check的工作流的一部分,通常会将一些步骤结合起来使用。
下面是一些例子:
.check(
// check the HTTP status is 200
status().is(200),
// check the HTTP status is in [200, 210]
status().in(200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210),
// check the response body contains 5 https links
regex("https://(.*)").count().is(5),
// check the response body contains 2 https links,
// the first one to www.google.com and the second one to gatling.io
regex("https://(.*)/.*").findAll().is(Arrays.asList("www.google.com", "gatling.io")),
// check the response body contains a second occurrence of "someString"
substring("someString").find(1).exists(),
// check the response body does not contain "someString"
substring("someString").notExists()
)
HTTP
HTTP是Gatling协议的主要目标,因此这是我们的主要关注点。
Gatling允许你对web应用,服务和网站进行负载测试。它几乎支持HTTP和HTTPS的全部特性,包括缓存、cookies和转发等。
下面是一个最基本的http负载测试的例子:
HttpProtocolBuilder httpProtocol = http.baseUrl("https://gatling.io");
ScenarioBuilder scn = scenario("Scenario"); // etc...
{
setUp(scn.injectOpen(atOnceUsers(1)).protocols(httpProtocol));
}
HTTP引擎
warmUp
Java/NIO引擎在启动并执行第一个请求的时候会有额外的开销,为了抵消此影响,Gatling会先自动向https://gatling.io.发送一个请求来预热。当然,你可以更改预热地址,或者禁用预热。
// 更改warmUp地址
http.warmUp("https://www.google.com);
// 禁用预热
http.disableWarmUp();
maxConnectionsPerHost
为了模拟真实的浏览器,gatling可以同时为每个虚拟用户建立多个到同一主机上的连接。默认情况下,针对同一虚拟用户到同一个远程主机上的并发连接数,gatling将其限制为6。你可以通过maxConnectionsPerHost来修改它。
http.maxConnectionsPerHost(10);
shareConnections
默认情况下,每个虚拟用户都有自己的连接池和SSLContext。这其实是模拟web浏览器访问服务器的场景,每个虚拟用户就是一个浏览器。
而如果你想模拟服务器到服务器的场景,在这种情况下,每个客户端(请求发起方)都有一个长期存在的连接池,可能让虚拟用户共享一个全局的连接池更合适。
http.shareConnections();
enableHttp2
可以通过enableHttp2设置来开启HTTP2协议支持。
http.enableHttp2();
注意,要开启HTTP2功能,要么使用JDK9以上的版本,要么确保gatling配置里
gatling.http.ahc.useOpenSsl不是false。
参考资料
[1]. https://gatling.io/docs/gatling/tutorials/quickstart/
[2]. https://gatling.io/docs/gatling/reference/current/extensions/maven_plugin/
边栏推荐
- 【AI玩家养成记】用AI识别邻居家旺财是什么品种
- Party, Google's autoregressive Wensheng graph model
- kotlin 语言特性
- Kubernetes cluster deployment
- AGCO AI frontier promotion (6.24)
- Quickly understand the commonly used message summarization algorithms, and no longer have to worry about the thorough inquiry of the interviewer
- 华为 PC 逆势增长,产品力决定一切
- Who is the fish and who is the bait? Summary of honeypot recognition methods from the perspective of red team
- Interviewer: the MySQL database is slow to query. What are the possible reasons besides the index problem?
- Mysql题目篇
猜你喜欢

Definition and use of constants in C language

这几个默认路由、静态路由的配置部署都不会,还算什么网工!

【5G NR】5G NR系统架构

Quickly understand the commonly used message summarization algorithms, and no longer have to worry about the thorough inquiry of the interviewer

Golden age ticket: Web3.0 Security Manual

Opengauss kernel: simple query execution

**Unity中莫名其妙得小问题-灯光和天空盒

Who is the fish and who is the bait? Summary of honeypot recognition methods from the perspective of red team

Teach you how to use airtestide to connect your mobile phone wirelessly!

Developer survey: rust/postgresql is the most popular, and PHP salary is low
随机推荐
黄楚平主持召开定点联系珠海工作视频会议 坚决落实省委部署要求 确保防疫情、稳经济、保安全取得积极成效
Goldfish rhca memoirs: do447 managing projects and conducting operations -- creating a project for ansible scripts
Manuel d'entrevue du gestionnaire de l'analyse des sources
Coinbase will launch the first encryption derivative for individual investors
Richard Sutton, the father of reinforcement learning, paper: pursuing a general model for intelligent decision makers
【sdx62】WCN685X IPA注册失败问题分析及解决方案
The data value reported by DTU cannot be filled into Tencent cloud database through Tencent cloud rule engine
What is the difference between sap QM and UD for inspection lots with hum?
Kotlin initialization block
How to create a new empty branch in the web development process of easyrtc?
Cloud native essay solicitation progress case practice
Resolve symbol conflicts for dynamic libraries
Activity lifecycle
Docker安装redis
Redis面试题
kotlin 数组、集合和 Map 的使用
Yyds dry goods counting solution sword finger offer: adjust the array order so that odd numbers precede even numbers (2)
kotlin 接口 泛型 协变 逆变
How to avoid serious network security accidents?
Troubleshooting the kubernetes problem: deleting the rancher's namespace by mistake causes the node to be emptied