服務端|性能測試入門指南
故寫本文,希望對大家 認識性能測試有一定幫助。也歡迎大家多多指正。
全文包括:
什么是性能
性能對用戶和產品收入影響
性能測試目的、性能關注指標、性能測試類型、性能測試流程
常用的性能測試工具對比
1、前言
隨着5G時代的到來,以及萬物互聯時代的到來,雲應用和雲服務會越來越多,數據量會指數級增長。尤其是2020年全球疫情的時代意義,會導致各行各業開始上雲。從而會催生出極具個性化的各類產品的誕生。所有行業的生態會像鯨落效應一樣,圍繞若干個巨無霸公司衍生出滿足人們各種需求的中小型產品。大部分產品的形態可能會變成重服務端、輕客戶端。所以,服務端性能測試的需求也有可能會出現井噴式增長。但是服務端性能測試需求對於中小型公司,尤其是大部分不關注用戶體驗的公司來說,性能測試需求特點是周期短、時間緊。因此,對於大部分測試從業人員來說,了解並掌握常見的性能測試知識是必不可少的,雖然不會經常用到。2、什么是性能
不同角色關注的性能是不一樣的,性能測試這項系統工程,需要各角色在其關注的緯度提供信息或幫助。用戶眼中的性能
性能對於用戶來說,就是操作的響應速度、產品的崩潰是否影響自己的生活。比如滴滴之前在情人節當天打車服務的性能事故。
老板眼中的性能
老板主要關心產品的收入、成本(用了多少錢服務了多少用戶)、用戶滿意度(用戶對產品是否滿意)。
運維眼中的性能

開發眼中的性能

測試眼中的性能

3、性能的影響
3.1 性能對用戶的影響
對於大部分商業公司的ToC產品,性能關乎產品命運和增長。如下圖所示,雖然現在阿里京東壟斷不太容易更換應用,但是遇到性能很差的時候,心里還是忍不住問候幾句。
3.2 性能對收入的影響
大家都知道性能對產品收入有非常大的影響,但是很少公司有全面的運營分析來證明這件事。以下是《2016全球零售業數字化性能基准報告》中關於性能對收入影響的數據。
從圖中可以看到,響應時間對轉化率的影響非常大,比如沃爾瑪夠硬核了吧,沃爾瑪的響應時間降低0.1秒,收入即可增加1%,是非常大的收入提升。

4、性能的組成
以中小型電商網站為例,如下圖所示,性能基本組成:-
客戶端(Web、移動端、小程序)性能
-
DNS性能
-
負載均衡服務性能
-
Nginx集群性能、折損率
-
CDN緩存性能(回源率、穿透率)
-
應用服務器性能
-
DB性能(Mysql/Redis/Memcache)

5、性能測試基礎知識和注意事項
熟悉性能測試之前,首先了解性能測試的目標是什么。帶着目標去思考會更有利於理解下面的內容。5.1 性能測試目的
性能測試的最終目的是為了最大限度的滿足用戶的需求,通常要達成以下目標:(1)性能評估:測試中評估系統的QPS、響應時間、成功率等;
(2)尋找系統瓶頸,進行系統調優;
(3)檢測軟件中的問題;
(4)驗證穩定性和可靠性;
5.2 性能應該關注的指標
一般來說,性能測試要統一考慮這么幾個因素:Thoughput吞吐量,Latency響應時間,資源利用(CPU/MEM/IO/Bandwidth…),成功率,系統穩定性。 (1)響應時間:你得定義一個系統的響應時間latency,建議是TP95或以上。響應時間具體要求多少,一般讀不超過200ms,寫不超過500ms。要是實在不知道,對標同行業競品。(2)最高吞吐量:TPS(每秒事務請求數)或QPS(每秒請求量),在目標響應時間要求下,系統可支撐的最高吞吐量。
(3)成功率:在關注QPS和響應時間的同時,還要關注成功率。如果QPS和響應時間都滿足性能要求時,請求成功率只有50%,用戶也是不會接收的。
(4)性能拐點:一般服務都有性能臨界點。當超過臨界點時,吞吐量非線性下降,響應時間指數級增加,成功率降低。
找到出現性能拐點的主要原因:
基於性能拐點主要原因設置高危性能報警線。此為高風險注意事項,因為一旦達到性能拐點,有可能會出現雪崩現象,造成極其嚴重的事故。
觀察超過性能拐點后,系統是否會出現假死、崩潰等高風險事件。
(5)系統穩定性:保持最高吞吐量(目標響應時間下的最高吞吐量),持續運行7*24小時。然后收集CPU,內存,硬盤/網絡IO,等指標,查看系統是否穩定,比如,CPU是平穩的,內存使用也是平穩的。那么,這個值就是系統的性能。
(6)極限吞吐量:階梯式增加並發壓力,找到系統的極限值。比如:在成功率100%的情況下(不考慮響應時間的長短),系統能堅持10分鍾的吞吐量。
(7)系統健壯性:做Burst Test。用第二步得到的吞吐量執行5分鍾,然后在第四步得到的極限值執行1分鍾,再回到第二步的吞吐量執行5鍾,再到第四步的權限值執行1分鍾,如此往復個一段時間,比如2天。收集系統數據:CPU、內存、硬盤/網絡IO等,觀察他們的曲線,以及相應的響應時間,確保系統是穩定的。
(8)低吞吐量和網絡小包的測試:有時候,在低吞吐量的時候,可能會導致latency上升,比如TCP_NODELAY的參數沒有開啟會導致latency上升(詳見TCP的那些事),而網絡小包會導致帶寬用不滿也會導致性能上不去,所以,性能測試還需要根據實際情況有選擇的測試一下這兩咱場景。
5.3 性能測試類型
首先簡單分析性能測試的壓力模型。如下圖所示,隨着單位時間壓力的不斷增長,被測系統和服務器的壓力不斷增加,TPS會因為這些因素而發生變化,而且通常符合一下規律。


-
性能測試(狹義)
-
說明:性能測試方法是通過模擬生產運行的業務壓力量和使用場景組合,測試系統的性能是否滿足生產性能要求。通俗地說,這種方法就是要在特定的運行條件下驗證系統的能力狀態。
-
特點:1、這種方法的主要目的是驗證系統是否有系統宣稱具有的能力。2、這種方法要事先了解被測試系統經典場景,並具有確定的性能目標。3、這種方法要求在已經確定的環境下運行。也就是說,這種方法是對系統性能已經有了解的前提,並對需求有明確的目標,並在已經確定的環境下進行的。
-
負載測試(Load Test)
-
說明:通過在被測系統上不斷加壓,直到性能指標達到極限(例如“響應時間”)超過預定指標或都某種資源已經達到飽和狀態。
-
特點:1、這種性能測試方法的主要目的是找到系統處理能力的極限。2、這種性能測試方法需要在給定的測試環境下進行,通常也需要考慮被測試系統的業務壓力量和典型場景、使得測試結果具有業務上的意義。3、這種性能測試方法一般用來了解系統的性能容量,或是配合性能調優來使用。也就是說,這種方法是對一個系統持續不段的加壓,看你在什么時候已經超出“我的要求”或系統崩潰。
-
壓力測試(強度測試)(Stress Test)
-
說明:壓力測試方法測試系統在一定飽和狀態下,例如cpu、內存在飽和使用情況下,系統能夠處理的會話能力,以及系統是否會出現錯誤
-
特點:1、這種性能測試方法的主要目的是檢查系統處於壓力性能下時應用的表現。2、這種性能測試一般通過模擬負載等方法,使得系統的資源使用達到較高的水平。3、這種性能測試方法一般用於測試系統的穩定性。也就是說,這種測試是讓系統處在很大強度的壓力之下,看系統是否穩定,哪里會出問題。
-
並發測試(Concurrency Testing)
-
說明:並發測試方法通過模擬用戶並發訪問,測試多用戶並發訪問同一個應用、同一個模塊或者數據記錄時是否存在死鎖或其者他性能問題。
-
特點:1、這種性能測試方法的主要目的是發現系統中可能隱藏的並發訪問時的問題。2、這種性能測試方法主要關注系統可能存在的並發問題,例如系統中的內存泄漏、線程鎖和資源爭用方面的問題。3、這種性能測試方法可以在開發的各個階段使用需要相關的測試工具的配合和支持。也就是說,這種測試關注點是多個用戶同時(並發)對一個模塊或操作進行加壓。
-
配置測試(Configuration Testing)
-
說明:配置測試方法通過對被測系統的軟\硬件環境的調整,了解各種不同對系統的性能影響的程度,從而找到系統各項資源的最優分配原則。
-
特點:1、這種性能測試方法的主要目的是了解各種不同因素對系統性能影響的程度,從而判斷出最值得進行的調優操作。2、這種性能測試方法一般在對系統性能狀況有初步了解后進行。3、這種性能測試方法一般用於性能調優和規划能力。也就是說,這種測試關注點是“微調”,通過對軟硬件的不段調整,找出這他們的最佳狀態,使系統達到一個最強的狀態。
-
可靠性測試
-
說明:通過給系統加載一定業務壓力(例如資源在70%-90%的使用率),使系統運行一段時間,以此檢測系統是否穩定運行。
-
特點:1、這種性能測試方法的主要目的是驗證是否支持長期穩定的運行。2、這種性能測試方法需要在壓力下持續一段時間的運行。(2~3天) 3、測試過程中需要關注系統的運行狀況。如果測試過程中發現,隨着時間的推移,響應時間有明顯的變化,或是系統資源使用率有明顯波動,都可能是系統不穩定的征兆。也就是說,這種測試的關注點是“穩定”,不需要給系統太大的壓力,只要系統能夠長期處於一個穩定的狀態。
-
失效恢復測試
-
說明:如果系統局部發生故障,用戶是否能夠繼續使用系統,以及如果這種情況發生,用戶將受到多大程度的影響。
-
特點:1.這種性能測試方法的主要目的是驗證在局部故障情況下,系統能否繼續使用。2.這種性能測試方法還需要指出,當問題發生時,“能支持多少用戶訪問”的結論和“采取何種應急措施”的方案。3.一般來說,只有對系統持續運行指標有明確要求的系統才需要進行這種類型的測試。
-
大數據量測試:針對某些系統存儲、傳輸、統計查詢等業務進行大數據量的測試。
5.4 性能測試流程

(1)性能需求分析
性能需求分析是整個性能測試工作開展的基礎,如果連性能的需求都沒弄清楚,后面的性能測試工具以及執行就無從談起了。在這一階段,性能測試人員需要與PM、DEV及項目相關的人員進行溝通,同時收集各種項目資料,對系統進行分析,確認測試的目標。並將其轉化為可衡量的具體性能指標。
測試需求分析階段的主要任務是分析被測系統及其性能需求,建立性能測試數據模型,分析性能需求,確定合理性能目標,並進行評審;
(2)性能測試准備
主要包括:設計場景,根據場景編寫程序、編寫腳本、准備測試環境,構造測試數據,環境預調優等;設計場景:針對系統的特點設計出合理的測試場景。為了讓測試結果更加准確,這里需要很細致的工作。如建立用戶模型,只有知道真實的用戶是如何對系統產生壓力,才可以設計出有代表性的壓力測試場景。這就涉及到很多信息,如用戶群的分布、各類型用戶用到的功能、用戶的使用習慣、工作時間段、系統各模塊壓力分布等等。只有從多方面不斷的積累這種數據,才會讓壓力場景更有意義。最后將設計場景轉換成具體的用例。
測試數據:測試數據的設計也是一個重點且容易出問題的地方。生成測試數據量達到未來預期數量只是最基礎的一步,更需要考慮的是數據的分布是否合理,需要仔細的確認程序中使用到的各種查詢條件,這些重點列的數值要盡可能的模擬真實的數據分布, 否則測試的結果可能是無效的。測試數據最好使用線上脫敏后的數據,盡可能接近真實用戶行為。
預調優:指根據系統的特點和團隊的經驗,提前對系統的各個方面做一些優化調整,避免測試執行過程中的無謂返工。比如一個高並發的系統,10000人在線,連接池和線程池的配置還用默認的,顯然是會測出問題的。
(3)執行性能測試
執行階段工作主要包含兩個方面的內容:一是執行測試用例模型,包括執行腳本和場景;其次測試過程監控,包括測試結果、記錄性能指標和性能計數器的值(4)結果分析與性能調優
發現問題或者性能指標達不到預期,及時的分析定位,處理后重復測試過程。性能問題通常是相互關聯相互影響的,表面上看到的現象很可能不是根本問題,而是另一處出現問題后引起的反應。這就要求監控收集數據時要全面,從多方面多個角度去判斷定位。調優的過程其實也是一種平衡的過程,在系統的多個方面達到一個平衡即可。
(5)性能報告與總結
編寫性能測試報告,闡明性能測試目標、性能結果、測試環境、數據構造規則、遇到的問題和解決辦法等。並對此次性能測試經驗進行總結與沉淀。具體性能測試報告的編寫可以參考《性能測試報告模板》。上面所有內容中,如果排除技術上的問題,性能測試中最難做好的,就是用戶模型的分析。它直接決定了壓力測試場景是否能夠有效的模擬真實世界壓力,而正是這種對真實壓力的模擬,才使性能測試有了更大的意義。可以說,性能測試做到一定程度,差距就體現在了模型建立上。至於性能問題的分析、定位或者調優,很大程度是一種技術問題,需要多方面的專業知識。數據庫、操作系統、網絡、開發都是一個合格的性能測試人員需要擁有的技能,只有這樣,才能從多角度全方位的去考慮分析問題。
6、性能工具性能對比
基於目前市場上主流的性能工具,進行橫向對比測試,以幫助我們在不同的環境靈活選擇不同的測試工具。6.1 性能工具對比結果
測試對象:Nginx index.html(612Byte),CPU:16核 / 內存:16GB / 磁盤:500GB壓力機:Ubuntu18.04, CPU: 8核 / 內存:8G / 磁盤: 500GB

6.2 性能工具介紹
(1)wrk / wrk2
wrk 是一款針對 Http 協議的基准測試工具,它能夠在單機多核 CPU 的條件下,使用系統自帶的高性能 I/O 機制,如 epoll,kqueue 等,通過多線程和事件模式,對目標機器產生大量的負載。PS: 其實,wrk 是復用了 redis 的 ae 異步事件驅動框架,准確來說 ae 事件驅動框架並不是 redis 發明的, 它來至於 Tcl 的解釋器 jim, 這個小巧高效的框架, 因為被 redis 采用而被大家所熟知。
優勢:
輕量級性能測試工具;安裝簡單(相對 Apache ab 來說);
學習曲線基本為零,幾分鍾就能學會咋用了;
基於系統自帶的高性能 I/O 機制,如 epoll, kqueue, 利用異步的事件驅動框架,通過很少的線程就可以壓出很大的並發量;
劣勢:
wrk 目前僅支持單機壓測,后續也不太可能支持多機器對目標機壓測,因為它本身的定位,並不是用來取代 JMeter, LoadRunner 等專業的測試工具,wrk 提供的功能,對后端開發人員來說,應付日常接口性能驗證還是比較友好的。之前在樂視我們的測試架構師基於wrk2主導開發並支持了分布式,開發成本還是略高的。
基礎使用:
子命令參數說明:使用方法: wrk <選項> <被測HTTP服務的URL>
Options:
-c, --connections <N> 跟服務器建立並保持的TCP連接數量
-d, --duration <T> 壓測時間
-t, --threads <N> 使用多少個線程進行壓測(為了減少現成的上下文切換,官方建議thread數量等同CPU核數)
-s, --script <S> 指定Lua腳本路徑
-H, --header <H> 為每一個HTTP請求添加HTTP頭
--latency 在壓測結束后,打印延遲統計信息
--timeout <T> 超時時間
-v, --version 打印正在使用的wrk的詳細版本信息
<N>代表數字參數,支持國際單位 (1k, 1M, 1G)
<T>代表時間參數,支持時間單位 (2s, 2m, 2h)
PS: 關於線程數,並不是設置的越大,壓測效果越好,線程設置過大,反而會導致線程切換過於頻繁,效果降低,一般來說,推薦設置成壓測機器 CPU 核心數的 2 倍到 4 倍就行了。
# 示例報告解析:
wrk -c400 -t24 -d30s --latency http://10.60.82.91/
Running 30s test @ http://www.baidu.com (壓測時間30s)
12 threads and 400 connections (共12個測試線程,400個連接)
(平均值) (標准差) (最大值)(正負一個標准差所占比例)
Thread Stats Avg Stdev Max +/- Stdev
(延遲)
Latency 386.32ms 380.75ms 2.00s 86.66%
(每秒請求數)
Req/Sec 17.06 13.91 252.00 87.89%
Latency Distribution (延遲分布)
50% 218.31ms
75% 520.60ms
90% 955.08ms
99% 1.93s
4922 requests in 30.06s, 73.86MB read (30.06s內處理了4922個請求,耗費流量73.86MB)
Socket errors: connect 0, read 0, write 0, timeout 311 (發生錯誤數)
Requests/sec: 163.76 (QPS 163.76,即平均每秒處理請求數為163.76)
Transfer/sec: 2.46MB (平均每秒流量2.46MB)
Running 30s test @ http://10.60.82.91/ (壓測時間30s)
32 threads and 400 connections (共32個測試線程,400個連接)
(平均值) (標准差) (最大值)(正負一個標准差所占比例)
Thread Stats Avg Stdev Max +/- Stdev
Latency(延遲) 10.31ms 40.13ms 690.32ms 98.33%
Req/Sec(每秒請求數) 2.14k 482.15 6.36k 77.39%
Latency Distribution
50% 5.11ms
75% 7.00ms
90% 9.65ms
99% 212.68ms
(30.10s內處理了2022092個請求,耗費流量1.62GB)
2022092 requests in 30.10s, 1.62GB read
Requests/sec: 67183.02 (QPS 67183.02,即平均每秒處理請求數為67183.02)
Transfer/sec: 55.03MB (平均每秒流量55.03MB)
(2)Jmeter
Jmeter是Java開發的、基於多線程並發模型的壓測工具,也是目前最流行的開源壓測工具,工作原理類似,如下圖:
-
其所謂的虛擬用戶(vuser)就是對應一個線程
-
在單個線程中,每個請求(query)都是同步調用的,下一個請求要等待前一個請求完成才能進行
-
一個請求(query)分成三部分:
-
send - 施壓端發送開始,直到承壓端接收完成
-
wait - 承壓端接收完成開始,直至業務處理結束
-
recv - 承壓端返回數據,直至施壓端接收完成
-
同一線程中連續的兩個請求之間存在等待時間這種概念,即圖中的空白處
答案是否定的。事實上一個進程在一個時間點只能執行一個線程,而所謂的並發是指在進程里不斷切換線程實現了看上去的多個任務的並發,但是線程上下文切換有很高的成本,過多的線程數反而會造成性能的嚴重下滑。

從應用角度來看,基於多線程的並發模型,往往需要設置最大並發數參數,而如果壓測場景需要不斷往上加壓,那這類工具其實挺難應付的。
(3)Locust
Locust是用Python開發的分布式壓測工具,近年來在國內比較流行。Locust並不是基於Python的多線程,而是coroutine(協程,gevent提供),gevent使用了libev或者 libuv作為eventloop。Locust響應時間失真問題:
Locust當壓力機CPU達到瓶頸后,響應時間會嚴重失真。
比如當Locust normal模式下,8進程,CPU瓶頸后,90%響應時間為340ms。同時wrk獲取的響應時間為59.41ms.


基本使用介紹:
裝飾器task可以設置壓力比例-
HttpUser示例:
from locust import HttpUser, task
class QuickstartUser(HttpUser):
# task為每個正在運行的用戶創建一個greenlet(微線程)
@task(1)
def detail(self):
self.client.get("http://10.60.82.91/")
def on_start(self):
pass
-
FastHttpUser示例
from locust import task
from locust.contrib.fasthttp import FastHttpUser
class QuickstartUser(FastHttpUser):
# task為每個正在運行的用戶創建一個greenlet(微線程)
@task(1)
def detail(self):
self.client.get("http://10.60.82.91/")
def on_start(self):
pass
-
啟動及分布式
# -c 並發用戶數
# -r 每秒啟動用戶數
# -t 持續運行時間
locust -f load_test.py --host=http://10.60.82.91 --no-web -c 10 -r 10 -t 1m
# 分布式
nohup locust -f locust_files/fast_http_user.py --master &
nohup locust -f locust_files/fast_http_user.py --worker --master-host=10.60.82.90 &
6.3 測試記錄
(1)wrk測試記錄
2線程:-c1000 -t1(因最少2線程) QPS: 35560.79
wrk -c1000 -t1 -d30s --latency http://10.60.82.91/
3線程:( -c1000 -t2 QPS: 66941.77 )
wrk -c1000 -t2 -d30s --latency http://10.60.82.91/
8線程: ( -c1000 -t8 QPS: 75579.30 )
wrk -c1000 -t8 -d30s --latency http://10.60.82.91/Nginx: 86% * 16 = 1376% , 達到CPU瓶頸


(2)Locust HttpUser記錄
1進程:(10並發,QPS:512)



8進程:(100並發,QPS:3300)



(3)Locust FastHttpUser記錄
1進程:(10並發,QPS:1836, 90%RT: 5ms)



8進程:(100並發,QPS:11000, 90%RT: 7ms)



(4)Jmeter 測試記錄
8核(100並發,QPS:38500)


