引子:自上世紀末Kent Beck提出TDD(Test-Driven Development)開發理念以來,開發和測試的邊界變的愈來愈模糊,從本來上下游的依賴關係,逐步演變成你中有我、我中有你的互賴關係,甚至不少公司設立了新的QE(Quality Engineer)職位。和傳統的QA(Quality Assurance)不一樣,QE的主要職責是經過工程化的手段保證項目質量,這些手段包括但不只限於編寫單元測試、集成測試,搭建自動化測試流程,設計性能測試等。能夠說,QE身上兼具了QA的質量意識和開發的工程能力。我會從開發的角度分三期聊聊QE這個亦測試亦開發的角色所需的基本技能,這篇是第二篇。html
前情概要:java
先來看一下維基百科裏對性能測試的定義,github
In software engineering, performance testing is in general, a testing practice performed to determine how a system performs in terms of responsiveness and stability under a particular workload. - Wikipediaspring
注意上述定義中有三個關鍵詞:shell
responsiveness,即響應時間,請求發出去以後,服務端須要多久才能返回結果,顯然響應時間越短,性能越好。apache
stability,即穩定性,一樣的請求,不一樣時刻發出去,響應時間差異越小,穩定性越好,性能也越好。segmentfault
workload,即負載,同一時刻服務端收到的請求數量,其中單位時間內成功處理的請求數量即吞吐量,吞吐量越大,性能越好。架構
響應時間和吞吐量是衡量應用性能好壞最重要的兩個指標。對於絕大多數應用,剛開始的時候,響應時間最短;隨着負載的增大,吞吐量快速上升,響應時間也逐漸變長;當負載超過某一個值以後,響應時間會忽然呈指數級放大,同時吞吐量也應聲下跌,應用性能急劇降低,整個過程以下:併發
圖片出處:性能測試應該怎麼作?
瞭解了應用性能變化的廣泛規律,性能測試的目的也就有了答案:針對某一應用,找出響應時間和吞吐量的量化關係,找到應用性能變化的臨界點。你可能會問,知道了這些有什麼用呢?在我看來,至少有3個層面的好處:
第一,有的放矢,提升資源利用率。性能測試的過程就是量化性能的過程,有了各類性能數據,你才能對應用性能進行定量分析,找到並解決潛在的性能問題,從而提升資源利用率。
第二,科學的進行容量規劃。找到了應用性能變化的臨界點,也就很容易找到單節點的性能極限,這是進行容量規劃的重要決策依據。好比某一應用在單節點下的極限吞吐量是2000 QPS,那麼面對10000 QPS的流量,至少須要部署5個節點。
第三,改善QoS(Quality of Service)。不少時候,資源是有限的,面對超出服務能力的流量,爲了保證QoS,必須作出取捨(好比限流降級,開關預案等),應用性能數據是設計QoS方案的重要依據。
用平均值來衡量響應時間是性能測試中最多見的誤區。從第1小節的插圖能夠看出,隨着吞吐量的增大,響應時間會逐漸變長,當達到最大吞吐量以後,響應時間會開始加速上升,尤爲是排在後面的請求。在這個時刻,若是隻看平均值,你每每察覺不到問題,由於大部分請求的響應時間仍是很短的,慢請求只佔一個很小的比例,因此平均值變化不大。但實際上,可能已經有超過1%,甚至5%的請求的響應時間已經超出設計的範圍了。
更科學、更合理的指標是看TP95或者TP99響應時間。TP是Top Percentile的縮寫,是一個統計學術語,用來描述一組數值的分佈特徵。以TP95爲例,假設有100個數字,從小到大排序以後,第95個數字的值就是這組數字的TP95值,表示至少有95%的數字是小於或者等於這個值。
以一次具體的性能測試爲例,
總共有1000次請求,平均響應時間是58.9ms,TP95是123.85ms(平均響應時間的2.1倍),TP99是997.99ms(平均響應時間的16.9倍)。假設應用設計的最大響應時間是100ms,單看平均時間是徹底符合要求的,但實際上已經有超過50個請求失敗了。若是看TP95或者TP99,問題就很清楚了。
雖然說衡量應用性能好壞最主要是看響應時間和吞吐量,但這裏有個大前提,全部請求(若是作不到全部,至少也要絕大多數請求,好比99.9%)都被成功處理了,而不是返回一堆錯誤碼。若是不能保證這一點,那麼再低的響應時間,再高的吞吐量都是沒有意義的。
性能測試的第三個誤區是隻關注服務端,而忽略了測試端自己可能也存在限制。好比測試用例設置了10000併發數,但實際運行用例的機器最大隻支持5000併發數,若是隻看服務端的數據,你可能會誤覺得服務端最大就只支持5000併發數。若是遇到這種狀況,或者換用更高性能的測試機器,或者增長測試機器的數量。
介紹完性能測試相關的一些概念以後,再來看一下有哪些工具能夠進行性能測試。
JMeter多是最經常使用的性能測試工具。它既支持圖形界面,也支持命令行,屬於黑盒測試的範疇,對非開發人員比較友好,上手也很是容易。圖形界面通常用於編寫、調試測試用例,而實際的性能測試建議仍是在命令行下運行。
併發設置
請求參數
結果報表
命令行下的經常使用命令:
設置JVM參數:JVM_ARGS="-Xms2g -Xmx2g"
運行測試:jmeter -n -t <jmx_file>
運行測試同時生成報表:jmeter -n -t <jmx_file> -l <log_file> -e -o <report_dir>
除了JMeter,其餘經常使用的性能測試工具還有ab, http_load, wrk以及商用的LoaderRunner。
若是測試用例比較複雜,或者負責性能測試的人員具備必定的開發能力,也能夠考慮使用一些框架編寫單獨的性能測試程序。對於Java開發人員而言,JMH是一個推薦的選擇。相似於JUnit,JMH提供了一系列註解用於編寫測試用例,以及一個運行測試的引擎。事實上,即將發佈的JDK 9默認就會包含JMH。
下面是我GitHub上的示例工程裏的一個例子,
@BenchmarkMode(Mode.Throughput) @Fork(1) @Threads(Threads.MAX) @State(Scope.Benchmark) @Warmup(iterations = 1, time = 3) @Measurement(iterations = 3, time = 3) public class VacationClientBenchmark { private VacationClient vacationClient; @Setup public void setUp() { VacationClientConfig clientConfig = new VacationClientConfig("http://localhost:3000"); vacationClient = new VacationClient(clientConfig); } @Benchmark public void benchmarkIsWeekend() { VacationRequest request = new VacationRequest(); request.setType(PERSONAL); OffsetDateTime lastSunday = OffsetDateTime.now().with(TemporalAdjusters.previous(SUNDAY)); request.setStart(lastSunday); request.setEnd(lastSunday.plusDays(1)); Asserts.isTrue(vacationClient.isWeekend(request).isSuccess()); } // 僅限於IDE中運行 public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(VacationClientBenchmark.class.getSimpleName()) .build(); new Runner(opt).run(); } }
其中:
@BenchmarkMode: 性能測試模式,支持Throughput,AverageTime,SingleShotTime等多種模式。
@Fork: 設置運行性能測試的Fork進程數,默認是0,表示共用JMH主進程。
@Threads: 併發數,Threads.MAX表示同系統的CPU核數。
@Warmup和@Measurement: 分別設置預熱和實際性能測試的運行輪數,每輪持續的時間等
@Setup和@Benchmark: 等同於JUnit裏的@BeforeClass和@Test
在命令行下,使用JMH框架編寫的性能測試程序只能以Jar包的形式運行(Main函數固定爲org.openjdk.jmh.Main),所以通常會針對每一個JMH程序單獨維護一個項目。若是是Maven項目,可使用官方提供的jmh-java-benchmark-archetype,若是是Gradle項目,可使用jmh-gradle-plugin插件。
以上就是我對性能測試的一些看法,歡迎你到個人留言板分享,和你們一塊兒過過招。下一篇我將聊一下Web的自動化測試,敬請期待。