铁匠 铁匠
首页
golang
java
架构
常用算法
  • Java
  • nginx
  • 系统运维
  • 系统安全
  • mysql
  • redis
参考文档
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

铁匠

不予评判的专注当下
首页
golang
java
架构
常用算法
  • Java
  • nginx
  • 系统运维
  • 系统安全
  • mysql
  • redis
参考文档
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 性能测试神器 wrk 使用教程

    • 安装
      • 用法
        • 编写 lua 测试脚本
          • wrk 内置函数
          • 生命周期回调函数
          • 启动阶段
          • 执行阶段
          • 结束阶段
          • 官方示例
          • 实际案例-用户信息查询接口压测
      • 架构
      • 性能优化
      • 性能测试工具
      xugaoyi
      2020-07-13
      目录

      性能测试神器 wrk 使用教程

      wrk 是一个类似 ab(apache bench)、jmeter 的压力测试工具,底层基于 epoll 和 kqueue 实现,能充分利用 cpu 资源,降低测试工具本身性能开销对测试结果准确性的影响。支持使用 lua 脚本自定义测试逻辑,使用上非常简单,但功能足够强大。

      没有了解过 lua 的同学,可以看下 lua 简明教程 https://coolshell.cn/articles/10739.html (opens new window)

      # 安装

      • linux https://github.com/wg/wrk/wiki/Installing-wrk-on-Linux (opens new window)
      • macOS https://github.com/wg/wrk/wiki/Installing-wrk-on-Mac-OS-X (opens new window)
      • windows( Windows Subsystem for Linux ) https://github.com/wg/wrk/wiki/Installing-wrk-on-Windows-10 (opens new window)

      # 用法

      $ wrk -h
      wrk: invalid option -- h
      Usage: wrk <options> <url>
        Options:
          -c, --connections <N>  Connections to keep open
          -d, --duration    <T>  Duration of test
          -t, --threads     <N>  Number of threads to use
      
          -s, --script      <S>  Load Lua script file
          -H, --header      <H>  Add header to request
              --latency          Print latency statistics
              --timeout     <T>  Socket/request timeout
          -v, --version          Print version details
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      参数 说明
      -c 与服务器保持的 http 连接数
      -d 压测时间
      -t 使用线程数
      -s 自定义 lua 脚本路径
      -H 自定义 http header 请求头,例如:"User-Agent: benchmark-wrk"
      --latency 打印延迟统计数据
      --timeout http 超时时间
      --version 打印版本信息

      eg: wrk -t2 -c5 -d10s https://httpbin.org/get

      这种情况只适用于每次请求都相同的情况

      $ wrk -t2 -c5 -d10s https://httpbin.org/get
      Running 10s test @ https://httpbin.org/get
        2 threads and 5 connections
        Thread Stats   Avg      Stdev     Max   +/- Stdev
          Latency   251.97ms   50.38ms 510.96ms   94.52%
          Req/Sec     7.60      2.40    10.00     75.23%
        146 requests in 10.05s, 61.17KB read
      Requests/sec:     14.52
      Transfer/sec:      6.08KB
      
      1
      2
      3
      4
      5
      6
      7
      8
      9

      # 编写 lua 测试脚本

      官方文档:https://github.com/wg/wrk/blob/master/SCRIPTING (opens new window)

      编写 lua 脚本可以实现复杂的测试场景,例如:需要登录认证的接口,查询不用 id 的数据(相同 id 服务端可能有缓存,达不到真实压测效果)

      先看官方一个简单的自定义脚本 (opens new window)

      wrk.method = "POST"
      wrk.body   = "foo=bar&baz=quux"
      wrk.headers["Content-Type"] = "application/x-www-form-urlencoded"
      
      1
      2
      3
      $ wrk -d3s -c2 -s scripts/post.lua https://httpbin.org/get
      
      1

      wrk 是一个内置的全局 table 类型变量,不需要定义可以直接使用,修改 wrk 变量的值,会对所有请求都生效。

      wrk = {
          scheme  = "http",
          host    = "localhost",
          port    = nil,
          method  = "GET",
          path    = "/",
          headers = {},
          body    = nil,
          thread  = <userdata>
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10

      # wrk 内置函数

      • wrk.format

        function wrk.format(method, path, headers, body)

        wrk.format returns a HTTP request string containing the passed parameters merged with values from the wrk table.

        返回一个 http 请求字符串,参数会覆盖 wrk 全局配置,可以通过 format 可以构造出不同的 request

      • wrk.lookup

        function wrk.lookup(host, service)

        wrk.lookup returns a table containing all known addresses for the host and service pair. This corresponds to the POSIX getaddrinfo() function.

        返回所有可用服务器的地址信息

      • wrk.connect

        function wrk.connect(addr)

        wrk.connect returns true if the address can be connected to, otherwise it returns false. The address must be one returned from wrk.lookup().

        测试指定的服务器地址是否能正常连接

        参考:

        local addrs = nil
        
        function setup(thread)
          if not addrs then
              addrs = wrk.lookup(wrk.host, wrk.port or "http")
              for i = #addrs, 1, -1 do
                if not wrk.connect(addrs[i]) then
                    table.remove(addrs, i)
                end
              end
          end
        
          thread.addr = addrs[math.random(#addrs)]
        end
        
        function init(args)
          local msg = "thread addr: %s"
          print(msg:format(wrk.thread.addr))
        end
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19

      # 生命周期回调函数

      wrk 包括下面几个生命周期,在脚本中重新定义这些全局函数,可以修改 wrk 默认行为,实现个性化测试需求。

      The following globals are optional, and if defined must be functions:

      global setup    -- called during thread setup
      global init     -- called when the thread is starting,
      global delay    -- called to get the request delay,
      global request  -- called to generate the HTTP request,
      global response -- called with HTTP response data,
      global done     -- called with results of run
      
      1
      2
      3
      4
      5
      6

      # 启动阶段

      • setup 每个线程初始化时执行一次

        function setup(thread)

        setup 方法会传入一个 thread 对象,可以修改或设置 thread 相关参数,也可以终止线程执行,这里一般做一些初始化的工作,例如读取配置文件,加载到内存(不要每次请求的时候读取一遍,这样对测试准确性影响很大)

        thread.addr             - get or set the thread's server address,获取或设置服务器地址信息
        thread:get(name)        - get the value of a global in the thread's env,获取当前线程参数
        thread:set(name, value) - set the value of a global in the thread's env,设置线程当前参数
        thread:stop()           - stop the thread,终止线程
        
        1
        2
        3
        4

      # 执行阶段

      • init 每个线程开始启动时执行一次

        function init(args)

        args 是通过命令行传入的参数,通过 -- 指定

        例如:wrk -d3s -c2 -s wrk.lua https://httpbin.org/get -- test 100

        function init(args)
          for i, v in ipairs(args) do
              print(i,v)
          end
        end
        -- 输出
        -- 1	test
        -- 2	100
        
        1
        2
        3
        4
        5
        6
        7
        8
      • delay 每次发送请求时,间隔时间(ms),每次请求执行一次

        function delay()

        返回值决定每次请求间隔

      • request 创建 request 时(发送 request 前)执行,每次请求执行一次

        function request()

        一般在这里会配合 wrk.format 方法,动态创建请求,这里不要执行耗时的代码,否则会影响测试结果准确性

      • response http 响应时执行,每次请求执行一次

        function response(status, headers, body)

        http 响应处理逻辑,参数对应 http 响应的 status, headers, body。

        注意

        解析 header 和 body 的开销比较大,如果脚本没有定义 response 方法,wrk 将不会解析 header 和 body,这样测试结果会更加准确(解析响应数据是客户端负责的,不能算到服务器处理时间里面)

      # 结束阶段

      • done 返回结果时执行,整个测试过程只执行一次,可以生成自定义测试报告,如果没有特别需求,一般不重写这个方法

        function done(summary, latency, requests)

        参数含义

        latency.min              -- minimum value seen
        latency.max              -- maximum value seen
        latency.mean             -- average value seen
        latency.stdev            -- standard deviation
        latency:percentile(99.0) -- 99th percentile value
        latency(i)               -- raw value and count
        
        summary = {
          duration = N,  -- run duration in microseconds
          requests = N,  -- total completed requests
          bytes    = N,  -- total bytes received
          errors   = {
            connect = N, -- total socket connection errors
            read    = N, -- total socket read errors
            write   = N, -- total socket write errors
            status  = N, -- total HTTP status codes > 399
            timeout = N  -- total request timeouts
          }
        }
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19

      # 官方示例

      https://github.com/wg/wrk/blob/master/scripts/setup.lua (opens new window)

      local counter = 1
      local threads = {}
      
      function setup(thread)
         thread:set("id", counter)
         table.insert(threads, thread)
         counter = counter + 1
      end
      
      function init(args)
         requests  = 0
         responses = 0
      
         local msg = "thread %d created"
         print(msg:format(id))
      end
      
      function request()
         requests = requests + 1
         return wrk.request()
      end
      
      function response(status, headers, body)
         responses = responses + 1
      end
      
      function done(summary, latency, requests)
         for index, thread in ipairs(threads) do
            local id        = thread:get("id")
            local requests  = thread:get("requests")
            local responses = thread:get("responses")
            local msg = "thread %d made %d requests and got %d responses"
            print(msg:format(id, requests, responses))
         end
      end
      
      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

      更多用法查看官方示例:https://github.com/wg/wrk/tree/master/scripts (opens new window)

      # 实际案例-用户信息查询接口压测

      某系统中用户信息查询接口,查询后会将数据放到 redis 缓存,如果压测参数不变,大部分请求都会从缓存获取,无法模拟出实际业务场景。所以参数需要传不同的userId,以下脚本从uid.txt读取userId参数,每次请求传不同参数。

      # uid.txt
      714948
      714951
      714952
      714955
      714961
      714971
      714974
      714975
      714977
      714979
      714981
      714982
      714984
      714989
      714990
      714991
      714992
      714994
      715004
      715005
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      -- user.lua
      -- user api
      -- wrk -t2 -c16 -d30 -s user.lua http://${host}:${port}
      
      path = './uid.txt'
      wrk.headers["User-Agent"] = "wrk_client"
      
      function setup(thread)
          local ids = {}
          local f = io.open(path, "r")
          local id = f:read()
          while id do
              table.insert(ids, id)
              id = f:read()
          end
          thread:set("ids", ids)
          f:close()
      end
      
      function init(args)
          index = 1
          print("ids: " .. #ids)
      end
      
      function request()
          local id = ids[index]
          if index + 1 == #ids then
              index = 1
          else
              index = index + 1
          end
          local queryPath = "/findByIds?userIds=" .. id
          print(index .. " - " .. queryPath)
          return wrk.format("POST", queryPath)
      end
      
      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
      #wrk#压力测试
      Last Updated: 2024/04/23, 01:30:37
      最近更新
      01
      go-kit学习指南 - 多协议支持
      04-19
      02
      go-kit学习指南 - 中间件
      04-19
      03
      go-kit开发微服务 - 服务注册与发现
      04-19
      更多文章>
      Theme by Vdoing | Copyright © 2016-2024 铁匠 | 粤ICP备15021633号
      • 跟随系统
      • 浅色模式
      • 深色模式
      • 阅读模式