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/ 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), // 在指定时间段内保持恒定的虚拟用户数。注意,常态并发用户意味着当某个用户的scenario完成后,gatling会创建一个新的用户,以此来保持并发用户数的恒定。因此,active user可能会大于constantConcurrentUsers 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。
实践比较
Gatling出现得比较晚,用到的技术更新,理论上性能会更好。但在实际压测过程中发现,Gatling压测出来的结果是不如Jmeter和Apache benchmark的,以访问AWS S3资源为例,1000个并发用户访问10个S3资源,Jmeter测出来的RPS达到了接近2000,而Gatling只有几百。访问同一个资源,Gatling测出来的结果也比Jmeter和AB小。
RPS并不是TPS,RPS是服务端的吞吐量,TPS是"并发/响应时间",在Gatling中无法直观看到TPS,Jmeter可以下载插件来显示,其结果是一个曲线图。
经过一些调整后,Gatling的RPS能够上来不少。主要是两个点:一个是scenario,原来每个scenario只执行一个请求,这不符合实际的用户行为。实际场景中,用户的行为是连贯的,是存在一定的生命周期的。如果每次执行完一个请求就结束,系统就要新创建一个用户来补充并发用户数量,会消耗不少资源。另一个点就是连接复用,这个在上文中介绍过,根据实际需要来选择是否开启。
经过调整后,两者的测试结果差不多。下面对官方测试网站进行压测,并发用户是500,时间是30s(超过30s就可能触发网站的qps限制)。Gatling脚本如下:
讯享网class PerformanceSimulation extends BaseSimulation {
private val headers = Map("Content-Type" -> "application/json")
val httpConf: HttpProtocolBuilder = http
.baseUrl("https://computer-database.gatling.io")
.headers(headers)
.acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
.shareConnections()
.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")
.disableCaching
val scn: ScenarioBuilder = scenario("Get Request")
.exec(http("Request 1").get("/computers").check(status is 200)).pause(1)
.exec(http("Request 2").get("/computers/6").check(status is 200))
{
//注入用户,刚开始就500个
setUp(scn.inject(constantConcurrentUsers(500).during(30)).protocols(httpConf))
}
}
结果如下:

Jmeter也设置相同的条件,结果如下:

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/46487.html