Gatling是什么
Gatling是一個(gè)使用Scala編寫(xiě)的開(kāi)源的負(fù)載測(cè)試框架叠纷,基于Akka和Netty苫亦,具有以下亮點(diǎn):
- 高性能
- 友好的HTML報(bào)告
- 基于情境的記錄器(recoder),對(duì)開(kāi)發(fā)友好的DSL
Gatling VS Jmeter
Jmeter是目前非常成熟的負(fù)載測(cè)試工具,支持相當(dāng)多的協(xié)議,支持插件,可以輕松的擴(kuò)展拢蛋。
而Gatling性能上更有優(yōu)勢(shì),并且使用Scala DSL代替xml做配置蔫巩,相比jmeter要更靈活谆棱,而且更容易修改和維護(hù)。
關(guān)于Jmeter和Gatling的一個(gè)比較好的對(duì)比可以參見(jiàn)infoq的文章
同時(shí)圆仔,Gatling也對(duì)Maven
和Gradle
這樣的構(gòu)建工具比較友好垃瞧,易于集成到Jenkins
中,輕松加入到CI流程中坪郭。
TIPS: 在實(shí)際使用中建議版本化管理gatling的配置个从,使用maven插件或gradle插件形成對(duì)應(yīng)的maven/gradle工程項(xiàng)目管理,更容易,而且容量更小嗦锐,升級(jí)gatling也會(huì)更方便嫌松,減少了很多手工的操作。
Gatling的基本使用
從官方網(wǎng)站下載zip壓縮包奕污,解壓就行了萎羔,需要預(yù)先安裝有JDK,并設(shè)置好JAVA_HOME
碳默,熟悉JAVA的朋友應(yīng)該都懂贾陷,就不細(xì)說(shuō)了。
Gatling的目錄結(jié)構(gòu)看起來(lái)像這樣:
│ LICENSE
│
├─bin
│ gatling.bat
│ gatling.sh
│ recorder.bat
│ recorder.sh
│
├─conf
│ gatling-akka.conf
│ gatling.conf
│ logback.xml
│ recorder.conf
│
├─lib
├─results
│ .keep
│
└─user-files
├─bodies
│ .keep
│
├─data
│ search.csv
│
└─simulations
└─computerdatabase
│ BasicSimulation.scala
│
└─advanced
AdvancedSimulationStep01.scala
AdvancedSimulationStep02.scala
AdvancedSimulationStep03.scala
AdvancedSimulationStep04.scala
AdvancedSimulationStep05.scala
bin/
目錄存放gatling的可執(zhí)行文件嘱根,conf/
存放配置髓废,通常保持默認(rèn)即可,lib/
存放gatling本身的依賴(lài)该抒,用戶(hù)不用管慌洪,results/
存放報(bào)告,user-files/
是用戶(hù)最主要使用的目錄凑保,用戶(hù)定義的測(cè)試場(chǎng)景相關(guān)的代碼均存放于此目錄下蒋譬。
zip包解壓縮以后已經(jīng)帶有了一個(gè)官方的示例文件BasicSimulation.scala
,想看看演示效果的直接使用bin/gatling.(bat|sh)
啟動(dòng)就可以了愉适。這個(gè)演示的場(chǎng)景描述見(jiàn)官方文檔。那幾個(gè)AdvancedSimulationStep
其實(shí)效果上和BasicSimulation
完全一致癣漆,只是官方提供了一些參考的DSL寫(xiě)法而已维咸。
一些實(shí)戰(zhàn)中的DSL參考范例
盡管gatling和jmeter一樣,帶有一個(gè)圖形化的recorder惠爽,但是功能極其簡(jiǎn)陋癌蓖,只能模擬一個(gè)用戶(hù),并且沒(méi)有結(jié)構(gòu)化代碼架構(gòu)婚肆。因此只能用來(lái)生成最基本的框架租副,絕大多數(shù)情況需要用戶(hù)自己編寫(xiě)DSL,其實(shí)官方文檔中幾乎已經(jīng)涵蓋了大部分的用例较性,照著抄就可以了用僧。這里提供幾個(gè)參考的DSL
Random不起作用?
有時(shí)候我們需要在測(cè)試場(chǎng)景中引入隨機(jī)數(shù)赞咙,從而更好的模擬大量用戶(hù)請(qǐng)求的場(chǎng)景责循。很自然的想到幾乎各個(gè)編程語(yǔ)言都帶有Random
函數(shù)庫(kù)。而Scala自然也不例外攀操,帶有一個(gè)scala.util.Random類(lèi)庫(kù)院仿。但是實(shí)際使用的時(shí)候可能會(huì)發(fā)現(xiàn)沒(méi)用。比如下面這個(gè)例子:
forever(
exec(http("Random id browse")
.get("/articles/" + scala.util.Random.nextInt(100))
.check(status.is(200))
)
這個(gè)scala.util.Random.nextInt(100)
會(huì)發(fā)現(xiàn)只有第一次會(huì)隨機(jī)生成一個(gè)數(shù)字,后面都不變歹垫。按照gatling的官方文檔的解釋?zhuān)捎贒SL會(huì)預(yù)編譯剥汤,在整個(gè)執(zhí)行過(guò)程中是靜態(tài)的。因此Random在運(yùn)行過(guò)程中就已經(jīng)靜態(tài)化了排惨,不會(huì)再執(zhí)行吭敢。應(yīng)改為Feeder
實(shí)現(xiàn)。Feeder是gatling用于實(shí)現(xiàn)注入動(dòng)態(tài)參數(shù)或變量的若贮。改用Feeder
實(shí)現(xiàn):
val randomIdFeeder =
Iterator.continually(Map("id" ->
(scala.util.Random.nextInt(100))))
forever(
feed(randomIdFeeder)
.exec(http("Random id browse")
.get("/articles/${id}"))
.check(status.is(200))
)
feed()
在每次執(zhí)行時(shí)都會(huì)從Iterator[Map[String, T]]
對(duì)象中取出一個(gè)值省有,這樣才能實(shí)現(xiàn)這個(gè)需求。
使用import
引入外部方法
例如專(zhuān)門(mén)寫(xiě)一個(gè)Feeders.scala
文件谴麦,存儲(chǔ)著各種需要用到的Feeder
:
import scala.util.Random
object LinchangFeeders {
def randomGeoFeeder() : Iterator[Map[String, Number]] = {
val LNG_RANGE = List(108.75, 109.1)
val LAT_RANGE = List(34.0, 34.4)
return Iterator.continually(
Map(
"lng" -> (
Random.nextFloat() * (LNG_RANGE(1)
- LNG_RANGE(0)) + LNG_RANGE(0)
)
,"lat" -> (
Random.nextFloat() * (LAT_RANGE(1)
- LAT_RANGE(0)) + LAT_RANGE(0)
)
)
)
}
def randomOffsetFeeder() : Iterator[Map[String, Number]] = {
Iterator.continually(Map("offset" -> Random.nextInt(100)))
}
}
然后在MySimulation.scala
就可以import
蠢沿,使用里面定義好的方法了:
import scala.concurrent.duration._
import scala.util.Random
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import io.gatling.jdbc.Predef._
import Feeders._
class MySimulation extends Simulation {
val brownse = feed(randomOffsetFeeder)
.exec(
home
)
}
用戶(hù)注入策略
- <= 10: 一把注入
- > 10: 每10秒注入10個(gè)用戶(hù)
val injectStrategy =
if (USERS_COUNT > 10) {
splitUsers(USERS_COUNT) into(
rampUsers(10) over(5 seconds)
) separatedBy(10 seconds)
} else {
atOnceUsers(USERS_COUNT)
}
壓測(cè)時(shí)間策略
- = 0: 所有模擬用戶(hù)不循環(huán),執(zhí)行完測(cè)試場(chǎng)景即退出
- > 0: 所有模擬用戶(hù)循環(huán)執(zhí)行測(cè)試場(chǎng)景匾效,直到達(dá)到指定時(shí)間
val scn = scenario("My test scenario")
.doIfOrElse(DURATION > 0) {
forever(
exec(steps)
)
} {
exec(steps)
}
val setup = setUp(
scn.inject(
injectStrategy
).protocols(httpProtocol)
)
if (DURATION > 0) {
setup.maxDuration(DURATION minutes)
}
錯(cuò)誤處理
這個(gè)是gatling的一大亮點(diǎn)舷蟀。在壓力測(cè)試的過(guò)程中,無(wú)可避免會(huì)遇到各種花式錯(cuò)誤面哼。比如服務(wù)器超時(shí)無(wú)響應(yīng)野宜,服務(wù)端執(zhí)行錯(cuò)誤返回了非預(yù)期結(jié)果等等。這些錯(cuò)誤如果不進(jìn)行處理魔策,將會(huì)影響后續(xù)測(cè)試匈子。
比如后續(xù)所有鏈接請(qǐng)求都依賴(lài)于登錄成功,一旦登錄失敗闯袒,后續(xù)請(qǐng)求將無(wú)任何意義虎敦,而且會(huì)影響到最終匯總的測(cè)試結(jié)果。
gatling可以通過(guò)check
指令檢測(cè)URL的返回結(jié)果是否符合預(yù)期(如返回的HTTP code政敢,返回的內(nèi)容是否包含預(yù)期的內(nèi)容等等)其徙。通過(guò)tryMax
, doIf
等指令進(jìn)行失敗重試以及處理鏈接之間的依賴(lài)問(wèn)題。
更多關(guān)于失敗處理可以參考: http://gatling.io/docs/2.2.3/advanced_tutorial.html#step-05-check-and-failure-management
比如一個(gè)簡(jiǎn)單的登錄請(qǐng)求的DSL:
val login = tryMax(MAX_RETRY) {
pause(PAUSE_BEFORE_RETRY)
.exec(http("登錄系統(tǒng)")
.post("/login")
.formParam("code", "${code}")
.headers(jwtRequestHeader)
.check(status.is(200),
jsonPath("$.token").find.saveAs("token")))
}
val brownse = doIf("${token.exists()}") {
exec(
// other steps
)
}
登錄成功會(huì)返回一個(gè)JSON
喷户,包含有token
屬性唾那,將token
存儲(chǔ)于session
(這個(gè)session指gatling的session,作用是存儲(chǔ)每個(gè)虛擬用戶(hù)各自的屬性褪尝,并不是服務(wù)器端的session)闹获,用于以后的登錄請(qǐng)求。通過(guò)check
期望返回200 OK
河哑,并且期望返回一個(gè)token
屬性昌罩。
由于后續(xù)的請(qǐng)求都必須依賴(lài)于token
屬性存在,因此使用doIf
來(lái)確保這個(gè)依賴(lài)關(guān)系成立灾馒,遇到登錄失敗時(shí)將不會(huì)繼續(xù)向下請(qǐng)求茎用。