request 添加header_Gatling设置header、请求体、条件语句

设置header等信息

import io.gatling.core.Predef._import io.gatling.http.Predef._import scala.concurrent.duration._class BaiduSimulation extends Simulation {    /*    *设置请求的方式和路径并命名此请求名称,建议请求名称全局唯一    */  object HttpDemo {    val demo = exec(http("Request").get("/"))    }  /*    设置请求属于归属方案   */   var scn = scenario("scenarioRequest").exec(HttpDemo.demo)   //设置请求的的域名和一些公用的请求头信息  var httpConf = http  .baseUrl("https://www.baidu.com")  .acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")  .acceptEncodingHeader("gzip, deflate")  .acceptLanguageHeader("zh-CN,en-US;q=0.7,en;q=0.3")  .userAgentHeader("Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0")  //设置请求场景  setUp(scn.inject(atOnceUsers(1))).protocols(httpConf)}

请求传header信息

 val headers_1 = Map("Content-Type" -> "application/x-www-form-urlencoded") // 表示数据内容格式,可以定义多个,方便不同的用例请求使用,不过建议还是一个接口用例用独立一个文件来写吧  val headers_2 = Map("Content-Type" -> "multipart/form-data; boundary=----------------------------2bb6caed7d98")   
.exec(      http("post's example") // POST请求例子      .post("/api/getconf.json?mid=ebcd32d5f68e404db1ccc8ff2dacb360&ver=1.0")      .headers(headers_1)      .body(StringBody("""{

上述代码只是简单的描述了一个压测场景,其实在我们使用的过程中会有更复杂的情况,总体来说可以将上述定义步骤分位四个模块

  1. 请求体构建

  2. 方案配置

  3. 请求公共配置

  4. 请求模拟场景设置

1.请求体的构建
对于HTTP/HTTPS请求gatling支持主流的请求类型GET/POST/PUT/PATCH/HEAD /DELETE/OPTIONS
比如示例代码中我们以GET请求去访问百度首页,所以我们构建请求体为下方这样

object HttpDemo {
val demo = exec(http("Request").get("/"))
}

exec(...)里的参数就是我们的执行动作,http(...)http中内容代表着这个请求的命名,可以是中文或者英文但是需要全局唯一,get(...)get代表着这个请求的方式,可以为post(...)、put(...)等等
但是在日常中我们使用可能不简单的是发送一个url过去可能会有多种情况发生,所以gatling还支持许多参数化的传参

object Demo {    val demo = exec(        http("Post")          .post("/computers")  // 发送post请求          .queryParam("key","value")  // 增加query参数          .queryParamMap(Map("key"->"value"))  // 以Map的形式增加query参数          .formParam("key", "value")  // form表单格式发送参数          .formParamMap(Map("key"->"value"))  // 以Map的形式增加form表单格式参数          .body(StringBody("""{"key":"value"}""")).asJson  // 请求的body内容格式为json          .multivaluedFormParam("key","value")  // 发送multivalue格式的from表单      )  }  val demotwo =  exec(      http("get")        .httpRequest("get","/")  // 发送get请求根目录    )

方案配置

方案配置是决定上述定义的请求提如何在何场景实现,也可已经请求体设置在不同的方案中用于不同的请求场景

val scn = scenario("scenarioRequest").exec(Demo.demo) //将上述定义的Demo.demo中方法在命名为“scenarioRequest”的方案中执行
/*
val scn = scenario("scenarioRequest").exec(Demo.demo,Demo.demotwo)

val scn = scenario("scenarioRequest").exec(Demo.demo)
val scn1 = scenario("scenarioRequestTwo").exec(Demo.demotwo)
*/

除了上述的定义方案外,gatling还有支持其他定义方法例如repeat/during等等,当我们压测部分请求时里面有很多参数是循环迭代可以得到的,例如我们请求某个分页接口,里面需要对每页的数据遍历,如果在造数据时我们使用同样的数据只是页码变化的话这样我们重复的工作就太多了。所以gatling给出了循环的方法

val browse = repeat(5, "n") { // 1
exec(http("Page ${n}")
.get("/computers?p=${n}")) // 2
.pause(1)
}

或者我们将在一段时间内循环某个页面

during(duration, counterName, exitASAP) {  myChain}

3.请求公共配置

val http = http
.baseURL("https://www.baidu.com") // 请求域名
.acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") // 公共请求头
.acceptEncodingHeader("gzip, deflate") // 公共请求头
.acceptLanguageHeader("zh-CN,en-US;q=0.7,en;q=0.3") // 公共请求头
.userAgentHeader("Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0") // 公共请求头

除了上述的定义的元素在HTTP PROTOCOL中还可以定义代理服务器,自动预热等等功能自动预热
Java/NIO引擎启动会在要执行的第一个请求上产生开销。为了补偿这种影响,Gatling自动执行对https://gatling.io的请求。

要禁用此功能,只需添加.disableWarmUp到HTTP协议配置定义。要更改预热网址,只需添加.warmUp("newUrl")

// override warm up URL to http://www.google.com
val httpProtocol = http.warmUp("http://www.google.com")
// disable warm up
val httpProtocolNoWarmUp = http.disableWarmUp

虚拟主机
当我们压测过程中某个请求并不想访问这个集群,而是正对某个ip地址进行访问时,我们又不想每次重写本地host,使用gatling时可以通过使用虚拟主机的方式,使域名和ip绑定访问特定主机的域名

val httpProtocol1 = http
.baseUrl("http://127.0.0.1")
.virtualHost("www.baidu.com")

又或者我们相对多台服务器的同一个域名进行压测时我们可以按以下的写法来写

val httpProtocol1 = http
.baseUrls("127.0.0.1","127.0.0.2")
.virtualHost("www.baidu.com")

4.请求模拟场景设置
在上篇文章中我们讲述了场景设置中的开放模型和封闭模型,所以在这里对这些不在做过多解释。在一般使用过程中我比较喜欢使用开放模型,这种我可以看到更多的因为连接数导致的问题

scn.inject(
nothingFor(4 seconds), // 1
atOnceUsers(10), // 2
rampUsers(10) during (5 seconds), // 3
constantUsersPerSec(20) during (15 seconds), // 4
constantUsersPerSec(20) during (15 seconds) randomized, // 5
rampUsersPerSec(10) to 20 during (10 minutes), // 6
rampUsersPerSec(10) to 20 during (10 minutes) randomized, // 7
heavisideUsers(1000) during (20 seconds) // 8
).protocols(httpProtocol)
)

2.SSL使用

在我们日常的测试中会发现其实有时候,请求接口时我们不光是需要请求参数有时候我们还需要验签信息,如果我们关闭验签功能时虽然可以继续后续的操作,但是这等于我们放弃了一部分的真实场景来压测,这与我们预想的过程不相同,所以gatling还提供了验签的功能

exec(
http("Request")
.get("/foo/bar?baz=qix")
.sign(new SignatureCalculator {
override def sign(request: Request): Unit = {
import java.util.Base64
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
val mac = Mac.getInstance("HmacSHA256")
mac.init(new SecretKeySpec("THE_SECRET_KEY".getBytes("UTF-8"), "HmacSHA256"))
val rawSignature = mac.doFinal(request.getUri.getQuery.getBytes("UTF-8"))
val authorization = Base64.getEncoder.encodeToString(rawSignature)
request.getHeaders.add("Authorization", authorization)
}
})
)

从官方是示例来看我们可以再sign方法中通过重写SignatureCalculator类中的sign方法来使先自主验签的过程,这里有一个有趣的事情,就是我在gatling的早期版本,可能在gatling3.0左右的版本通过request.getUri.getEncodedQueryParams.add方法可以再query中重写uri实现在query中增加参数,但是随着gatling更新我在以前的版本和现在的版本发现并不能重写uri了

3.条件语句

在我们使用时,如果我们对某一条链路进行压测时,对于用户来说不可能是100%转化的,每一个层级用户会减少,例如当有100个用户进入我们的首页,发现需要登录才能访问这是只有80个用户选择了登录,然后这启动有50%的用户选择去查看我们推送的广告,只有10% 的用户在我们广告页面中下单,所以我们需要定义不同的模型来模拟用户真实的场景,所以我们可以使用gatling的条件语句来进行判断

doIf
gatling的DSL具有条件执行支持。如果仅在满足某些条件时才想执行特定的请求链,则可以使用doIf方法执行操作。

doIf("${myBoolean}") {
// 如果“myBoolean”中存储的session中的值为true,则执行下面的请求体
exec(http("...").get("..."))
}

如果要测试复杂的条件,则必须通过Expression[Boolean]:

doIf(session => session("myKey").as[String].startsWith("admin")) {
// 如果存储在session中的key“myKey”中的值以“admin”开头,则执行下面的请求体
exec(http("if true").get("..."))
}

doIfEquals
如果您的测试条件只是比较两个值,则可以简单地使用doIfEquals:

doIfEquals("${actualValue}", "expectedValue") {
// 如果session中的key“actualValue”中的值等于“expectedValue”,则执行下面的请求体
exec(http("...").get("..."))
}

doIfOrElse
与相似doIf,但是如果条件的计算结果为false,则执行第二条请求链

doIfOrElse(session => session("myKey").as[String].startsWith("admin")) {
// 如果session中的key为“myKey”中值以“admin”开头,则执行下面的请求体
exec(http("if true").get("..."))
} {
// 否则执行此部分
exec(http("if false").get("..."))
}

doIfEqualsOrElse
与doIfEquals条件类似,但条件为false时会执行第二条请求链

doIfEqualsOrElse(session => session("actualValue").as[String], "expectedValue") {
// 如果session中的key为“actualValue”中值等于“expectedValue”,则执行
exec(http("if true").get("..."))
} {
// 否则执行此部分
exec(http("if false").get("..."))
}

doSwitch
与java请求中的switch相同更具key的不同值,执行不同的请求体

doSwitch("${myKey}")( // 注意:这里使用的是小括号不是大括号 与其他不相同
key1 -> chain1,
key1 -> chain2
)

doSwitchOrElse
与相似doSwitch,但如果任何case中时,则执行备用部分的请求体

doSwitchOrElse("${myKey}")( // 这里也是小括号
key1 -> chain1,
key1 -> chain2
)(
myFallbackChain
)

randomSwitch
设置的概率值必须小于100%,命中概率不相等

randomSwitch( // 这里也是小括号
percentage1 -> chain1,
percentage2 -> chain2
)

randomSwitchOrElse
与randomSwitch相似,但如果未选择任何case,则执行备用请求体(即:随机数超过百分比总和)

randomSwitchOrElse( // 这里也是小括号
percentage1 -> chain1,
percentage2 -> chain2
) {
myFallbackChain
}

uniformRandomSwitch
与randomSwitch相似,命中概率

uniformRandomSwitch( // 这里还是小括号
chain1,
chain2
)

roundRobinSwitch
与randomSwitch相似,但是是循环执行

roundRobinSwitch( // 不好意思,这里也是小括号
chain1,
chain2
)

4.Check和Session使用

在性能测试的过程中我们有时候获取到外部的状态码可能只是知道服务端对请求完成了,但是我们并不知道,请求内部的返回是否符合我们的需求,所以gatling给我们提供了checks的方法,check在gatling中的作用是:

  1. 验证对请求的响应是否符合期望

  2. 最终捕获其中的一些元素 使用该check方法根据请求执行进行检查。例如在HTTP请求上判断请求状态码是否是200时: java http("demo").get("/").check(status.is(200)) .check(status.not(404), status.not(500)) //进行多项检查 又或者我们需要获取某个请求内返回的参数是否正确 java .check(jsonPath("$.status").is("200")) //如果返回请求是json可以通过jsonpath来定位到指定元素 .check(regex("""ACC${account_id}""").notNull) // 通过正则去获取判断元素 如果我们在上下流请求有强依赖问题,下流解决强依赖上流的某个返回值可以通过check将值获取并保存到session中供下流接口使用 java substring("foo") // 与 substring("foo").find.exists 相同 substring("foo").findAll.saveAs("indices") // 找到所有的foo并保存至key indices中 substring("foo").count.saveAs("counts") // 获取数量并保存 在下流使用此参数时只需按动态变量使用就行${mykey} # 5.Feeder Gatling由于DSL会预编译,在整个执行过程中是静态的,因此有的方法在运行过程中就已经静态化了,不会再执行,所以gatling提供了Feeder方法,Feeder是gatling用于实现注入动态参数或变量的,Gatling的Feeder支持多种格式注入数据分别包含csv/tsv/ssv/jsonFile/jsonUrl/jdbc/redis几种方式导入数据 基于Iterator[Map[String, T]] 我们在使用过程中一些参数不需要使用文件导入,例如自增数据或者随机数据这时我们可以使用Iterator[Map[String, T]]方法来实现数据的生成,减少我们投入的成本 java import scala.util.Random val feeder = Iterator.continually(Map("email" -> (Random.alphanumeric.take(20).mkString + "@foo.com"))) 如果我们要使用我们定义的feeder需要在使用的地方将其引入 java feed(feeder) 这样就定义了一个注入的步骤,其中每个虚拟用户都在同一个Feeder上进行Feed 每次虚拟用户到达此步骤时,它将从Feeder中获取一条数据,该数据将注入到用户的Session中,从而产生一个新的Session实例以供后续使用,但是如果Feeder无法产生足够的数据,Gatling将报错并且脚本将停止

文件导入
Gatling提供了各种支持多种文件导入的方法,但是在使用时,文件必须位于user-files/resources目录中,当使用诸如maven之类的构建工具时,必须将文件放置在src/main/resources或中src/test/resources,或者在gatling的config文件中配置配置路径,gatling将会从根目录下去寻找配置的路径,在使用过程中我配置的为绝对路径,gatling也可以找到对应的数据,gatling提供了以下的方法用于数据的读取

.queue // 默认行为,按顺序读取数据,当数据读取完后停止脚本,需要确保数据的量足够多
.random // 随机从数据中读取
.shuffle // 重洗数据排序,然后和queue一样读取
.circular // 和queue一样读取,但是当数据读取完成后会返回头部重新读取一遍

csv/tsv/ssv
Gatling提供了几个内置函数来读取以字符分隔的值文件,其解析器遵循RFC4180规范,或根据文件首行列值作为session的key使用

val csvFeeder = csv("foo.csv") // 使用逗号分隔
val tsvFeeder = tsv("foo.tsv") // 使用制表符分隔
val ssvFeeder = ssv("foo.ssv") // 使用分号分隔符
val customSeparatorFeeder = separatedValues("foo.txt", '#') // 使用自定义分隔符

gatling读取文件的方法是将数据加载到内存中,并且提供了几种加载的选项用于自定义调节eagereager在模拟开始之前,将整个数据加载到内存中,从而在运行时节省磁盘访问。此模式最适合处理较小的文件,这些文件可以快速解析而不会延迟模拟开始时间,并且可以轻松地放在内存中。当文件过大时或者内存不够加载文件时,这个模式加载文件可能会出现脚本崩溃等行为

val csvFeeder = csv("foo.csv").eager.random

batchbatch在大文件读取时是按块开始的,所以在大文件处理的方面这个方法会更加好,当batch模式,random并shuffle不能当然的全数据进行操作,只能在记录的内部缓冲区进行操作。该缓冲区的默认大小为2000,可以进行更改。

val csvFeeder = csv("foo.csv").batch.random
val csvFeeder2 = csv("foo.csv").batch(200).random // 这里将每次读取数据缩减为200条

压缩文件
如果文件很大,则可以使用gatling提供的压缩文件处理的方法unzip

val csvFeeder = csv("foo.csv.zip").unzip

JSON文件
如果你希望读取的数据是json文件而不是csv文件则可以使用gatling的json读取方法jsonFile

val jsonFileFeeder = jsonFile("foo.json")
val jsonUrlFeeder = jsonUrl("http://me.com/foo.json")
/*
*文件类型
*/
{
"id":19434,
"foo":1
},
{
"id":19435,
"foo":2
}
]

JDBC
当我们使用的数据希望是从数据库拉取而不是使用本地的数据可以使用jdbcFeeder此方法,但是不建议使用这种方法,因为可能会因为网络io等元素导致脚本压力无法上去

import io.gatling.jdbc.Predef._
//注意这里要导入jdbc模块
jdbcFeeder("databaseUrl", "username", "password", "SELECT * FROM users")

Redis

import io.gatling.redis.Predef._

val redisPool = new RedisClientPool("localhost", 6379)

// 存为一个列表使用,根据"foo"这个key获取数据
val feeder = redisFeeder(redisPool, "foo")

然后,您可以覆盖所需的Redis命令:

// 使用SPOP命令从名为“foo”的集合读取数据
val feeder = redisFeeder(redisPool, "foo").SPOP
// 使用SRANDMEMBER命令从名为“foo”的集合读取数据
val feeder = redisFeeder(redisPool, "foo").SRANDMEMBER

分布式

gatling不支持分布式,就意味着不能像jmeter那样,由master机器控制多个slave一起工作.

但是实际压测的时候肯定还是需要多个机器同一时间压测才能达到高并发.

这里只能曲线救国一把,结合jenkins的pipeline脚本控制多个节点并发.

脚本中使用了两台jenkins节点机器,通过agent的标签指定节点.

脚本可以提前上传到服务器上,或者放到github上每次运行的时候拉下来.

pipline中使用parallel才可以达到节点并发执行.

pipeline {
agent none

stages {
stage('run tests') {
parallel {
stage('aliyun_agent') {
agent {
label 'aliyun'
}
steps {
sh 'pwd'
sh 'cd ~/jenkins_node/workspace/pipline_gatling_test/gatling-maven-plugin-demo && mvn gatling:test'
}
}

stage('jingdong_agent') {
agent {
label 'jingdong'
}
steps {
sh 'pwd'
sh 'cd /root/doc/gatling-docker/gatling-maven-plugin-demo && mvn gatling:test'
}
}
}
}
}
}

可视化展示

gatling的报告也是每次运行完成后才会生成,不能实时查看当前压测QPS及RT时间.

jmeter方案

在jmeter方案中,如果想实时查看压测qps及rt时间,是在脚本中配置后端监听器并且配置influxdb地址,jmeter会把压测过程中数据实时传给influxdb存储,前端配合grafana展示就可以完成实时展示了.

gatling方案

在gatling方案中也是类似的思路,但是不知道怎么把压测试试传到influxdb上.

后来想到了gatling有个gatling.conf文件,其中有段配置如下:

e196a6f95803e3b8bd7ca403d93e7671.png

这段配置是的意思是可以把数据写到graphite的host + 2003地址上.

grafana && influxdb

这里使用docker-compose启动两个镜像,配置如下:

在influxdb的中influxdb.conf配置graphite.

8bf7a802f345d72fcaf145da219217d0.png

influxdb开启了三个端口,其实2003就是刚才gatling.conf配置中.

3af1406303b91c00b94be78ad6ebad06.png

展示效果

a186281e9f3262ccba6bd31d9ca0b48f.png

项目地址

https://github.com/xinxi1990/gatling-docker-test.git

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值