前言
负载测试,压力测试可以衡量服务是否是一个高可用,高性能的服务。负载测试能检验在不同的工作负荷下,服务的硬件消耗和响应,从而得到不同负载情况下的性能指标。压力测试能检验软硬件环境下服务所能承受的最大负荷并帮助找出系统瓶颈所在。
环境说明
- 腾讯云轻量服务器, 配置 
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可以执行如下命令
1 2 3 4 5
   | 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
1
   | docker pull loadimpact/k6
 
  | 
 
HTTP请求
新建一个 test.js 文件   
Get 请求 get( url, [params] )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
   | import http from 'k6/http'; export let options = {   vus: 100,    duration: '10s',  };
  export default function () {      let params = { headers: { 'Content-Type': 'application/json' } };     var res=http.get("https://test.k6.io",params) } ```   
  Post 请求 Post( url, [body],[params]) ``` js import http from 'k6/http'; export let options = {   vus: 100,   duration: '10s', };
  export default function () {      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])
1 2 3 4 5 6 7 8 9 10 11
   | import http from 'k6/http'; export let options = {   vus: 1,    duration: '10s',  };
  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])
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
   | 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])
1 2 3 4 5 6 7 8 9 10 11
   | 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); }
 
  | 
 
执行脚本,进入脚本根目录
1 2 3
   | 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 | 
    
自定义自己的指标
1 2 3 4 5 6 7 8 9 10 11 12 13
   | import http from 'k6/http'; import { Trend } from 'k6/metrics'; export let options = {   vus: 100,   duration: '10s', };
  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
1 2 3 4
   | export let options = {   vus: 10,   duration: '10s', };
 
  | 
 
1 2
   | k6 run -u 10 test.js k6 run --vus 10 test.js
 
  | 
 
Duration:一个字符串,指定测试运行的总持续时间,与 vus 选项一起使用。默认值:null
1 2 3 4
   | export let options = {   vus: 10,   duration: '10s', };
 
  | 
 
1 2
   | 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 的版本
1 2 3
   | export let options = {   userAgent: 'Mozilla/5.0', };
 
  | 
 
1
   | k6 run  --user-agent 'Mozilla/5.0'  test.js
 
  | 
 
TLS Version:表示允许在与服务器交互中使用的唯一 SSL/TLS 版本的字符串,或者一个指定允许使用的“最小”和“最大”版本的对象。 默认值:null (允许所有版本)
1 2 3 4 5 6 7 8 9 10
   | 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(允许所有)
1 2 3 4 5 6
   | export let options = {   tlsCipherSuites: [     'TLS_RSA_WITH_RC4_128_SHA',     'TLS_RSA_WITH_AES_128_GCM_SHA256',   ], };
 
  | 
 
TLS Auth: tls 身份验证。默认值:null
1 2 3 4 5 6 7 8 9
   | export let options = {   tlsAuth: [     {       domains: ['example.com'],       cert: open('mycert.pem'),       key: open('mycert-key.pem'),     },   ], };
 
  | 
 
Throw:一个布尔值,true or false ,指定是否在失败的 HTTP 请求上抛出异常。 默认值:false
1 2 3
   | export let options = {   throw: true, };
 
  | 
 
1 2
   | k6 run  --throw test.js k6 run  -w test.js
 
  | 
 
Thresholds:一组阈值规范,用于根据指标数据配置在何种条件下测试成功与否,测试通过或失败。默认值:null
1 2 3 4 5 6
   | export let options = {   thresholds: {     http_req_duration: ['avg<100', 'p(95)<200'],     'http_req_connecting{cdnAsset:true}': ['p(95)<100'],   }, };
 
  | 
 
Tags:指定应在所有指标中设置为测试范围的标签。如果在请求、检查或自定义指标上指定了同名标签,它将优先于测试范围的标签。 默认值:null
1 2 3 4 5
   | export let options = {   tags: {     name: 'value',   }, };
 
  | 
 
1
   | k6 run --tag NAME=VALUE test.js
 
  | 
 
RPS:每秒发出的最大请求数。 默认值:0
1 2 3
   | export let options = {   rps: 500, };
 
  | 
 
1
   | k6 run --rps 500  test.js
 
  | 
 
Paused:是否可以暂停和和恢复的方式运行脚本,暂停启动后需要使用另外的窗口执行k6 resume 恢复使用。在恢复窗口可以实时的查看脚本的运行情况。 启动后不支持暂停, 默认值:false
1 2 3
   | export let options = {   paused: true, };
 
  | 
 
1 2
   | k6 run --paused  test.js k6 run --p test.js
 
  | 
 
No VU Connection Reuse:布尔值,是否复用 TCP 链接。默认值:false
1 2 3
   | export let options = {   noVUConnectionReuse: true, };
 
  | 
 
1
   | run --no-vu-connection-reuse  test.js
 
  | 
 
No Usage Report:布尔值,是否给 K6 发送使用报告,true 值不会发使用报告。 默认值:false
1
   | k6 run --no-usage-report test.js
 
  | 
 
No Thresholds:布尔值,是否禁用阈值。默认是:fasle
1
   | k6 run --no-thresholds test.js
 
  | 
 
No Summary:是否禁用测试结束生成的概要。默认值:false
1
   | k6 run --no-summary test.js
 
  | 
 
No Cookies Reset:是否重置 Cookies,fasle 每次迭代都会重置 Cookie ,true 会在迭代中持久化 Cookie 。默认值:false
1 2 3
   | export let options = {   noCookiesReset: true, };
 
  | 
 
No Connection Reuse:是否禁用保持活动连接,默认值:false
1 2 3
   | export let options = {   noConnectionReuse: true, };
 
  | 
 
1
   | k6 run --no-connection-reuse test.js
 
  | 
 
Minimum Iteration Duration:指定默认函数每次执行的最短持续时间,任何小于此值的迭代都将剩余时间内休眠,直到达到指定的最小持续时间。默认值:0
1 2 3
   | export let options = {   minIterationDuration: '10s', };
 
  | 
 
1
   | k6 run --min-iteration-duration '1s' test.js
 
  | 
 
Max Redirects:最大重定向,默认值:10
1 2 3
   | export let options = {   maxRedirects: 10, };
 
  | 
 
1
   | k6 run -max-redirects 10 test.js
 
  | 
 
Batch: batch 同时调用的最大连接总数,如果同时有 20 api 请求需要发出 ,batch 值是 15,那么将会立即发出 15 个请求,其余的请求会进行一个排队。默认值:20
1 2 3 4
   | export let options = {   batch: 15, };
 
 
  | 
 
1
   | k6 run --batch 10 test.js
 
  | 
 
Batch per host:batch 对同一个主机名同时进行的最大并行连接数。默认值:6
1 2 3
   | export let options = {   batchPerHost: 5, };
 
  | 
 
1
   | k6 run --batch-per-host 10 test.js
 
  | 
 
Blacklist IPs:黑名单。默认值:null
1 2 3
   | export let options = {   blacklistIPs: ['10.0.0.0/8'], };
 
  | 
 
1
   | k6 run --blacklist-ip= ['10.0.0.0/8'] test.js
 
  | 
 
Block Hostnames:基于模式匹配字符串来阻止主机,如 *.example.com , 默认值:null
1 2 3
   | export let options = {   blockHostnames: ["test.k6.io" , "*.example.com"], };
 
  | 
 
1
   | k6 run --block-hostnames="test.k6.io,*.example.com" test.js
 
  | 
 
Discard Response Bodies:是否应丢弃响应正文,将 responseType 的默认值修改成 none,建议设置成 true,可以减少内存暂用和GC使用,有效的较少测试机的负载。默认值:false
1 2 3
   | export let options = {   discardResponseBodies: true, };
 
  | 
 
HTTP Debug:记录所有HTTP请求和响应。默认情况下排除正文,包括正文使用 –http debug=full 默认值:false
1 2 3
   | export let options = {   httpDebug: 'full', };
 
  | 
 
1
   | k6 run --http-debug test.js
 
  | 
 
Thresholds 阈值
阈值是用来指定被测系统的性能预期的通过/失败标准。阈值用来分析性能指标并确定最终测试结果。内置的指标都可以作为阈值。
K6 中包含的四种度量类型每一种都提供了自己的一组可用于阈值表达式的聚合方法。
- Counter: count and rate
 
- Gauge:value
 
- Rate:rate
 
- Trend:p(N) 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
   | 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: {          http_reqs:['count>1000'],          http_req_failed: ['rate<0.01'],           ContentSize: ['value<4000'],               http_req_duration: ['p(90) < 400', 'p(95) < 800', 'p(99.9) < 2000'],               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 或者特定标签上使用阈值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
   | import http from 'k6/http'; import { sleep } from 'k6'; import { Rate } from 'k6/metrics';
  export let options = {   vus: 10,   duration: '10s',   thresholds: {          'http_req_duration{type:baidu}': ['p(95)<500'],          '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 来终止。
1 2 3 4 5 6 7 8 9 10 11 12 13
   | 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'); }
 
 
  | 
 
对通过的阈值前面会有一个✓,而失败的则会有一个 ✗ 。只有满足所有阈值的情况下测试才算通过。
    
日志输出
输出到控制台。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | 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') }
 
  | 
 
输出到文件,输出到文件的同时控制台不在输出。
1
   | k6 run  test.js --console-output=test.log
 
  | 
 
InfluxDB + Grafana 可视化测试结果
Docker 启动 InfluxDB
1 2 3
   | 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,
1 2 3
   | 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
1
   | k6 run --out influxdb=http://xxxxx:8086/K6test test.js
 
  | 
 
效果图
 
   
总结
 动手实践了一下 K6 , 作为一款全面高效的性能测试工具,功能远远不止这些,需要在工作中不断的去挖掘。