如何在Ubuntu14.04上使用wrk对HTTP延迟进行基准测试

来自菜鸟教程
跳转至:导航、​搜索

介绍

本文重点介绍一个名为 wrk 的开源 HTTP 基准测试工具,它可以测量 HTTP 服务在 高负载 下的 延迟

Latency 是指从发出请求(通过 wrk)到收到响应(来自服务)之间的时间间隔。 这可用于模拟访问者在使用浏览器或任何其他发送 HTTP 请求的方法访问您的网站时会遇到的延迟。

wrk 可用于测试任何依赖 HTTP 的网站或应用程序,例如:

  • Rails 和其他 Ruby 应用程序
  • Express 和其他 JavaScript 应用程序
  • PHP 应用程序
  • 在 Web 服务器上运行的静态网站
  • Nginx 等负载均衡器背后的站点和应用程序
  • 您的缓存层

测试 无法真实用户 进行比较,但它们应该为您提供良好的 估计 预期延迟,以便您可以更好地规划您的基础设施。 测试还可以让您深入了解性能瓶颈。

wrk 是开源的,可以在 GitHub 上找到。

由于其多线程特性,它非常稳定并且允许模拟高负载。 wrk 最大的特点是它能够集成 Lua 脚本,这增加了许多可能性,例如:

  • 使用 cookie 对请求进行基准测试
  • 自定义报告
  • 对多个 URL 进行基准测试 - 流行的 ab,Apache HTTP 服务器基准测试工具,不能

先决条件

我们将在本教程中使用的基础架构如下图所示:

如您所见,我们将在一个非常简单的场景中使用 wrk。 我们将在 Node.js 应用程序上对 Express 进行基准测试。

我们将启动 两个 Droplet:一个用于 wrk,它生成负载,另一个用于应用程序。 如果他们在同一个盒子上,他们会争夺资源,我们的结果将不可靠。

基准测试的机器应该足够强大以处理压力系统,但在我们的例子中,应用程序非常简单,我们将使用相同大小的机器。

  • 同一区域 中启动两个 Droplet,因为它们将通过私有 IP 进行通信
  • 调用一个 Droplet wrk1 和另一个 app1,按照本教程进行操作
  • 选择 2 GB 内存
  • 选择Ubuntu 14.04
  • 可用设置 部分中选择 私有网络
  • 在每台服务器上创建一个 sudo 用户

较小的液滴也可以,但您应该期望测试结果有更多的延迟。 在真实的测试环境中,您的应用服务器应该与您打算在生产中使用的大小相同。

如果您在设置 Droplets 时需要帮助,请参阅 这篇文章

第 1 步 — 两台服务器:安装 Docker

为了让我们的生活更轻松,我们将使用 Docker,这样我们就可以在容器中启动 wrk 和我们的应用程序。 这让我们可以跳过设置 Node.js 环境、npm 模块和 deb 包; 我们只需要下载并运行适当的容器。 节省的时间将用于学习 wrk。

如果你不熟悉 Docker,你可以在这里the-docker-ecosystem-an-introduction-to-common-components阅读它的介绍。

注意:本节中的命令应该在两个Droplet上执行。

要安装 Docker,请登录到您的服务器并执行以下命令。 首先,更新包列表:

sudo apt-get update

安装 Wget 和 cURL:

sudo apt-get install -y wget curl

下载并安装 Docker:

sudo wget -qO- https://get.docker.com/ | sh

将您的用户添加到 docker 组,这样您就可以在不使用 sudo 的情况下执行 Docker 命令:

sudo gpasswd -a ${USER} docker
sudo service docker restart
newgrp docker

如果您使用不同的 Linux 发行版,Docker 有 安装文档 可能会涵盖您的情况。


要验证 docker 是否已正确安装,请使用以下命令:

docker --version

您应该得到以下或类似的输出:

OutputDocker version 1.7.1, build 786b29d

第 2 步 — 准备测试申请

app1 Droplet 上执行这些命令。

出于测试目的,作者在公共 Docker 注册表中发布了 Docker 镜像。 它包含一个用 Node.js 编写的 HTTP 调试应用程序。 它不是性能野兽(我们今天不会打破任何记录),但对于测试和调试来说已经足够了。 你可以在这里查看源代码

当然,在现实生活中,您会想要测试自己的应用程序。

在我们启动应用程序之前,让我们将 Droplet 的私有 IP 地址保存到一个名为 APP1_PRIVATE_IP 的变量中:

export APP1_PRIVATE_IP=$(sudo ifconfig eth1 | egrep -o "inet addr:[^ ]*" | awk -F ":" '{print $2}')

您可以通过以下方式查看私有 IP:

echo $APP1_PRIVATE_IP

输出:

Output10.135.232.163

您的私有 IP 地址会有所不同,因此请记下它。

您也可以从 digitalocean.com 控制面板获取私有 IP。 只需选择您的 Droplet,然后转到 设置 部分,如下图所示:

现在只需执行以下命令即可启动应用程序:

docker run -d -p $APP1_PRIVATE_IP:3000:3000 --name=http-debugging-application czerasz/http-debugger

上面的命令会先下载所需的 Docker 镜像,然后运行一个 Docker 容器。 容器以 分离模式 启动,这意味着它将在后台运行。 选项 -p $APP1_PRIVATE_IP:3000:3000 将代理所有进出端口 3000 上的本地容器的流量,以及端口 3000 上进出主机私有 IP 的所有流量。

下图描述了这种情况:

现在用 curl 测试看看应用程序是否正在运行:

curl -i -XPOST http://$APP1_PRIVATE_IP:3000/test -d 'test=true'

预期输出:

OutputHTTP/1.1 200 OK
X-Powered-By: Express
X-Debug: true
Content-Type: text/html; charset=utf-8
Content-Length: 2
ETag: W/"2-79dcdd47"
Date: Wed, 13 May 2015 16:25:37 GMT
Connection: keep-alive

ok

该应用程序非常简单,只返回一个 ok 消息。 所以每次 wrk 请求这个应用程序时,它都会返回一个小的 ok 消息。

最重要的部分是我们可以通过分析应用程序日志来查看 wrk 向我们的应用程序发出了哪些请求。

使用以下命令查看应用程序日志:

docker logs -f --tail=20 http-debugging-application

您的示例输出应如下所示:

Output[2015-05-13 16:25:37] Request 1

POST/1.1 /test on :::3000

Headers:
 - user-agent: curl/7.38.0
 - host: 0.0.0.0:32769
 - accept: */*
 - content-length: 9
 - content-type: application/x-www-form-urlencoded

No cookies

Body:
test=true

如果您愿意,您可以在运行基准测试时让它继续运行。 用 CTRL-C 退出尾部。

第 3 步 — 安装 wrk

登录wrk1服务器,准备安装wrk。

因为我们有 Docker,所以这很容易。 只需使用以下命令从 Docker 注册表中心下载 williamyeh/wrk 映像:

docker pull williamyeh/wrk

上面的命令会下载一个包含 wrk 的 Docker 镜像。 我们不需要构建 wrk,也不需要安装任何额外的包。 要运行 wrk(在容器内),我们只需要基于此映像启动一个容器,我们很快就会这样做。

下载应该只需要几秒钟,因为图像非常小 - 小于 3 MB。 如果你想在你最喜欢的 Linux 发行版上直接安装 wrk,请访问 this wiki page 并按照说明进行操作。

我们还将在此服务器上设置 APP1_PRIVATE_IP 变量。 我们需要来自 app1 Droplet 的私有 IP 地址。

导出变量:

export APP1_PRIVATE_IP=10.135.232.163

请记住将 10.135.232.163 IP 地址更改为 app1 Droplet 的私有 IP。 这个变量只会保存在当前会话中,所以下次登录使用wrk时记得重新设置。

第 4 步 — 运行 wrk 基准测试

在本节中,我们将最终看到 wrk 的实际应用。

本节中的所有命令都应在 wrk1 Droplet 上执行。

让我们看看 wrk 为我们提供的选项。 仅使用 --version 标志运行 wrk 容器将打印出其用法的简要摘要:

docker run --rm williamyeh/wrk --version

输出:

Outputwrk 4.0.0 [epoll] Copyright (C) 2012 Will Glozer
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

  Numeric arguments may include a SI unit (1k, 1M, 1G)
  Time arguments may include a time unit (2s, 2m, 2h)

现在我们有了一个很好的概述,让我们编写命令来运行我们的测试。 请注意,这个命令还不会做任何事情,因为我们不是从容器内部运行它。

我们可以使用 wrk 运行的最简单的情况是:

wrk -t2 -c5 -d5s -H 'Host: example.com' --timeout 2s http://$APP1_PRIVATE_IP:3000/

意思是:

  • -t2:使用两个单独的线程
  • -c5:打开六个连接(第一个客户端为零)
  • -d5s:运行测试五秒
  • -H 'Host: example.com':传递一个 Host header
  • --timeout 2s:定义一个两秒超时
  • http://$APP1_PRIVATE_IP:3000/ 目标应用程序正在监听 $APP1_PRIVATE_IP:3000
  • 对我们应用程序的 / 路径进行基准测试

这也可以描述为六个用户重复请求我们的主页五秒钟。

下图显示了这种情况:

请记住,连接 不能真实用户 进行比较,因为真实用户在查看您的主页时也会下载 CSS、图像和 JavaScript 文件。


这是测试的实际命令:

让我们在我们的 wrk Docker 容器中运行所描述的场景:

docker run --rm williamyeh/wrk -t2 -c5 -d5s -H 'Host: example.com' --timeout 2s http://$APP1_PRIVATE_IP:3000/

等待几秒钟让测试运行,然后查看结果,我们将在下一步中对其进行分析。

第 5 步 — 评估输出

输出:

OutputRunning 5s test @ http://10.135.232.163:3000
  2 threads and 5 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     3.82ms    2.64ms  26.68ms   85.81%
    Req/Sec   550.90    202.40     0.98k    68.00%
  5494 requests in 5.01s, 1.05MB read
Requests/sec:   1096.54
Transfer/sec:    215.24KB
  • 当前配置摘要:

      Running 5s test @ http://10.135.232.163:3000
        2 threads and 5 connections

    在这里,我们可以看到我们的基准配置的简要总结。 基准测试耗时5秒,基准机器IP为10.135.232.163,测试使用了两个线程。

  • 延迟和请求/秒统计的正态分布参数:

      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency     3.82ms    2.64ms  26.68ms   85.81%
        Req/Sec   550.90    202.40     0.98k    68.00%

    这部分向我们展示了我们的基准测试的正态分布细节 - Gaussian 函数 将具有哪些参数。

    基准并不总是具有正态分布,这就是这些结果可能具有误导性的原因。 因此,请始终查看 Max+/- Stdev 值。 如果这些值很高,那么您可以预期您的分布可能有一个沉重的尾巴。

  • 请求数、传输数据和吞吐量的统计信息:

        5494 requests in 5.01s, 1.05MB read
      Requests/sec:   1096.54
      Transfer/sec:    215.24KB

    在这里我们看到,在 5.01 秒的时间内,wrk 可以发出 5494 请求并传输 1.05MB 的数据。 结合简单的数学运算 (total number of requrests/benchmark duration),我们得到每秒 1096.54 请求的结果。

通常,您设置的客户端越多,您每秒获得的请求数就越少。 延迟也会增加。 这是因为应用程序将承受较重的负载。

什么结果最好?

您的目标是使 Requests/sec 尽可能高,而 Latency 尽可能低。

理想情况下,延迟不应该太高,至少对于网页来说是这样。 资产页面加载时间的限制在 两秒 或更少时是最佳的。

现在您可能会问自己:延迟为 3.82ms550.90 Requests/sec 是否是一个好的结果? 不幸的是,没有简单的答案。 这取决于许多因素,例如:

  • 客户数量,正如我们之前讨论过的
  • 服务器资源 - 它是大实例还是小实例?
  • 为应用程序服务的机器数量
  • 您的服务类型 - 是提供静态文件的缓存还是提供动态响应的广告服务器?
  • 数据库类型、数据库集群大小、数据库连接类型
  • 请求和响应类型——它是一个小的 AJAX 请求还是一个胖 API 调用?
  • 和许多其他人

第 6 步 — 采取行动改善延迟

如果您对自己的服务表现不满意,您可以:

  • 调整您的服务 - 检查您的代码,看看可以更有效地完成哪些工作
  • 检查您的数据库,看看它是否是您的瓶颈
  • 垂直扩展 - 向您的机器添加资源
  • 水平扩展 - 添加另一个服务实例并将其添加到负载均衡器
  • 添加缓存层

有关应用程序改进的更详细讨论,请查看 5 改进生产 Web 应用程序服务器设置的方法

请记住在应用更改后对您的服务进行基准测试 - 只有这样您才能确定您的服务已经改进。

就是这样,你可能会想,如果没有这个 Lua 的东西。 . .

使用 Lua 脚本模拟高级 HTTP 请求

因为 wrk 有一个内置的 LuaJIT(Lua 的即时编译器),它可以使用 Lua 脚本进行扩展。 正如介绍中提到的,这为 wrk 添加了很多功能。

使用带有 wrk 的 Lua 脚本很简单。 只需将文件路径附加到 -s 标志。

因为我们在 Docker 内部使用 wrk,所以我们必须首先与容器共享这个文件。 这可以通过 Docker 的 -v 选项来实现。

wrk 的 Lua 脚本的一部分

在通用形式中,使用名为 test.lua 的脚本,整个命令可能如下所示:

docker run --rm -v `pwd`/scripts:/scripts williamyeh/wrk -c1 -t1 -d5s -s /scripts/test.lua http://$APP1_PRIVATE_IP:3000

我们在前面的步骤中解释了 wrk 命令及其选项。 这个命令并没有添加太多; 只是脚本的路径和一些额外的命令来告诉 Docker 如何在容器外找到它。

--rm 标志将在容器停止后自动删除。

但我们真的知道如何编写 Lua 脚本吗? 不要害怕; 您将轻松学习它。 我们将在这里介绍一个简单的示例,您可以自己运行自己的更高级的脚本。

首先说一下反映wrk内部逻辑的预定脚本结构。 下图说明了一个线程:

wrk 执行以下执行阶段:

  • 解析域的IP地址
  • 从线程 setup 开始
  • 执行压力测试阶段,称为运行阶段
  • 最后一步简称为 done

当使用多个线程时,您将有一个解决阶段和一个完成阶段,但有两个设置阶段和两个运行阶段:

此外,运行阶段可以分为三个步骤:initrequestresponse

根据提供的图表和 文档,我们可以在 Lua 脚本中使用以下方法:

  • setup(thread):在所有线程都已初始化但尚未启动时执行。 用于向线程传递数据

  • init(args):每个线程初始化时调用

    此函数接收脚本的额外命令行参数,这些参数必须与 -- 的 wrk 参数分开。

    例子:

      wrk -c3 -d1s -t2 -s /scripts/debug.lua http://$APP1_PRIVATE_IP:3000 -- debug true
  • request():需要为每个请求返回HTTP对象。 在这个函数中我们可以修改方法、标题、路径和正文

    使用 wrk.format 辅助函数来塑造请求对象。

    例子:

      return wrk.format(method, path, headers, body)
  • response(status, headers, body):响应返回时调用

  • done(summary, latency, requests):当所有请求完成并计算统计信息时执行

    在此函数中,可以使用以下属性:

    财产 描述
    summary.duration 以微秒为单位的运行持续时间
    summary.requests 已完成的请求总数
    summary.bytes 收到的总字节数
    summary.errors.connect 套接字连接错误总数
    summary.errors.read 套接字读取错误总数
    summary.errors.write 套接字写入错误总数
    summary.errors.status 总 HTTP 状态代码 > 399
    summary.errors.timeout 总请求超时
    latency.min 测试期间达到的最小延迟值
    latency.max 测试期间达到的最大延迟值
    latency.mean 测试期间达到的平均延迟值
    latency.stdev 延迟标准差
    latency:percentile(99.0) 第 99 个百分位值
    latency[i] 请求的原始延迟数据 i

每个线程都有它自己的 Lua 上下文和它自己的局部变量。

现在我们将通过一些实际示例,但您可以在 wrk 项目的 脚本目录 中找到更多有用的基准测试脚本。

示例:POST 请求

让我们从最简单的例子开始,我们模拟一个 POST 请求。

POST 请求通常用于向服务器发送数据。 这可用于基准测试:

  • HTML 表单处理程序:使用 HTML 表单的 action 属性中的地址:

      <form action="/login.php">
      ...
      </form>
  • POST API 端点:如果您有一个 RESTful API,请使用您创建文章的端点:

      POST /articles

首先在 wrk1 Droplet 上创建一个 scripts/post.lua 文件。

cd ~
mkdir scripts
nano scripts/post.lua

向其中添加以下内容:

post.lua

wrk.method = "POST"
wrk.body   = "login=sammy&password=test"
wrk.headers["Content-Type"] = "application/x-www-form-urlencoded"

这个脚本非常简单,我们甚至还没有使用任何提到的方法。 我们刚刚修改了全局 wrk 对象属性。

我们将请求方法更改为 POST,添加了一些登录参数,并将 Content-Type 标头指定为 MIME 类型的 HTML 表单使用。

在我们开始基准测试之前,这里有一张图表,可以帮助您直观地了解脚本、Docker 容器和应用服务器之间的关系:

现在是关键时刻 - 使用此命令对应用程序进行基准测试(在 wrk1 Droplet 上执行):

docker run --rm -v `pwd`/scripts:/scripts williamyeh/wrk -c1 -t1 -d5s -s /scripts/post.lua http://$APP1_PRIVATE_IP:3000

输出:

OutputRunning 5s test @ http://10.135.232.163:3000
  1 threads and 1 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.04ms  718.38us  12.28ms   90.99%
    Req/Sec     1.02k   271.31     1.52k    66.00%
  5058 requests in 5.00s, 0.97MB read
Requests/sec:   1011.50
Transfer/sec:    198.55KB

输出与我们之前看到的类似。

请注意,我们在这里仅使用一个连接进行基准测试。 这对应于只有一个用户想通过用户名和密码连续登录的情况。 这不请求任何 CSS、图像或 JavaScript 文件。

对于更现实的场景,您应该增加客户端和线程的数量,同时观察延迟参数,以查看应用程序验证用户凭据的速度。

示例:多个 URL 路径

另一个常见的需求是同时测试应用程序的多个路径。

让我们在 data 目录中创建一个名为 paths.txt 的文件,并添加我们要在基准测试期间使用的所有路径。

cd ~
mkdir data
nano data/paths.txt

在下面找到 data/paths.txt 的示例:

路径.txt

/feed.xml
/contact/
/about/
/blog/
/2015/04/21/nginx-maintenance-mode/
/2015/01/06/vagrant-workflows/
/2014/12/10/top-vagrant-plugins/

然后抓取 这个简单的脚本 并将其保存为 scripts/multiple-url-paths.lua

多个 url-paths.lua

-- Load URL paths from the file
function load_url_paths_from_file(file)
  lines = {}

  -- Check if the file exists
  -- Resource: http://stackoverflow.com/a/4991602/325852
  local f=io.open(file,"r")
  if f~=nil then
    io.close(f)
  else
    -- Return the empty array
    return lines
  end

  -- If the file exists loop through all its lines
  -- and add them into the lines array
  for line in io.lines(file) do
    if not (line == '') then
      lines[#lines + 1] = line
    end
  end

  return lines
end

-- Load URL paths from file
paths = load_url_paths_from_file("/data/paths.txt")

print("multiplepaths: Found " .. #paths .. " paths")

-- Initialize the paths array iterator
counter = 0

request = function()
  -- Get the next paths array element
  url_path = paths[counter]

  counter = counter + 1

  -- If the counter is longer than the paths array length then reset it
  if counter > #paths then
    counter = 0
  end

  -- Return the request object with the current URL path
  return wrk.format(nil, url_path)
end

虽然本教程并未尝试详细教授 Lua 脚本,但如果您阅读脚本中的注释,您可以很好地了解它的作用。

multiple-url-paths.lua 脚本打开 /data/paths.txt 文件,如果此文件包含路径,则将它们保存到内部 paths 数组中。 然后,对于每个请求,都会采用下一条路径。

要运行此基准测试,请使用以下命令(在 wrk1 Droplet 上执行)。 您会注意到我们添加了一些换行符以便于复制:

docker run --rm \
           -v `pwd`/scripts:/scripts \
           -v `pwd`/data:/data \
           williamyeh/wrk -c1 -t1 -d5s -s /scripts/multiple-url-paths.lua http://$APP1_PRIVATE_IP:3000

输出:

Outputmultiplepaths: Found 7 paths
multiplepaths: Found 7 paths
Running 5s test @ http://10.135.232.163:3000
  1 threads and 1 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     0.92ms  466.59us   4.85ms   86.25%
    Req/Sec     1.10k   204.08     1.45k    62.00%
  5458 requests in 5.00s, 1.05MB read
Requests/sec:   1091.11
Transfer/sec:    214.17KB

使用 JSON 和 YAML 的高级请求

现在您可能认为其他基准测试工具也可以进行这些类型的测试。 但是,wrk 还能够使用 JSON 或 YAML 格式处理高级 HTTP 请求。

例如,您可以加载详细描述每个请求的 JSON 或 YAML 文件。

作者在作者的技术博客上发布了一个带有JSON请求的高级示例。

您可以使用 wrk 和 Lua 对您能想到的任何类型的 HTTP 请求进行基准测试。

结论

阅读本文后,您应该能够使用 wrk 对您的应用程序进行基准测试。 作为旁注,您还可以看到 Docker 的美妙之处以及它如何极大地减少您的应用程序和测试环境的设置。

最后,您可以使用带有 wrk 的 Lua 脚本来处理高级 HTTP 请求。