Gatling是什麼
Gatling是一個使用Scala編寫的開源的負載測試框架,基於Akka和Netty,具有以下亮點:
- 高效能
- 友好的HTML報告
- 基於情境的記錄器(recoder),對開發友好的DSL
Gatling VS Jmeter
Jmeter是目前非常成熟的負載測試工具,支援相當多的協議,支援外掛,可以輕鬆的擴充套件。
而Gatling效能上更有優勢,並且使用Scala DSL代替xml做配置,相比jmeter要更靈活,而且更容易修改和維護。
關於Jmeter和Gatling的一個比較好的對比可以參見infoq的文章
同時,Gatling也對Maven
和Gradle
這樣的構建工具比較友好,易於整合到Jenkins
中,輕鬆加入到CI流程中。
TIPS: 在實際使用中建議版本化管理gatling的配置,使用maven外掛或gradle外掛形成對應的maven/gradle工程專案管理,更容易,而且容量更小,升級gatling也會更方便,減少了很多手工的操作。
Gatling的基本使用
從官方網站下載zip壓縮包,解壓就行了,需要預先安裝有JDK,並設定好JAVA_HOME
,熟悉JAVA的朋友應該都懂,就不細說了。
Gatling的目錄結構看起來像這樣:
│ 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的可執行檔案,conf/
存放配置,通常保持預設即可,lib/
存放gatling本身的依賴,使用者不用管,results/
存放報告,user-files/
是使用者最主要使用的目錄,使用者定義的測試場景相關的程式碼均存放於此目錄下。
zip包解壓縮以後已經帶有了一個官方的示例檔案BasicSimulation.scala
,想看看演示效果的直接使用bin/gatling.(bat|sh)
啟動就可以了。這個演示的場景描述見官方文件。那幾個AdvancedSimulationStep
其實效果上和BasicSimulation
完全一致,只是官方提供了一些參考的DSL寫法而已。
一些實戰中的DSL參考範例
儘管gatling和jmeter一樣,帶有一個圖形化的recorder,但是功能極其簡陋,只能模擬一個使用者,並且沒有結構化程式碼架構。因此只能用來生成最基本的框架,絕大多數情況需要使用者自己編寫DSL,其實官方文件中幾乎已經涵蓋了大部分的用例,照著抄就可以了。這裡提供幾個參考的DSL
Random不起作用?
有時候我們需要在測試場景中引入隨機數,從而更好的模擬大量使用者請求的場景。很自然的想到幾乎各個程式語言都帶有Random
函式庫。而Scala自然也不例外,帶有一個scala.util.Random類庫。但是實際使用的時候可能會發現沒用。比如下面這個例子:
forever(
exec(http("Random id browse")
.get("/articles/" + scala.util.Random.nextInt(100))
.check(status.is(200))
)
這個scala.util.Random.nextInt(100)
會發現只有第一次會隨機生成一個數字,後面都不變。按照gatling的官方文件的解釋,由於DSL會預編譯,在整個執行過程中是靜態的。因此Random在執行過程中就已經靜態化了,不會再執行。應改為Feeder
實現。Feeder是gatling用於實現注入動態引數或變數的。改用Feeder
實現:
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()
在每次執行時都會從Iterator[Map[String, T]]
物件中取出一個值,這樣才能實現這個需求。
使用import
引入外部方法
例如專門寫一個Feeders.scala
檔案,儲存著各種需要用到的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
)
}
使用者注入策略
- <= 10: 一把注入
- > 10: 每10秒注入10個使用者
val injectStrategy =
if (USERS_COUNT > 10) {
splitUsers(USERS_COUNT) into(
rampUsers(10) over(5 seconds)
) separatedBy(10 seconds)
} else {
atOnceUsers(USERS_COUNT)
}
壓測時間策略
- = 0: 所有模擬使用者不迴圈,執行完測試場景即退出
- > 0: 所有模擬使用者迴圈執行測試場景,直到達到指定時間
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)
}