前言
負載測試,壓力測試可以衡量服務是否是一個高可用,高性能的服務。負載測試能檢驗在不同的工作負荷下,服務的硬件消耗和響應,從而得到不同負載情況下的性能指標。壓力測試能檢驗軟硬件環境下服務所能承受的最大負荷並幫助找出系統瓶頸所在。
環境說明
- 騰訊雲輕量服務器, 配置
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 , 作為一款全面高效的性能測試工具,功能遠遠不止這些,需要在工作中不斷的去挖掘。