文章插圖

文章插圖
我們經常會談論性能、并發等問題,但是衡量性能不是說寫段代碼循環幾百次這么簡單 。最近從項目上的同事了解到了代碼化的測試性能測試工具 k6,以及結合之前用過的Java 微基準測試 (JMH)、AB (Apache Benchmark) 測試、Jmeter 做一下總結 。談性能,實際上結合實際的業務背景、網絡條件、測試數據的選擇等因素影響非常大,單純的談 QPS 等數據意義不大 。
這里介紹的幾個工具剛好能滿足平時開發工作中不同場景下衡量性能的需求,因此整理出來 。
Java 微基準測試 (JMH) 可以用于衡量一段 Java 代碼到底性能如何,例如我們平時總是談 StringBuilder 比 new String() 快很多 。我們有一個很好地量化方法,就可以很直觀的展示出一段代碼的性能優劣 。AB (Apache Benchmark) 測試是 Apache 服務器內置的一個 http web 壓測工具,非常簡單易用 。Mac 預裝了 Apache,因此可以隨手使用來測試一個頁面或者 API 的性能 。貴在簡單易用,無需額外安裝 。k6 一款使用 go 語言編寫,支持用戶編寫測試腳本的測試套件 。彌補了 ab 測試功能不足,以及 jemeter 不容易代碼化的缺點 。也是項目上需要使用,從同事那里了解到的 。Jmeter 老牌的性能測試工具,有大量專門講 jmeter 的資料,本文不再贅述 。
那我們從 JMH 開始從來看下這幾個工具的特點和使用吧 。
Java 微基準測試
StringBuilder 到底比 new String() 快多少呢?我們可以使用 JMH 來測試一下 。JMH 是一個用于構建、運行和分析 Java 方法運行性能工具,可以做到 nano/micro/mili/macro 時間粒度 。JMH 不僅可以分析 Java 語言,基于 JVM 的語言都可以使用 。
JMH 由 OpenJDK 團隊開發,由一次下載 OpenJDK 時注意到官網還有這么一個東西 。
OpenJdk 官方運行 JMH 測試推的方法是使用 Maven 構建一個單獨的項目,然后把需要測試的項目作為 Jar 包引入 。這樣能排除項目代碼的干擾,得到比較可靠地測試效果 。當然也可以使用 IDE 或者 Gradle 配置到自己項目中,便于和已有項目集成,代價是配置比較麻煩并且結果沒那么可靠 。
使用 Maven 構建基準測試
根據官網的例子,我們可以使用官網的一個模板項目 。
mvn archetype:generate
-DinteractiveMode=false
-DarchetypeGroupId=org.openjdk.jmh
-DarchetypeArtifactId=jmh-java-benchmark-archetype
-DgroupId=org.sample
-DartifactId=test
-Dversion=1.0
【網絡質量檢測工具 網絡質量在線測試】創建一個項目,導入 IDE,Maven 會幫我們生成一個測試類,但是這個測試類沒有任何內容,這個測試也是可以運行的 。
先編譯成 jar
mvn clean install
然后使用 javar -jar 來運行測試
java -jar target/benchmarks.jar
運行后可以看到輸出信息中包含 JDK、JVM 等信息,以及一些用于測試的配置信息 。
#JMH version: 1.22# VM version: JDK 1.8.0_181, Java HotSpot(TM) 64-Bit Server VM, 25.181-b13# VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/bin/java# VM options: # Warmup: 5 iterations, 10 s each# Measurement: 5 iterations, 10 s each# Timeout: 10 min per iteration# Threads: 1 thread, will synchronize iterations# Benchmark mode: Throughput, ops/time# Benchmark: org.sample.MyBenchmark.testSimpleString下面是一些配置信息說明Warmup 因為 JVM 即時編譯的存在,所以為了更加準確有一個預熱環節,這里是預熱 5,每輪 10s 。Measurement 是真實的性能測量參數,這里是 5輪,每輪10s 。Timeout 每輪測試,JMH 會進行 GC 然后暫停一段時間,默認是 10 分鐘 。Threads 使用多少個線程來運行,一個線程會同步阻塞執行 。Benchmark mode 輸出的運行模式,常用的有下面幾個 。Throughput 吞吐量,即每單位運行多少次操作 。AverageTime 調用的平均時間,每次調用耗費多少時間 。SingleShotTime 運行一次的時間,如果把預熱關閉可以測試代碼冷啟動時間Benchmark 測試的目標類
實際上還有很多配置,可以通過 -h 參數查看
java -jar target/benchmarks.jar -h
由于默認的配置停頓的時間太長,我們通過注解修改配置,并增加了 Java 中最基本的字符串操作性能對比 。
@BenchmarkMode(Mode.Throughput)@Warmup(iterations = 3)@Measurement(iterations = 5, time = 5, timeUnit = TimeUnit.SECONDS)@Threads(8)@Fork(1)@OutputTimeUnit(TimeUnit.MILLISECONDS)public class MyBenchmark {@Benchmarkpublic void testSimpleString() {String s = "Hello world!";for (int i = 0; i < 10; i++) {s += s;}}@Benchmarkpublic void testStringBuilder() {StringBuilder sb = new StringBuilder();for (int i = 0; i < 10; i++) {sb.append(i);}}}在控制臺可以看到輸出的測試報告,我們直接看最后一部分即可 。BenchmarkModeCntScoreErrorUnitsMyBenchmark.testSimpleStringthrpt10226.930 ±16.621ops/msMyBenchmark.testStringBuilderthrpt1080369.037 ± 3058.280ops/msScore 這列的意思是每毫秒完成了多少次操作,可見 StringBuilder 確實比普通的 String 構造器性能高很多 。更多有趣的測試
實際上平時 Java 開發中一些細節對性能有明顯的影響,雖然對系統整體來說影響比較小,但是注意這些細節可以低成本的避免性能問題堆積 。
其中一個非常有意思細節是自動包裝類型的使用,即使是一個簡單的 for 循環,如果不小心講 int 使用成 Integer 也會造成性能浪費 。
我們來編寫一個簡單的基準測試
@Benchmarkpublic void primaryDataType() {int sum = 0;for (int i = 0; i < 10; i++) {sum += i;}}@Benchmarkpublic void boxDataType() {int sum = 0;for (Integer i = 0; i < 10; i++) {sum += i;}}運行測試后,得到下面的測試結果AutoBoxBenchmark.boxDataTypethrpt5312779.633 ±26761.457ops/msAutoBoxBenchmark.primaryDataTypethrpt58522641.543 ± 2500518.440ops/ms基本類型的性能高出了一個數量級 。當然你可能會說基本類型這種性能問題比較微小,但是性能往往就是這種從細微處提高的 。另外編寫 JMH 測試也會讓團隊看待性能問題更為直觀 。一份直觀的 Java 基礎性能報告
下面是我寫的常見場景的性能測試,例如 StringBuilder 比 new String() 速度快幾個數量級 。
Apache Benchmark 測試
我想用命令行快速簡單的壓測一下網站該怎么辦呢?Apache Benchmark (簡稱 ab,不同于產品領域的 A/B 測試) 是 Apache web 服務器自帶的性能測試工具,在 windows 或者 linux 上安裝了 Apache 服務器就可以在其安裝位置的 bin 目錄中找到 ab 這個程序 。
ab 使用起來非常簡單,一般只需要 -n 參數指明發出請求的總數,以及 -c 參數指明測試期間的并發數 。
例如對 ThoughtWorks 官網首頁發出 100 個請求,模擬并發數為 10:
ab -n 100 -c 10 https://thoughtworks.com/
需要注意的是 ab 工具接收一個 url 作為參數,僅僅是一個域名是不合法的,需要增加 / 表示首頁 。稍等片刻后就可以看到測試報告:
Server Software:nginx/1.15.6Server Hostname:thoughtworks.comServer Port:443SSL/TLS Protocol:TLSv1.2,ECDHE-RSA-AES256-GCM-SHA384,2048,256Server Temp Key:ECDH P-256 256 bitsTLS Server Name:thoughtworks.comDocument Path:/Document Length:162 bytesConcurrency Level:10Time taken for tests:42.079 secondsComplete requests:100Failed requests:0Non-2xx responses:100Total transferred:42500 bytesHTML transferred:16200 bytesRequests per second:2.38 [#/sec] (mean)Time per request:4207.888 [ms] (mean)Time per request:420.789 [ms] (mean, across all concurrent requests)Transfer rate:0.99 [Kbytes/sec] receivedConnection Times (ms)minmean[+/-sd] medianmaxConnect:1056 2474 3006.1114423032Processing:349740 1003.53798461Waiting:349461 290.93772265Total:1411 3214 3273.9167423424Percentage of the requests served within a certain time (ms)50%167466%295475%395180%439790%671395%940098%1497399%23424 100%23424 (longest request)從這個報告中可以看到服務器的一些基本信息,以及請求的統計信息 。比較重要的指標是 Requests per second 每秒鐘完成的請求數量,不嚴格的說也就是我們的平時說的 QPS 。ab 測試是專為 http 請求設計的,因此 ab 的其他參數和 curl 的參數比較類似,也可以指定 http method 以及 cookies 等參數 。
K6 測試套件
我需要編寫復雜的測試腳本,并保留壓測的腳本、參數、數據,以及版本化該怎么做呢?k6 是一個壓力測試套件,使用 golang 編寫 。主要特性有:
提供了友好的 CLI 工具使用 JavaScript 代碼編寫測試用例可以根據性能條件設置閾值,表明成功還是失敗
k6 沒有使用 nodejs 而是 golang 程序,通過包裹了一個 JavaScript 運行時來運行 JavaScript 腳本,因此不能直接使用 npm 包以及 Nodejs 提供的一些 API 。
同時,k6 在運行測試時,沒有啟動瀏覽器,主要用于測試頁面以及 API 加載速度 。k6 提供了通過網絡請求(HAR)生成測試腳本的方法,實現更簡便的測試腳本編寫,以及 session 的維護 。
使用
在 Mac 上比較簡單,直接使用 HomeBrew 即可安裝:
brew install k6
其他平臺官網也提供了相應的安裝方式,比較特別的是提供了 Docker 的方式運行 。
直接使用 k6 的命令運行測試,官網提供了一個例子:
k6 run github.com/loadimpact/k6/samples/http_get.js
也可以編寫自己的測試腳本:
import http from "k6/http";import { sleep } from "k6";export default function() {http.get("https://www.thoughtworks.com/");sleep(1);};保存文件 script.js 后運行 k6 命令k6 run script.js
然后可以看到 http 請求的各項指標
/| ̄ ̄|/ ̄ ̄// ̄///||_/// ///||/ ̄ ̄/|| ̄| (_) |/ __________|__|__ ___/ .ioexecution: localoutput: -script: k6.jsduration: -,iterations: 1vus: 1, max: 1done [==========================================================] 1 / 1data_received..............: 108 kB 27 kB/sdata_sent..................: 1.0 kB 252 B/shttp_req_blocked...........: avg=2.35smin=2.35smed=2.35smax=2.35sp(90)=2.35sp(95)=2.35shttp_req_connecting........: avg=79.18msmin=79.18msmed=79.18msmax=79.18msp(90)=79.18msp(95)=79.18mshttp_req_duration..........: avg=639.03ms min=639.03ms med=639.03ms max=639.03ms p(90)=639.03ms p(95)=639.03mshttp_req_receiving.........: avg=358.12ms min=358.12ms med=358.12ms max=358.12ms p(90)=358.12ms p(95)=358.12mshttp_req_sending...........: avg=1.79msmin=1.79msmed=1.79msmax=1.79msp(90)=1.79msp(95)=1.79mshttp_req_tls_handshaking...: avg=701.46ms min=701.46ms med=701.46ms max=701.46ms p(90)=701.46ms p(95)=701.46mshttp_req_waiting...........: avg=279.12ms min=279.12ms med=279.12ms max=279.12ms p(90)=279.12ms p(95)=279.12mshttp_reqs..................: 10.249921/siteration_duration.........: avg=4smin=4smed=4smax=4sp(90)=4sp(95)=4siterations.................: 10.249921/svus........................: 1min=1 max=1vus_max....................: 1min=1 max=1k6 提供的性能指標相對 ab 工具多很多,也可以通過腳本自己計算性能指標 。和 ab 工具中表明每秒鐘處理完的請求數是 http_reqs,上面的測試默認只有一個用戶的一次請求,如果通過參數增加更多請求,可以看到和 ab 工具得到的結果比較接近 。運行壓力測試時,需要增加更多的虛擬用戶(VU),vus 參數和持續時間的參數:
k6 run –vus 10 –duration 30s script.js
編寫測試腳本的一些規則
default 方法是用于給每個 VU 以及每次迭代重復運行的,因此需要把真正的測試代碼放到這個方法中,例如訪問某個頁面 。
為了保證測試的準確性,一些初始化的代碼不應該放到 default 方法中 。尤其是文件的讀取等依賴環境上下文的操作不能放到 default 方法中執行,這樣做也會丟失 k6 分布式運行的能力 。
前面提到的命令行參數,例如指定虛擬用戶數量 –vus 10,這些參數也可以放到腳本代碼中 。通過暴露一個 options 對象即可 。
export let options = {vus: 10,duration: "30s"};為了更為真實的模擬用戶訪問的場景,k6 提供了在整個測試期間讓用戶數量和訪問時間呈階段性變化的能力 。只需要在 options 中增加 stages 參數即可:export let options = { stages: [{ duration: "30s", target: 20 },{ duration: "1m30s", target: 10},{ duration: "20s", target: 0 },]};在測試過程中需要檢查網絡請求是否成功,返回的狀態碼是否正確,以及響應時間是否符合某個閾值 。在腳本中可以通過調用 check() 方法編寫檢查語句,以便 k6 能收集到報告 。import http from "k6/http";import { check, sleep } from "k6";export let options = {vus: 10,duration: "30s"};export default function() {let res = http.get("https://www.thoughtworks.com/");check(res, {"status was 200": (r) => r.status == 200,"transaction time OK": (r) => r.timings.duration < 200});sleep(1);};報告輸出k6 默認將報告輸出到 stdout 控制臺,同時也提供了多種格式報告輸出,包括:
JSONCSVInfluxDBApache KafkaStatsDDatadogLoad Impact cloud platform
當然,我們在編寫測試的時候不可能只有一個用例,對多個場景可以在腳本中通過group 進行分組,分組后輸出的報告會按照分組排列 。同時,也可以使用對一個組整體性能衡量的指標 group_duration 。
import { group } from "k6";export default function() {group("user flow: returning user", function() {group("visit homepage", function() {// load homepage resources});group("login", function() {// perform login});});};InfluxDB 等外部數據收集平臺時,還可以打上標簽,供過濾和檢索使用 。k6 提供了一些內置的標簽,并允許用戶自定義標簽 。總結
實際上用于性能測試的工具還有很多,也有一些專門的工具針對網絡質量(iperf) 、數據庫(sysbench)、前端頁面(PageSpeed)等專門方面進行性能測試 。
寫本文的初衷是想說評價性能,以及做性能優化的第一步應該是尋找到合適工具做一次基準測試,這樣的優化往往才有意義 。我在使用 JMH 后不僅在工作中使用它對一些代碼片段進行測試以及優化,同時更重要的是,在codereview 中對某些操作關于性能的討論不再基于經驗,而是事實 。
- 數據中心網絡架構設計 網站架構設計
- 在線條形碼生成器-條形碼在線生成工具 在線條形碼生成器 村美小站
- 虛擬機上網設置教程 虛擬機的網絡連接設置
- 哪家網絡優化好 怎么優化網絡速度
- 智能光網絡技術原理 智能光網絡技術50字介紹
- dm分區工具圖解教程 dm 分區
- 網絡全流量分析系統 網絡流量分析技術
- 淘寶官方數據分析工具 淘寶指數官方網站
- ubuntu網絡配置文件詳解 ubuntu配置上網
- 網絡監控的安裝方法 網絡監控安裝教程視頻
