13

API 性能测试工具 WRK

 3 years ago
source link: https://liqiang.io/post/961e61f4-928b-4923-ba4c-bf5a684efbf5?lang=ZH_CN
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

All Posts

API 性能测试工具 WRK

@POST· 2021-07-20 18:00 · 48 min read

wrk 介绍

在了解 wrk 之前,我对性能测试的了解一直局限在 ab(Apache Benchmark) 上,我印象中 ab 的功能比较简单,就是简单地压测给定的 API,然后看并发量是多少,这在一些全都是 get 的场景下倒是没什么问题,但是如果要涉及一些更复杂的测试就比较难搞了,所以我就了解了一下,然后看到别人安利了一下 wrk,所以就顺便记录一下关于 wrk 的东西。

wrk 的基本功能其实也很 ab 很类似,可以简单地做一些 GET 的大量请求,然后算 QPS(Request Per Seconds) 和 RT(Response Time) 之类的,但是 wrk 不能直接执行 POST 请求(ab 可以)。所以,这里就需要进一步了解到 wrk 还有一个比较重要的功能:它支持通过 lua 脚本来做扩展,通过 lua 脚本,你可以做到:

  • 执行一个自定义的 POST 请求
  • 先调用一个登陆 API 拿到认证信息后,再给下一个测试 API 使用;
  • 通过设置不同的 API 同时测试,更全面地测试并发能力;
  • 调用完一个 API 后,等待一段时间再测试(这个 API 可能比较特殊)
  • 自定义测试结果的展示效果

这些都是 lua 脚本的扩展功能,在这方便,会比 ab 会好用很多。有意思的一件事情是,如果你去找 wrk,你还会发现一个 wrk2 的东西,它其实是 wrk 的修改版本,作者自己加入了一个所谓的 constant throughput 的功能,其实就是不测试 QPS 了,而是保持一定的 QPS,然后看 server 端的响应速度如何,表现怎么样,所以,一般情况下,我是不需要 wrk2 的。

基本功能演示

光说不练假把式,所以这里我只能演示一个简单的 GET 请求,其实就是看下 wrk 是如何执行这两个简单的操作的,至于 POST 请求,我需要在下一章节 扩展功能 中介绍:

  • 一个 GET 请求

    1. [[email protected]]# wrk -t1 -c2 -d5s http://google.com
    2. Running 5s test @ http://google.com
    3. 1 threads and 2 connections
    4. Thread Stats Avg Stdev Max +/- Stdev
    5. Latency 17.33ms 17.71ms 150.59ms 94.35%
    6. Req/Sec 138.00 30.28 180.00 84.00%
    7. 692 requests in 5.03s, 356.81KB read
    8. Requests/sec: 137.50
    9. Transfer/sec: 70.90KB

    这里我很抱歉拿 google.com 来作为一个演示的例子,但是这确实是工作的,这里先解析一下输入的参数分别是什么意思,这很重要:

    • -t1: 表示开启一个线程来进行测试
    • -c2: 表示开启 2 个并发进行测试
    • -d5s: 表示执行测试 5s,从结果中可以看出,实际上执行时间是 5.03 秒,说明还是受控的
    • url: 这就是要进行测试的 url 了

扩展功能演示

lua 脚本执行逻辑

wrk 的 POST 请求不能直接通过命令行的参数执行,所以需要通过 lua 脚本的方式来运行,再看 lua 的编写和执行之前,先来了解一些 wrk 的 lua 的上下文概念:

  • 在 wrk 中,每个线程的 lua 上下文是独立的,不会互相干扰,这对于编写 lua 脚本有意义
  • 在 wrk 中,lua 脚本可以分为三部分,你可以只使用其中某一阶段的自定义脚本,也可以全都定义,这三个阶段分别是:
    • 启动阶段(setup):每个线程启动时,会且整个生命周期只会执行一遍
    • 运行阶段(start):执行测试的时候,lua 线程会调用里面的自定义函数
    • 结束阶段(stop):测试完毕之后,lua 线程会调用里面的函数,一般用于控制输出内容格式

lua 脚本框架

看完理论部分,解析来看下,每个阶段对应的 lua 框架是什么样的,也就是说,wrk 规定的 lua 脚本应该怎么写。

lua 线程启动之后,就会调用这个阶段的自定义函数,这个节点只有一个自定义函数:

  1. [[email protected]]# cat setup.lua
  2. local counter = 1
  3. function setup(thread)
  4. thread:set("id", counter)
  5. table.insert(threads, thread)
  6. counter = counter + 1
  7. end

可以看到,在启动阶段,你可以修改 setup 函数的内容作为自定义的启动内容,里面你可以修改这个线程相关的信息,例如修改线程的变量,修改线程的 server 地址之类的,一般都不用修改,如果你想修改的话,这里有一些参数的函数可以供你使用:

函数(变量)名称 作用 thread.addr 变量:用于获取 / 修改服务器的地址 thread:get(name) 函数:用于获取线程的全局变量 thread:set(name, value) 函数:用于设置线程的全局变量,上面的例子演示了 thread:stop() 函数:关闭线程,后面的两个阶段也不用执行啦

运行阶段是 wrk 的核心部分,在这一部分,你可以有多个函数可以定义,分别是:

函数名称 作用 function init(args) lua 线程进入运行阶段时,会执行一遍,后续就不执行了,你可以在这个阶段做一些请求的初始化之类的 function delay() lua 线程每次发起请求之前都会调用一次这个 function request() lua 线程会通过这个函数的响应作为请求进行发送 function response(status, headers, body) lua 线程会将每次请求的响应作为参数调用这个函数

这里给一个示例每个阶段的代码是怎么写的:

  1. [[email protected]]# cat running.lua
  2. function init(args)
  3. requests = 0
  4. responses = 0
  5. local msg = "thread %d created"
  6. print(msg:format(id))
  7. end
  8. function delay()
  9. return math.random(10, 50)
  10. end
  11. function request()
  12. requests = requests + 1
  13. return wrk.request()
  14. end
  15. function response(status, headers, body)
  16. responses = responses + 1
  17. end

这就是一个简单的示例了,在示例里面,这里每个请求都延迟了 10-50 毫秒,然后在请求之前会记录一下请求数,请求成功之后也会记录一下请求的响应数量。

在结束之后,lua 允许你定义你自己的处理结果,在这里,你可以自定义你的输出格式,如果你不喜欢默认的输出格式的话,这里也只有一个你需要自己定义的函数:

  1. [[email protected]]# cat stop.lua
  2. function done(summary, latency, requests)
  3. for index, thread in ipairs(threads) do
  4. local id = thread:get("id")
  5. local requests = thread:get("requests")
  6. local responses = thread:get("responses")
  7. local msg = "thread %d made %d requests and got %d responses"
  8. print(msg:format(id, requests, responses))
  9. end
  10. end
特别的例子

如果你只想修改一下 request 就好了,其他都不想自定义,那么你可以直接在 lua 里面写 request 里面的函数,不用 return,也不用写函数定义,下面个 POST 的示例就是这种情况。

POST 请求

如果按照我们上一小节介绍的 lua 框架来说,我们可以在 request () 里面定义请求的格式,将请求方式定义为 POST 就可以了,但是,最后我也提了一下,wrk 考虑到了这种场景的常见性,所以讲这个 feature 简化成你只需要写一段脚本就可以了,不用在定义 function 和 return 了,例如这样就可以了:

  1. [[email protected]]# cat post.lua
  2. wrk.method = "POST"
  3. wrk.body = "foo=bar&baz=quux"
  4. wrk.headers["Content-Type"] = "application/x-www-form-urlencoded"

这就是一个 POST 的示例了,然后通过 wrk 加载这个 lua 脚本运行:

  1. [[email protected]]# wrk -c3 -d1s -t2 -s ./scripts/post.lua http://localhost:5000/api/v1/login

lua 辅助函数

函数 解释 wrk.format(method, path, headers, body) 这个函数通常用于 request () 阶段返回 request 对象

done 的参数属性

done(summary, latency, requests) 里面记录了性能测试的很多数据,包含的字段如下:

对象 属性 解释 Property Description —- —- summary duration run duration in microseconds summary requests total completed requests summary bytes total bytes received summary errors.connect total socket connection errors summary errors.read total socket read errors summary errors.write total socket write errors summary errors.status total HTTP status codes > 399 summary errors.timeout total request timeouts latency min minimum latency value reached during test latency max maximum latency value reached during test latency mean average latency value reached during test latency stdev latency standard deviation latency percentile(99.0) 99th percentile value latency[i] raw latency data of request i ——

一个复杂的例子

  1. [[email protected]]# cat pipeline.lua
  2. init = function(args)
  3. local r = {}
  4. r[1] = wrk.format(nil, "/?foo")
  5. r[2] = wrk.format(nil, "/?bar")
  6. r[3] = wrk.format(nil, "/?baz")
  7. req = table.concat(r)
  8. end
  9. request = function()
  10. return req
  11. end
© 2012-2020 路人的技术 版权所有. Powered by LauZoo. Theme based on Nuo.

Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK