2
头图

大家好,我是张晋涛。

本篇我将为你介绍一个工具 - 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 的使命。

img

图 1 ,k6 加入 Grafana Labs

多样的压测工具

一个称心应手的自动化负载压测工具会极大的提升程序开发人员的代码质量及效率。

下图中是一些比较常见的用于负载压测的工具,我们可以在 GitHub 上看到,目前,更新比较频繁、活跃的项目主要有:Gatling, Jmeter 和 k6 。

img

图 2 ,压测工具们

如何从中选择,简单的讲就是工具效率的比拼。主要从以下两个方面来考量:

  • 工具性能
  • 工具使用体验

下图对以上工具进行了一些简单的对比。

img

这里我主要对比下其中较为活跃的 3 个项目。

  • JMeter - 熟悉 Java 的小伙伴可能比较了解这个工具。由于存在时间久,JMeter 的功能是这之中最全面的,并且集成、附加组件做的较好。基于它构建的 SaaS 服务 Blazemeter,相信大家也都熟识。这也导致了一个极大的问题,使用的复杂性高及不够轻量级;
  • Gatling - Gatling 也有着 SaaS 产品 Gatling Frontline。就使用门槛来讲,JS 要比 Scala 要低很多;
  • k6 - k6 最初是由 SaaS 服务 Load Impact 的几名员工开发维护。使用门槛低(JS),参数化更简单,并且 “负载测试即代码” 的理念也让他的维护成本更低。未来可期。

img

图 3 ,3 种热门工具比一比

执行效果

img

或者这样:

img

安装 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 上看到所有的指标了

img

总结

本篇主要是在介绍一个现代化的用户体验相对较好的压测工具 k6 。我目前正在计划将其引入到我们项目的 CI 中,以便了解每次核心部分的变更对项目性能的影响。

后续推进顺利的话,会再分享 k6 如何应用到 CI 环境中,敬请期待。


欢迎订阅我的文章公众号【MoeLove】


张晋涛
1.7k 声望19.7k 粉丝