大家好,我是張晉濤。
本篇我將為你介紹一個工具 - k6 ,它和 K8s 並沒有什麼直接的關係,它是一款開源的效能壓測工具。
k6 背後的故事
2016 年 8 月,k6 在 GitHub 上釋出了第一個版本,至此,一個出色的開源負載壓測工具進入了人們的視野。
2021 年的 6 月,對於 Grafana 和 k6 來講是個大日子,Grafana Labs 收購了 k6 。
而事實上, Grafana 與 k6 的緣分還要追溯到更早的 2 年前。
2019 年,在進行 Grafana 6.0 的短期令牌重新整理行為的壓測時,Grafana Labs 進行了一系列的技術選型。
由於 Grafana Labs 的大部分後端軟體是使用 Go 來實現的,恰巧 k6 滿足 OSS 和 Go 需求,並且負載測試是使用 JS 編寫(Grafana 前端框架及 UI 都在使用)。這使得 k6 自 Grafana 6.0 版本開始,不斷地為 Grafana 開發者及測試者完成追蹤 bug 的使命。
圖 1 ,k6 加入 Grafana Labs
多樣的壓測工具
一個稱心應手的自動化負載壓測工具會極大的提升程式開發人員的程式碼質量及效率。
下圖中是一些比較常見的用於負載壓測的工具,我們可以在 GitHub 上看到,目前,更新比較頻繁、活躍的專案主要有:Gatling, Jmeter 和 k6 。
圖 2 ,壓測工具們
如何從中選擇,簡單的講就是工具效率的比拼。主要從以下兩個方面來考量:
- 工具效能
- 工具使用體驗
下圖對以上工具進行了一些簡單的對比。
這裡我主要對比下其中較為活躍的 3 個專案。
- JMeter - 熟悉 Java 的小夥伴可能比較瞭解這個工具。由於存在時間久,JMeter 的功能是這之中最全面的,並且整合、附加元件做的較好。基於它構建的 SaaS 服務 Blazemeter,相信大家也都熟識。這也導致了一個極大的問題,使用的複雜性高及不夠輕量級;
- Gatling - Gatling 也有著 SaaS 產品 Gatling Frontline。就使用門檻來講,JS 要比 Scala 要低很多;
- k6 - k6 最初是由 SaaS 服務 Load Impact 的幾名員工開發維護。使用門檻低(JS),引數化更簡單,並且 “負載測試即程式碼” 的理念也讓他的維護成本更低。未來可期。
圖 3 ,3 種熱門工具比一比
執行效果
或者這樣:
安裝 k6
k6 是用 Go 語言開發的,要安裝 k6 步驟很簡單,只要直接在其 GitHub 的 Release 頁面下載二進位制檔案即可。比如:
(MoeLove) ➜ wget -q https://github.com/grafana/k6/releases/download/v0.35.0/k6-v0.35.0-linux-amd64.tar.gz
(MoeLove) ➜ tar -xzf k6-v0.35.0-linux-amd64.tar.gz
(MoeLove) ➜ ls
k6-v0.35.0-linux-amd64 k6-v0.35.0-linux-amd64.tar.gz
(MoeLove) ➜ mv ./k6-v0.35.0-linux-amd64/k6 ~/bin/k6
(MoeLove) ➜ k6 version
k6 v0.35.0 (2021-11-17T09:53:18+0000/1c44b2d, go1.17.3, linux/amd64)
或者也可以直接使用它的 Docker 映象:
➜ ~ docker run --rm loadimpact/k6 version
k6 v0.35.0 (2021-11-17T09:53:03+0000/1c44b2d, go1.17.3, linux/amd64)
核心概念
在 k6 中並沒有太多的概念。其中最主要的就是用來執行測試的 virtual users (VUs) ,它的本質就是併發執行任務的次數。
在使用 k6 執行測試的時候,可以通過 --vus
或者 -u
進行指定,預設是 1 。
上手實踐
我個人感覺 k6 在目前的這些主流壓測工具中算使用者體驗比較好的一個。它使用 JS(ES6)作為配置語言,還是比較方便的,我們來做一些示例。
簡單請求
如果對於進行 HTTP 請求的時候,我們只需要從 k6/http
匯入 http
即可。
注意在 k6 中,預設情況下必須得有個作為入口的 default
函式,這類似我們常用的 main
函式。
import http from "k6/http";
export default function(){
http.get("https://test-api.k6.io/public/crocodiles/")
}
執行後效果如下:
(MoeLove) ➜ k6 run simple_http_get.js
/\ |‾‾| /‾‾/ /‾‾/
/\ / \ | |/ / / /
/ \/ \ | ( / ‾‾\
/ \ | |\ \ | (‾) |
/ __________ \ |__| \__\ \_____/ .io
execution: local
script: simple_http_get.js
output: -
scenarios: (100.00%) 1 scenario, 1 max VUs, 10m30s max duration (incl. graceful stop):
* default: 1 iterations for each of 1 VUs (maxDuration: 10m0s, gracefulStop: 30s)
running (00m01.1s), 0/1 VUs, 1 complete and 0 interrupted iterations
default ✓ [======================================] 1 VUs 00m01.1s/10m0s 1/1 iters, 1 per VU
data_received..................: 6.3 kB 5.7 kB/s
data_sent......................: 634 B 578 B/s
http_req_blocked...............: avg=848.34ms min=848.34ms med=848.34ms max=848.34ms p(90)=848.34ms p(95)=848.34ms
http_req_connecting............: avg=75.59µs min=75.59µs med=75.59µs max=75.59µs p(90)=75.59µs p(95)=75.59µs
http_req_duration..............: avg=247.46ms min=247.46ms med=247.46ms max=247.46ms p(90)=247.46ms p(95)=247.46ms
{ expected_response:true }...: avg=247.46ms min=247.46ms med=247.46ms max=247.46ms p(90)=247.46ms p(95)=247.46ms
http_req_failed................: 0.00% ✓ 0 ✗ 1
http_req_receiving.............: avg=455.24µs min=455.24µs med=455.24µs max=455.24µs p(90)=455.24µs p(95)=455.24µs
http_req_sending...............: avg=103.77µs min=103.77µs med=103.77µs max=103.77µs p(90)=103.77µs p(95)=103.77µs
http_req_tls_handshaking.......: avg=848.07ms min=848.07ms med=848.07ms max=848.07ms p(90)=848.07ms p(95)=848.07ms
http_req_waiting...............: avg=246.9ms min=246.9ms med=246.9ms max=246.9ms p(90)=246.9ms p(95)=246.9ms
http_reqs......................: 1 0.911502/s
iteration_duration.............: avg=1.09s min=1.09s med=1.09s max=1.09s p(90)=1.09s p(95)=1.09s
iterations.....................: 1 0.911502/s
vus............................: 1 min=1 max=1
vus_max........................: 1 min=1 max=1
k6 預設會將執行後的結果輸出到終端。同時它自帶了一些指標會同時輸出。
這些指標基本上都是語義化的,看名字就可以理解其含義,這裡就不一一介紹了。
帶檢查的請求
我們可以在請求中同時增加一些測試,判斷介面的響應值是否符合我們的預期。如下:
import http from "k6/http";
import { check, group } from "k6";
export default function() {
group("GET", function() {
let res = http.get("http://httpbin.org/get?verb=get");
check(res, {
"status is 200": (r) => r.status === 200,
"is verb correct": (r) => r.json().args.verb === "get",
});
});
}
通過引入了 check
函式,來執行一些判斷的邏輯,當然上述的 ==>
其實是 ES6 中的一種簡寫,將其展開為正常的函式也可以。比如:
import http from "k6/http";
import { check, group } from "k6";
export default function() {
group("GET", function() {
let res = http.get("http://httpbin.org/get?verb=get");
check(res, {
"status is 200": function(r){
return r.status === 200
},
"is verb correct": (r) => r.json().args.verb === "get",
});
});
}
使用 k6 執行此指令碼後,得到的輸出相比之前的多瞭如下內容:
█ GET
✓ status is 200
✓ is verb correct
checks.........................: 100.00% ✓ 2 ✗ 0
從這裡可以看到我們當前請求介面的測試是否通過(也可以用來判斷當前介面是否能正常提供服務)。
自定義指標輸出
接下來我們嘗試下在壓測過程中定義一些自己定的指標。只需要從 k6/metrics
中匯入一些不同型別的指標即可。這和在 Prometheus 中的型別基本一致。
這裡我增加了兩個 metric。一個 testCounter
用於統計一共執行了多少次測試, passedRate
計算通過率。
import http from "k6/http";
import { Counter, Rate } from "k6/metrics";
import { check, group } from "k6";
let testCounter = new Counter("test_counter");
let passedRate = new Rate("passed_rate");
export default function() {
group("GET", function() {
let res = http.get("http://httpbin.org/get?verb=get");
let passed = check(res, {
"status is 200": (r) => r.status === 200,
"is verb correct": (r) => r.json().args.verb === "get",
});
testCounter.add(1);
passedRate.add(passed);
});
}
這裡我們設定了 2 個 VU, 以及設定了執行過程為 10s
執行後的輸出如下:
(MoeLove) ➜ k6 run -u 2 -d 10s simple_custom_metrics.js
...
execution: local
script: simple_custom_metrics.js
output: -
scenarios: (100.00%) 1 scenario, 2 max VUs, 40s max duration (incl. graceful stop):
* default: 2 looping VUs for 10s (gracefulStop: 30s)
running (10.4s), 0/2 VUs, 36 complete and 0 interrupted iterations
default ✓ [======================================] 2 VUs 10s
█ GET
✓ status is 200
✓ is verb correct
checks.........................: 100.00% ✓ 72 ✗ 0
data_received..................: 18 kB 1.7 kB/s
data_sent......................: 3.9 kB 372 B/s
group_duration.................: avg=567.35ms min=440.56ms med=600.52ms max=738.73ms p(90)=620.88ms p(95)=655.17ms
http_req_blocked...............: avg=266.72µs min=72.33µs med=135.14µs max=776.66µs p(90)=644.4µs p(95)=719.96µs
http_req_connecting............: avg=170.04µs min=45.51µs med=79.9µs max=520.69µs p(90)=399.41µs p(95)=463.55µs
http_req_duration..............: avg=566.82ms min=439.69ms med=600.31ms max=738.16ms p(90)=620.52ms p(95)=654.61ms
{ expected_response:true }...: avg=566.82ms min=439.69ms med=600.31ms max=738.16ms p(90)=620.52ms p(95)=654.61ms
http_req_failed................: 0.00% ✓ 0 ✗ 36
http_req_receiving.............: avg=309.13µs min=122.4µs med=231.72µs max=755.3µs p(90)=597.95µs p(95)=641.92µs
http_req_sending...............: avg=80.69µs min=20.47µs med=38.91µs max=235.1µs p(90)=197.87µs p(95)=214.79µs
http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s
http_req_waiting...............: avg=566.43ms min=439.31ms med=600.16ms max=737.8ms p(90)=620.19ms p(95)=654.18ms
http_reqs......................: 36 3.472534/s
iteration_duration.............: avg=567.38ms min=440.62ms med=600.53ms max=738.75ms p(90)=620.89ms p(95)=655.2ms
iterations.....................: 36 3.472534/s
passed_rate....................: 100.00% ✓ 36 ✗ 0
test_counter...................: 36 3.472534/s
vus............................: 2 min=2 max=2
vus_max........................: 2 min=2 max=2
可以看到在輸出中多了兩行:
passed_rate....................: 100.00% ✓ 36 ✗ 0
test_counter...................: 36 3.472534/s
與我們的預期相符。
不過這樣看起來不夠直觀,我們可以嘗試使用 k6 Cloud 來展示結果。登陸後,只要在執行 k6 時,通過 -o cloud
的方式將輸出指定到 cloud 就可以在 cloud 上看到所有的指標了
總結
本篇主要是在介紹一個現代化的使用者體驗相對較好的壓測工具 k6 。我目前正在計劃將其引入到我們專案的 CI 中,以便了解每次核心部分的變更對專案效能的影響。
後續推進順利的話,會再分享 k6 如何應用到 CI 環境中,敬請期待。
歡迎訂閱我的文章公眾號【MoeLove】