使用 K6 來給你的服務做一次負載和壓力測試吧
前言
負載測試,壓力測試可以衡量服務是否是一個高可用,高效能的服務。負載測試能檢驗在不同的工作負荷下,服務的硬體消耗和響應,從而得到不同負載情況下的效能指標。壓力測試能檢驗軟硬體環境下服務所能承受的最大負荷並幫助找出系統瓶頸所在。
環境說明
- 騰訊雲輕量伺服器, 配置
1c 2g 6mb
,系統是ubuntu 20.14
。
K6是什麼
k6 是用 Go 語言編寫的一種高效能的負載測試工具。具有下面幾個特點。
- K6 嵌入了 JavaScript 執行時,可以使用 JavaScript ES2015/ES6 來編寫指令碼。
- 強大的 CLI 工具。
- 使用 Checks 和 Thresholds 可以更加輕鬆的做面向目標的自動化的負載測試。
K6 相對於 JMeter 的優勢
- 因為 K6 是 Go 編寫的,相對於 JAVA 編寫的 JMeter 有效能上的差距,K6 可以只用較少的資源就能達到指定數量的負載。
- 支援閾值。
- Javascript 的指令碼可以更好的促進協作和版本管理。
- 資源利用率遠遠強於 JMeter。
- 豐富的視覺化方案。
- K6 vs JMeter 詳細報告
安裝K6
Debian/Ubuntu可以執行如下命令
sudo apt-get update && sudo apt-get install ca-certificates gnupg2 -y sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69 echo "deb https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list sudo apt-get update sudo apt-get install k6
Docker
docker pull loadimpact/k6
HTTP請求
新建一個 test.js 檔案
Get 請求 get( url, [params] )
import http from 'k6/http'; export let options = { vus: 100, // 指定要同時執行的虛擬使用者數量 duration: '10s', // 指定測試執行的總持續時間 }; // default 預設函式 export default function () { // 標頭 let params = { headers: { 'Content-Type': 'application/json' } }; var res=http.get("https://test.k6.io",params) }
Post 請求 Post( url, [body],[params])
import http from 'k6/http';
export let options = {
vus: 100,
duration: '10s',
};
// default 預設函式
export default function () {
// json 字串
let json = { content: 'linhui', image: 'images' };
// 標頭
let params = { headers: { 'Content-Type': 'application/json' } };
var res = http.post("https://host/api/feedback", JSON.stringify(json), params)
console.log(res.status);
}
del 請求 del( url,[body],[params])
import http from 'k6/http';
export let options = {
vus: 1,
duration: '10s',
};
// default 預設函式
export default function () {
let json = {id:1};
let params = { headers: { 'Content-Type': 'application/json' } };
http.del('https://host/delete', json, params);
}
batch 批處理,可以用來做頁面併發,批處理並不能保證執行順序,batch(method,url,[body],[params])
import http from 'k6/http';
export let options = {
vus: 1,
duration: '10s',
};
export default function () {
let get = {
method: 'GET',
url: 'https://host/get',
};
let get1 = {
method: 'GET',
url: 'https://host/get',
};
let post = {
method: 'POST',
url: 'https://host/post',
body: {
hello: 'world!',
},
params: {
headers: { 'Content-Type': 'application/json' },
},
};
let res = http.batch([req1, req2, req3]);
}
使用 request 傳送求 request( method, url, [body], [params])
import http from 'k6/http';
export let options = {
vus: 1,
duration: '10s',
};
export default function () {
let json = { content: 'linhui', image: 'images' };
let params = { headers: { 'Content-Type': 'application/json' } };
let res = http.request('POST', 'http://host/post', JSON.stringify(json), params);
let res1 = http.request('GET', 'http://host/get', null, params);
}
執行指令碼,進入指令碼根目錄
k6 run test.js
# 使用 docker
docker run -i loadimpact/k6 run - <test.js
常見指標說明
指標型別
名稱 | 描述 |
Counter | 計數器,對值進行累加 |
Gauge | 最小值、最大值和最後一個值。 |
Rate | 百分比 |
Trend | 最小值、最大值、平均值和百分位數的統計資料指標 |
K6 始終都會收集的指標
名稱 | 型別 | 描述 |
vue | Gauge | 當前活動的虛擬使用者數 |
vue_max | Gauge | 虛擬使用者的最大數量 |
iterations | Counter | 指令碼中的函式被執行的次數 |
data_received | Counter | 接收到的資料量大小 |
data_sent | Counter | 傳送的資料量大小 |
iteration_duration | Trend | 完成預設/主函式的一次完整迭代所花費的時間。 |
checks | Rate | checks 項的成功率 |
HTTP 特有的指標
名稱 | 型別 | 描述 |
http_reqs | Counter | 總請求數量 |
http_req_blocked | Trend | 在發起請求之前被阻塞的時間 |
http_req_connecting | Trend | 建立到遠端主機的TCP連線所花費的時間。 |
http_req_tls_handshaking | Trend | 與遠端主機握手建立TLS會話所花費的時間 |
http_req_sending | Trend | 將資料傳送到遠端主機所花費的時間 |
http_req_waiting | Trend | 等待遠端主機響應所花費的時間 |
http_req_receiving | Trend | 從遠端主機接收響應資料所花費的時間 |
http_req_duration | Trend | 請求的總時間。它等於http_req_sending + http_req_waiting + http_req_receiving(即,遠端伺服器處理請求和響應花了多長時間,而沒有初始DNS查詢/連線時間) |
http_req_failed | Rate | 失敗請求率 |
每一個 http 都會返回一個 HTTP Response 物件,下面是常用的一些屬性。
屬性 | 型別 |
Response.body | HTTP 響應正文 |
Response.cookies | 響應 cookies ,屬性是 cookie 名稱,值是 cookie 物件陣列 |
Response.error | 傳送請求失敗後的錯誤資訊。 |
Response.error_code | 錯誤碼 |
Response.headers | 標頭,鍵值對 |
Response.status | 從伺服器收到的 HTTP 響應程式碼 |
Response.timings | 耗時(以毫秒為單位) |
Response.timings.blocked | = http_req_blocked |
Response.timings.connecting | = http_req_connecting |
Response.timings.tls_handshaking | = http_req_tls_handshaking |
Response.timings.sending | = http_req_sending |
Response.timings.waiting | = http_req_waiting |
Response.timings.receiving | = http_req_receiving |
Response.timings.duration | = http_req_duration |
自定義自己的指標
import http from 'k6/http';
import { Trend } from 'k6/metrics';
export let options = {
vus: 100,
duration: '10s',
};
// 新建一個型別為 Trend 名為 sending_time 的自定製指標
let sendingTime = new Trend('sending_time');
export default function () {
let res = http.get('http://www.baidu.com');
sendingTime.add(res.timings.sending);
}
常用 Option 選項
Vus:指定要同時執行的虛擬使用者數量,必須是一個整數,和 duration 搭配使用。預設值:1
export let options = {
vus: 10,
duration: '10s',
};
k6 run -u 10 test.js
k6 run --vus 10 test.js
Duration:一個字串,指定測試執行的總持續時間,與 vus 選項一起使用。預設值:null
export let options = {
vus: 10,
duration: '10s',
};
k6 run -u 10 --d 20s test.js
k6 run --vus 10 --duration 20s test.js
User Agent:傳送 HTTP 請求時指定 User-Agent 標頭。預設值:k6/0.27.0 (https://k6.io/) 取決於你 k6 的版本
export let options = {
userAgent: 'Mozilla/5.0',
};
k6 run --user-agent 'Mozilla/5.0' test.js
TLS Version:表示允許在與伺服器互動中使用的唯一 SSL/TLS 版本的字串,或者一個指定允許使用的“最小”和“最大”版本的物件。 預設值:null (允許所有版本)
export let options = {
tlsVersion: 'tls1.2',
};
export let options = {
tlsVersion: {
min: 'ssl3.0',
max: 'tls1.2',
},
};
TLS Cipher Suites:允許在與伺服器的 SSL/TLS 互動中使用的密碼套件列表。由於底層 go 實現的限制,不支援更改 TLS 1.3 的密碼,並且不會執行任何操作。 預設值:null(允許所有)
export let options = {
tlsCipherSuites: [
'TLS_RSA_WITH_RC4_128_SHA',
'TLS_RSA_WITH_AES_128_GCM_SHA256',
],
};
TLS Auth: tls 身份驗證。預設值:null
export let options = {
tlsAuth: [
{
domains: ['example.com'],
cert: open('mycert.pem'),
key: open('mycert-key.pem'),
},
],
};
Throw:一個布林值,true or false ,指定是否在失敗的 HTTP 請求上丟擲異常。 預設值:false
export let options = {
throw: true,
};
k6 run --throw test.js
k6 run -w test.js
Thresholds:一組閾值規範,用於根據指標資料配置在何種條件下測試成功與否,測試通過或失敗。預設值:null
export let options = {
thresholds: {
http_req_duration: ['avg<100', 'p(95)<200'],
'http_req_connecting{cdnAsset:true}': ['p(95)<100'],
},
};
Tags:指定應在所有指標中設定為測試範圍的標籤。如果在請求、檢查或自定義指標上指定了同名標籤,它將優先於測試範圍的標籤。 預設值:null
export let options = {
tags: {
name: 'value',
},
};
k6 run --tag NAME=VALUE test.js
RPS:每秒發出的最大請求數。 預設值:0
export let options = {
rps: 500,
};
k6 run --rps 500 test.js
Paused:是否可以暫停和和恢復的方式執行指令碼,暫停啟動後需要使用另外的視窗執行k6 resume
恢復使用。在恢復視窗可以實時的檢視指令碼的執行情況。 啟動後不支援暫停, 預設值:false
export let options = {
paused: true,
};
k6 run --paused test.js
k6 run --p test.js
No VU Connection Reuse:布林值,是否複用 TCP 連結。預設值:false
export let options = {
noVUConnectionReuse: true,
};
run --no-vu-connection-reuse test.js
No Usage Report:布林值,是否給 K6 傳送使用報告,true 值不會發使用報告。 預設值:false
k6 run --no-usage-report test.js
No Thresholds:布林值,是否禁用閾值。預設是:fasle
k6 run --no-thresholds test.js
No Summary:是否禁用測試結束生成的概要。預設值:false
k6 run --no-summary test.js
No Cookies Reset:是否重置 Cookies,fasle 每次迭代都會重置 Cookie ,true 會在迭代中持久化 Cookie 。預設值:false
export let options = {
noCookiesReset: true,
};
No Connection Reuse:是否禁用保持活動連線,預設值:false
export let options = {
noConnectionReuse: true,
};
k6 run --no-connection-reuse test.js
Minimum Iteration Duration:指定預設函式每次執行的最短持續時間,任何小於此值的迭代都將剩餘時間內休眠,直到達到指定的最小持續時間。預設值:0
export let options = {
minIterationDuration: '10s',
};
k6 run --min-iteration-duration '1s' test.js
Max Redirects:最大重定向,預設值:10
export let options = {
maxRedirects: 10,
};
k6 run -max-redirects 10 test.js
Batch: batch 同時呼叫的最大連線總數,如果同時有 20 api 請求需要發出 ,batch 值是 15,那麼將會立即發出 15 個請求,其餘的請求會進行一個排隊。預設值:20
export let options = {
batch: 15,
};
k6 run --batch 10 test.js
Batch per host:batch 對同一個主機名同時進行的最大並行連線數。預設值:6
export let options = {
batchPerHost: 5,
};
k6 run --batch-per-host 10 test.js
Blacklist IPs:黑名單。預設值:null
export let options = {
blacklistIPs: ['10.0.0.0/8'],
};
k6 run --blacklist-ip= ['10.0.0.0/8'] test.js
Block Hostnames:基於模式匹配字串來阻止主機,如 *.example.com , 預設值:null
export let options = {
blockHostnames: ["test.k6.io" , "*.example.com"],
};
k6 run --block-hostnames="test.k6.io,*.example.com" test.js
Discard Response Bodies:是否應丟棄響應正文,將 responseType 的預設值修改成 none,建議設定成 true,可以減少記憶體暫用和GC使用,有效的較少測試機的負載。預設值:false
export let options = {
discardResponseBodies: true,
};
HTTP Debug:記錄所有HTTP請求和響應。預設情況下排除正文,包括正文使用 --http debug=full 預設值:false
export let options = {
httpDebug: 'full',
};
k6 run --http-debug test.js
Checks 檢查
Checks 類似斷言,不同在於 Checks 不會停止當前的指令碼。指標都可以作為檢查的專案。
import http from 'k6/http';
import { sleep } from 'k6';
import { check } from 'k6';
export let options = {
vus: 100,
duration: '10s',
};
export default function () {
let res = http.get('http://test.k6.io/');
check(res, {
'狀態碼為200': (r) => r.status === 200,
'響應時間小於200ms': (r) => r.timings.duration < 200,
'等待遠端主機響應時間小於200ms': (r) => r.timings.waiting < 200,
});
}
Thresholds 閾值
閾值是用來指定被測系統的效能預期的通過/失敗標準。閾值用來分析效能指標並確定最終測試結果。內建的指標都可以作為閾值。
K6 中包含的四種度量型別每一種都提供了自己的一組可用於閾值表示式的聚合方法。
- Counter: count and rate
- Gauge:value
- Rate:rate
- Trend:p(N)
import http from 'k6/http';
import { Trend, Rate, Counter, Gauge } from 'k6/metrics';
export let GaugeContentSize = new Gauge('ContentSize');
export let TrendRTT = new Trend('RTT');
export let options = {
vus: 10,
duration: '10s',
thresholds: {
// 發出的請求數量需要大於1000
http_reqs:['count>1000'],
// 錯誤率應該效率 0.01%
http_req_failed: ['rate<0.01'],
// 返回的內容必須小於 4000 位元組。
ContentSize: ['value<4000'],
// p(N) 其中 N 是一個介於 0.0 和 100.0 之間的數字,表示要檢視的百分位值,例如p(99.99) 表示第 99.99 個百分位數。這些值的單位是毫秒。
// 90% 的請求必須在 400 毫秒內完成,95% 必須在 800 毫秒內完成,99.9% 必須在 2 秒內完成
http_req_duration: ['p(90) < 400', 'p(95) < 800', 'p(99.9) < 2000'],
// 99% 響應時間必須低於 300 毫秒,70% 響應時間必須低於 250 毫秒,
// 平均響應時間必須低於 200 毫秒,中位響應時間必須低於 150 毫秒,最小響應時間必須低於 100 毫秒
RTT: ['p(99)<300', 'p(70)<250', 'avg<200', 'med<150', 'min<100'],
},
};
export default function () {
let res = http.get('http://www.baidu.com');
TrendRTT.add(res.timings.duration);
GaugeContentSize.add(res.body.length);
}
閾值標籤,測試中可以給指定的 url 或者特定標籤上使用閾值。
import http from 'k6/http';
import { sleep } from 'k6';
import { Rate } from 'k6/metrics';
export let options = {
vus: 10,
duration: '10s',
thresholds: {
// type 為 baidu 使用
'http_req_duration{type:baidu}': ['p(95)<500'],
// type 為 bing 使用
'http_req_duration{type:bing}': ['p(95)<200'],
},
};
export default function () {
let res1 = http.get('https://www.baidu.com', {
tags: { type: 'baidu' },
});
let res2 = http.get('https://cn.bing.com/', {
tags: { type: 'bing' },
});
let res3 = http.batch([
[
'GET',
'https://www.baidu,com',
null,
{ tags: { type: 'baidu' } },
],
[
'GET',
'https://cn.bing.com/',
null,
{ tags: { type: 'bing' } },
],
]);
}
預設情況下沒有達標閾值標準是不會停止指令碼的,通過設定閾值的 abortOnFail: true
來終止。
import http from 'k6/http';
export let options = {
vus: 10,
duration: '10s',
thresholds: {
http_req_duration: [{threshold: 'p(99) < 10', abortOnFail: true}],
},
};
export default function () {
let res = http.get('http://www.baidu.com');
}
對通過的閾值前面會有一個✓,而失敗的則會有一個 ✗ 。只有滿足所有閾值的情況下測試才算通過。
日誌輸出
輸出到控制檯。
import http from 'k6/http';
export let options = {
vus: 10,
duration: '2s',
};
export default function () {
let res = http.get('http://www.baidu.com');
console.log('log')
console.info('info');
console.error('err');
console.debug('debug')
console.warn('warn')
}
輸出到檔案,輸出到檔案的同時控制檯不在輸出。
k6 run test.js --console-output=test.log
InfluxDB + Grafana 視覺化測試結果
Docker 啟動 InfluxDB
docker pull tutum/influxdb
# 8083是influxdb的web管理工具埠,8086是influxdb的HTTP API埠
docker run -d -p 8083:8083 -p8086:8086 --expose 8090 --expose 8099 --name influxsrv tutum/influxdb
Docker 啟動 Grafana,
docker pull grafana/grafana
docker run -d -p 3000:3000 grafana/grafana
新建一個 K6test 資料庫,訪問 "http://xxxxx:8083" InfluxDB web 管理頁面,新建一個 K6test 資料庫
配置 Grafana 資料來源
選擇 InfluxDB
填寫域名埠和資料庫,點選 sava&test 。出現 Data source is working 表示成功,如遇到問題檢視一下埠是否放行。
匯入儀表盤
通過 ID 匯入,輸入 2587 點選 load 資料來源選擇 InfluxDB 點選 Import
官方還有幾款儀表盤
將 K6 的測試指標匯入到 InfluxDB
k6 run --out influxdb=http://xxxxx:8086/K6test test.js
效果圖
總結
動手實踐了一下 K6 , 作為一款全面高效的效能測試工具,功能遠遠不止這些,需要在工作中不斷的去挖掘。