介紹
本篇博客,旨在記錄學習的要點,所以格式隨意, 方便本人日后自考和回憶,有興趣的朋友可以評論討論。
原文地址:https://www.cnblogs.com/clockq/p/10539974.html
一. 性能測試基礎
1.1 性能測試時什么?
性能測試時通過自動化的測試工具模擬多種正常、峰值、以及異常負載條件,以此來對系統的各項性能指標進行評測。
性能測試 = 負載測試 + 壓力測試
- 通過負載測試,確定在各種工作負載下系統的性能,目的是測試系統的負載逐漸增加的情況下,系統的各項性能指標的變化情況。
- 通過壓力測試,確定一個系統的瓶頸或者不能接受的性能點,來獲得系統所能提供的最大服務級別。
1.2 性能測試的目的
- 評估系統的能力
- 識別體系中的弱點
- 系統調優
- 檢查軟件中的問題
- 驗證系統穩定性
- 驗證系統可靠性
1.3 性能測試的常見觀察指標
- Avg Rps: 平均每秒響應次數 = 總請求時間 / 秒數
- Avg time to last byte per terstion(mstes): 平均每秒業務腳本迭代次數
- Successful Rounds: 成功的請求
- Failed Hits: 失敗的單擊次數
- Hits Per Second: 每秒單擊次數
- Successful Hits Per Second: 每秒成功的單擊次數
- Failed Hist Per Second: 每秒失敗的單擊次數
- Attempted Connections: 嘗試連接數
- Throughput: 吞吐率
同時,對於服務端的CPU占有率,內存占有率,數據庫連接池等也是需要觀察的重點。
1.4 性能測試的基本流程
- 明確性能測試需求
- 制定性能測試方案
- 編寫性能測試案例
- 執行性能測試案例
- 分析性能測試結果
- 生成性能測試報告
二. Gatling基礎 -> 基礎使用法
2.1 安裝Gatling
獲取安裝包 http://gatling.io.download/
下載成功后解壓即可 使用Gatling需要安裝JDK
2.2 使用Gatling
- 編寫測試腳本(這塊重點學習和講解)或者使用自帶的錄制器(bin/recorder.sh)
- 執行測試腳本(bin/gatling.sh),在開啟的窗口中選擇要執行的腳本
- 查看測試報告(報告默認在“result/”目錄下)
- 分析測試結果
三. Gatling 和 Mvn 整合使用 (推薦)
3.1 導入依賴
<properties>
<gatling.version>2.1.7</gatling.version>
<gatling-plugin.version>2.1.7</gatling-plugin.version>
</properties>
<!-- Gatling Module -->
<dependency>
<groupId>io.gatling.highcharts</groupId>
<artifactId>gatling-charts-highcharts</artifactId>
<version>${gatling.version}</version>
</dependency>
3.2 導入插件
<build>
<sourceDirectory>src/test/scala</sourceDirectory>
<testSourceDirectory>src/test/scala</testSourceDirectory>
<plugins>
<!-- Gatling Maven plugin that runs the load-simulation. -->
<plugin>
<groupId>io.gatling</groupId>
<artifactId>gatling-maven-plugin</artifactId>
<version>${gatling-plugin.version}</version>
<configuration>
<configFolder>src/test/resources</configFolder>
<dataFolder>src/test/resources/data</dataFolder>
<resultsFolder>target/gatling/results</resultsFolder>
<runMultipleSimulations>true</runMultipleSimulations>
<simulationsFolder>src/test/scala/com/pharbers/gatling</simulationsFolder>
<simulationClass>com.pharbers.gatling.scenario.getHome</simulationClass>
<!-- <noReports>false</noReports> -->
<!-- <reportsOnly>directoryName</reportsOnly> -->
<!-- <simulationClass>foo.Bar</simulationClass> -->
<!-- <jvmArgs> -->
<!-- <jvmArg>-DmyExtraParam=foo</jvmArg> -->
<!-- </jvmArgs> -->
<!-- <fork>true</fork> -->
<!-- <propagateSystemProperties>true</propagateSystemProperties> -->
<!-- <failOnError>true</failOnError> -->
</configuration>
</plugin>
</plugins>
</build>
3.3 編寫腳本
忽略
注意: 腳本要寫在 src/test/scala 下
3.4 執行腳本
mvn gatling:execute
3.5 分析報告
四. 現實測試舉例
我們先以測試“博客園系統登錄頁”性能為例,講解一次測試過程的幾個步驟,和測試報告怎么分析。
4.1 明確性能測試需求
好的開始是成功的一半
明確性能測試的需求是至關重要的,所以我們要先有一份測試需求實例
測試需求名稱: 博客園登錄接口性能測試
| 信息描述 | 描述內容 |
|---|---|
| 參與者 | 張三 |
| 概述 | 測試博客園登錄接口的最大並發量 |
| 前置條件 | 博客園前端頁面已經成功部署,並可以正常訪問 |
| 后置條件 | 無 |
| 業務數據 | 測試登錄賬號 |
| 不可測試原因 | 網絡不可達 |
| 流程規則 | 用戶訪問博客園登錄頁,滯留5s,之后調用登錄接口 |
| 業務規則 | 無 |
| 頁面規則 | 無 |
| 特殊規則 | 無 |
| 接口規則 | 無 |
| 檢查內容 | 檢查當用戶量達到多大時,會導致服務端阻塞,用戶響應時間超過5s |
4.2 編寫性能測試案例
測試需求名稱: 博客園登錄接口性能測試
| 測試步驟 | 步驟描述 | 預期結果 |
|---|---|---|
| 步驟 1 | 是否測試博客園登錄接口最大並發量 | 確定性能測試登錄接口的並發用戶數量 |
| 步驟 2 | 啟動博客園的前端工程 | 前端工程啟動成功 |
| 步驟 3 | 准備性能測試腳本 | 性能測試腳本准備完成 |
| 步驟 4 | 准備測試數據 | 無 |
| 步驟 5 | 執行腳本,驗證系統是否滿足相關性能測試指標 平均響應時長<2s 95%響應時長<= 5s | 系統滿足相關性能測試指標 |
| 步驟 5 | 執行1小時壓力測試 | 1. 系統滿足相關性能測試指標 2. 1小時壓力測試中腳本未報錯 |
4.3 執行性能測試案例
按照性能測試案例編寫測試腳本
package com.pharbers.gatling.base
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import io.gatling.http.config.HttpProtocolBuilder
object phHttpProtocol {
implicit val noneWhiteList: io.gatling.core.filter.WhiteList = WhiteList()
implicit val noneBlackList: io.gatling.core.filter.BlackList = BlackList()
implicit val staticBlackList: io.gatling.core.filter.BlackList = BlackList(""".*\.js""", """.*\.css""", """.*\.gif""", """.*\.jpeg""", """.*\.jpg""", """.*\.ico""", """.*\.woff""", """.*\.(t|o)tf""", """.*\.png""")
implicit val staticWhiteList: io.gatling.core.filter.WhiteList = WhiteList(""".*\.js""", """.*\.css""", """.*\.gif""", """.*\.jpeg""", """.*\.jpg""", """.*\.ico""", """.*\.woff""", """.*\.(t|o)tf""", """.*\.png""")
def apply(host: String)
(implicit blackLst: io.gatling.core.filter.BlackList, whiteLst: io.gatling.core.filter.WhiteList): HttpProtocolBuilder = { http
.baseURL(host)
.inferHtmlResources(blackLst, whiteLst)
.acceptHeader("application/json, text/javascript, */*; q=0.01")
.acceptEncodingHeader("gzip, deflate")
.acceptLanguageHeader("zh-CN,zh;q=0.9,zh-TW;q=0.8")
.doNotTrackHeader("1")
.userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36")
}
}
package com.pharbers.gatling.base
object phHeaders {
val headers_base = Map(
"Accept" -> "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
"Upgrade-Insecure-Requests" -> "1")
}
package com.pharbers.gatling.scenario
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import io.gatling.core.structure.ChainBuilder
import com.pharbers.gatling.base.phHeaders.headers_base
object getHome {
val getHome: ChainBuilder = exec(http("home")
.get("/")
.headers(headers_base))
}
package com.pharbers.gatling.scenario
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import io.gatling.core.structure.ChainBuilder
import com.pharbers.gatling.base.phHeaders.headers_json
object userLogin {
val feeder = csv("loginUser.csv").random
println(feeder)
val login: ChainBuilder = exec(http("login")
.get("/api/user/login")
.headers(headers_json)
.body(StringBody("""{ "condition" : { "email" : "nhwa", "password" : "nhwa" } }""")).asJSON)
}
package com.pharbers.gatling.simulation
import io.gatling.core.Predef._
import scala.concurrent.duration._
import com.pharbers.gatling.scenario._
import com.pharbers.gatling.base.phHttpProtocol
class userLogin extends Simulation {
import com.pharbers.gatling.base.phHttpProtocol.{noneBlackList, noneWhiteList}
val httpProtocol = phHttpProtocol("http://192.168.100.141:9000")
val scn = scenario("user_login")
.exec(
getHome.getHome
.pause(5 seconds),
userLogin.login
.pause(60 seconds)
)
setUp(scn.inject(rampUsers(1000) over (3 seconds))).protocols(httpProtocol)
}
並執行上述腳本
4.4 分析性能測試結果
看下圖,可以看到67% + 8%的請求可以在1.2s內完全,同時在1000用戶的並發測試下,會有用戶請求不到資源,也就是加載失敗。
其實,這個地方,可以通過修改gatling.conf來改變表格的渲染區間,使結果更符合我們的測試要求

這里,75th的總響應時間=1s,還是很快的,但95th的總響應時間>9s, 所以不符合我們的測試要求。

我們使用遞增的方式,在3s內逐漸增加用戶並發量,並且用戶會滯留5s + 60s,在下圖中就得到了體現

下圖是本次測試,在每個時間點的請求情況,包含請求狀態(成功,失敗)和請求數量

還有更多圖表,就不一一展示了,我們主要就是查看前兩個圖表,以此判斷服務器所能承受的壓力。
當然,如果需要考查更多標准,就需要查看其它圖表,比如延遲分布圖,負載分布圖等等。。。。
4.5 生成性能測試報告
一份合格的性能測試報告,至少應該包含如下內容:
- 測試基本信息: 包含: 測試目的,報告目標讀者,術語定義,參考資料
- 測試環境描述: 包含: 服務器軟硬件環境,網絡環境,測試工具,測試人員
- 性能測試案例執行分析: 需要詳細描述每個測試案例的執行情況,以及對對應測試結果進行分析
- 測試結果綜合分析及建議:對本次性能測試做綜合分析,並給出測試結論和改進建議
- 測試經驗總結
博客園登錄接口性能測試報告
測試信息
信息描述 描述內容 測試人員 齊鍾昱 測試目的 檢查當用戶量達到多大時,會導致服務端阻塞,用戶響應時間超過5s 術語定義 50th,安裝遞增排序后,排在50%的請求的信息 術語定義 95th,安裝遞增排序后,排在95%的請求的信息 參考資料 零成本實現Web性能測試[電子工業出版社] 測試環境
信息描述 描述內容 服務器系統 CentOS Linux release 7.4.1708 (Core) 服務器集群數量 4 服務器內存(台) 16G 服務器CPU核心數(台) 12 服務器硬盤空間(台) 256G SSD JAVA版本 1.8.121 Scala版本 2.11.8 Play版本 2.5.0-M2 Redis版本 4.0.1 MongoDB版本 3.4.4 Node.js 8.11.2 Ember.js 2.18.2 網絡環境 公司局域網 測試工具 Gatling 2.1.7 結果分析
測試內容 預期結果 測試結果 備注 博客園系統登錄頁的最大訪問量 在當前環境下可以1000用戶並發,不會造成用戶請求失敗 在3s內逐漸提高並發量,當並發量在643時有三個資源請求失敗,在並發量達到689時,有64個資源請求失敗 未通過,當前博客園系統登錄頁的最大訪問量應小於643 博客園系統登錄接口的最大並發量 在當前環境下可以1000用戶並發,不會造成用戶請求失敗 在3s內逐漸提高並發量,當並發量達到1000時,請求資源仍全部成功 通過 博客園登錄頁的響應時間 在當前環境下用戶平均響應時長<2s 95%響應時長<= 5s 50th響應時間為1.6s,95th為22s 未通過 博客園登錄接口的響應時間 在當前環境下用戶平均響應時長<2s 95%響應時長<= 5s 50th響應時間 < 1s,95th < 1s 通過 測試總結
根據上述分析報告,本次性能測試為通過制定要求,博客園系統登錄功能的最大並發量應小於643,為保持性能,建議並發數小於500
五. 常用腳本api
5.1 並發量控制
atOnceUsers(100)使用100並發量測試目標服務器rampUsers(100) over (10 seconds)循序漸進的增大壓力,在10s中內線性增加用戶數達到最大壓力100並發量nothingFor(10 seconds)等待10sconstantUsersPerSec(rate) during(duration)在指定duration內,以固定頻率注入用戶,每秒注入rate個用戶,默認固定間隔constantUsersPerSec(rate) during(duration) randomized與上面不同的是用戶以隨機間隔注入rampUsersPerSec(rate1) to (rate2) during(duration)在指定duration內,以遞增頻率注入用戶,每秒注入 rate1 ~ rate2 個用戶
5.2 用戶行為控制
.exec()實際的用戶行為.pause(20)用戶滯留20s,模擬用戶思考或者瀏覽內容.pause(min: Duration, max: Duration)用戶隨機滯留,滯留時間在min ~ max 之間
5.3 流程控制
repeat(time, counterName)內置循環器foreach(seq, elem, counterName)foreach循環器csv("file").random創建填充器doIf("", "")判斷語句
