如何在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.82ms
的 550.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
当使用多个线程时,您将有一个解决阶段和一个完成阶段,但有两个设置阶段和两个运行阶段:
此外,运行阶段可以分为三个步骤:init、request和response。
根据提供的图表和 文档,我们可以在 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 请求。