优化操作系统
注意
本页面重点介绍如何帮助用户在本地运行大型测试脚本。如果您刚开始使用 k6,并且尚未遇到
Too Many Open Files
错误,则可以跳过这些说明。
在本地运行大型测试脚本时,用户有时会遇到操作系统内部的限制,这些限制会阻止他们发出完成测试所需的请求数量。这种限制通常以 Too Many Open Files
错误的形式出现。这些限制如果保持不变,如果您选择在本地计算机上运行更大或更复杂的测试,可能会成为严重的瓶颈。
本文将向您展示如何检查系统施加的操作系统限制、如何进行调整以及如何为大型测试进行扩展。
在此需要强调的是,本文中涵盖的所有内容都需要谨慎对待。与对操作系统进行任何更改一样,我们不建议盲目地将系统设置更改为特定值。您应该记录测试方法,这些方法清晰地显示更改前后的关系。例如,在更改 MSL / TIME_WAIT 周期之前,确认您正在经历该问题(错误消息、netstat、ss 等),保守地更改设置,重新运行测试,并记录任何改进。通过这种方式,您可以评估优化的效果,发现任何负面副作用,并提出推荐值的范围。
开始之前
- 以下修改已针对 Linux 和 macOS Sierra 10.12 及更高版本进行过测试。如果您使用的是较旧的 macOS 版本,更改这些设置的过程可能会有所不同。
网络资源限制
Unix 操作系统的派生版本,例如 GNU/Linux、BSDs 和 macOS,可以限制一个进程可用的系统资源数量,以确保系统稳定性。这包括单个进程被允许管理的内存总量、CPU 时间或打开文件数量。
由于在 Unix 中万物皆文件,包括网络连接,因此大量使用网络的应用程序测试工具(例如 k6)可能会达到配置的允许打开文件限制,具体取决于特定测试中使用的网络连接数量。
如引言部分所述,这会导致在测试期间显示如下消息:
WARN[0127] Request Failed error="Get http://example.com/: dial tcp example.com: socket: too many open files"
此消息意味着已达到网络资源限制,这将阻止 k6 创建新的连接,从而改变测试结果。在某些情况下,这可能是期望的结果,例如用于衡量整体系统性能,但在大多数情况下,这会成为测试 HTTP 服务器和 Web 应用程序本身的瓶颈。
下面几节描述了增加此资源限制的方法,并允许 k6 在单个系统上运行数百或数千个并发 VU 的测试。
查看限制配置
Unix 系统有两种资源限制类型:
- 硬限制。 每个用户允许的绝对最大值,只能由 root 用户配置。
- 软限制。 这些可以由每个用户配置,但不能高于硬限制设置。
Linux
在 GNU/Linux 上,您可以使用 ulimit 命令查看配置的限制。
ulimit -Sa
将显示当前用户的所有软限制
$ ulimit -Sa
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 3736
max locked memory (kbytes, -l) 16384
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 3736
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
而 ulimit -Ha
将显示当前用户的所有硬限制
$ ulimit -Ha
core file size (blocks, -c) unlimited
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 3736
max locked memory (kbytes, -l) 16384
max memory size (kbytes, -m) unlimited
open files (-n) 1048576
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) unlimited
cpu time (seconds, -t) unlimited
max user processes (-u) 3736
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
请注意,打开文件数量的差异在于软限制的最大值为 1024,而硬限制的最大值为 1048576。
macOS
然而,在 macOS 中,您需要考虑几个不同的系统强制限制。第一个是 launchctl limit maxfiles
,它打印进程的限制,这些限制也被指定为软限制和硬限制。当超过软限制时,进程可能会收到信号(例如,如果超过 CPU 时间或文件大小),但它将被允许继续执行直到达到硬限制(或修改其资源限制)。kern.maxfiles
是整个系统上总文件描述符的限制 - 所有进程打开的所有文件总和加上内核出于自身目的打开的所有文件。
sysctl kern.maxfiles
sysctl kern.maxfilesperproc
sysctl kern.maxfilesperproc
因此,重申一下,运行上述命令将显示系统对打开文件和运行进程的限制。
更改限制配置
在更改配置之前,您应该首先考虑的是测试预期需要的网络连接数量。k6 结果摘要中的 http_reqs 指标可以提示这一点,但最大 VU 数 * 单个 VU 迭代中的 HTTP 请求数的基准计算将提供一个合理的近似值。请注意,k6 还处理文本文件和其他计入“打开文件”配额的资源,但网络连接是最大的消费者。
在 macOS 中禁用限制
在更改 macOS 中任何系统强制限制之前,我们需要禁用一个旨在阻止我们进行更改的安全功能。您需要禁用在 OS X El Capitan 中引入的“系统完整性保护”(System Integrity Protection),以防止某些系统拥有的文件和目录被没有适当权限的进程修改。
要禁用它,您需要重启 Mac 并在启动时按住 Command + R
。这将使其进入恢复模式。
在恢复模式下,您应该导航到位于屏幕顶部菜单栏中的 Utilities
,然后打开 Terminal
。打开终端后,输入以下命令:
csrutil disable
按下回车键并关闭 Terminal
后,您可以正常重启 Mac 并登录到您的账户。
更改软限制
Linux
假设我们要运行一个 1000 VU 的测试,每次迭代进行 4 个 HTTP 请求。在这种情况下,我们可以将打开文件限制增加到 5000,以考虑额外的非网络文件使用。可以使用以下命令完成此操作:
$ ulimit -n 5000
这仅更改当前 shell 会话的限制。
如果我们想将此更改持久化到未来的会话,可以将其添加到 shell 启动文件。对于 Bash,这将是:
$ echo "ulimit -n 5000" >> ~/.bashrc
macOS
如果软限制太低,请将当前会话设置为(此处写入的值通常接近默认值):
sudo launchctl limit maxfiles 65536 200000
由于需要 sudo,系统会提示您输入密码。
更改硬限制
Linux
如果上述命令导致类似 cannot modify limit: Operation not permitted 或 value exceeds hard limit 的错误,则表示硬限制太低,如前所述,这只能由 root 用户更改。
这可以通过修改 /etc/security/limits.conf
文件来完成。
例如,要为 alice 账户设置每个进程打开文件数量的软限制和硬限制,请以 root 身份在您选择的文本编辑器中打开 /etc/security/limits.conf
并添加以下行:
alice soft nofile 5000
alice hard nofile 1048576
新的限制将在注销并重新登录后生效。
或者,* hard nofile 1048576 会将设置应用于所有非 root 用户账户,而 root hard nofile 1048576 则应用于 root 用户。有关 ulimit 命令的文档,请参阅该文件中的文档或 man bash。
macOS
下一步将是配置新的文件限制。打开终端并粘贴以下命令:
sudo nano /Library/LaunchDaemons/limit.maxfiles.plist
这将在您的终端窗口中打开一个文本编辑器,系统会提示您提供用户密码,然后粘贴以下内容:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>limit.maxfiles</string>
<key>ProgramArguments</key>
<array>
<string>launchctl</string>
<string>limit</string>
<string>maxfiles</string>
<string>64000</string>
<string>524288</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>ServiceIPC</key>
<false/>
</dict>
</plist>
按下 Control + X
将保存更改并退出编辑器。通过粘贴并保存此内容,我们为您的 maxfiles 限制引入了两个不同的限制。第一个(64000)是软限制,如果达到该限制,您的 Mac 将提示准备停止允许打开新文件,但仍然允许它们打开。如果达到第二个(524288),即硬限制,您将再次看到您的老朋友“too many open files”错误消息。
接下来我们将使用相同的步骤增加进程限制。
在 Terminal
中,使用此命令创建类似文件:
sudo nano /Library/LaunchDaemons/limit.maxproc.plist
同样,在提示您输入密码后,您可以粘贴以下内容并使用 Control + X
保存并关闭:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple/DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>limit.maxproc</string>
<key>ProgramArguments</key>
<array>
<string>launchctl</string>
<string>limit</string>
<string>maxproc</string>
<string>2048</string>
<string>4096</string>
</array>
<key>RunAtLoad</key>
<true />
<key>ServiceIPC</key>
<false />
</dict>
</plist>
完成此操作后,剩下的就是将 Mac 重启回恢复模式,打开 Terminal
,使用 csrutil enable
重新开启 SIP,并使用我们在开头使用的命令检查限制是否已更改。
在大多数情况下,这些限制应该足以在本地运行您的大部分简单测试一段时间,但您可以根据测试需要修改上述文件中的任何值。
警告
请注意,所有这些限制都是为了保护您的操作系统免受编写不良且可能大量泄漏内存的文件和应用程序的影响。我们建议不要过度设置这些值,否则您的系统可能会在 RAM 用尽时或用尽后变得非常缓慢。
本地端口范围
创建传出网络连接时,内核会从可用端口范围中为连接分配一个本地(源)端口。
GNU/Linux
在 GNU/Linux 上,您可以使用以下命令查看此范围:
$ sysctl net.ipv4.ip_local_port_range net.ipv4.ip_local_port_range = 32768 60999
虽然 28,231 个端口对于大多数用例可能已足够,但如果您正在测试数千个连接,这可能是一个限制因素。例如,您可以使用以下命令增加它:
sysctl -w net.ipv4.ip_local_port_range="16384 65000"
请注意,此范围同时适用于 TCP 和 UDP,因此请保守选择值并根据需要增加。
要使更改永久生效,请将 net.ipv4.ip_local_port_range=16384 65000
添加到 /etc/sysctl.conf
。最后的调整 如果您在进行上述更改后仍然遇到网络问题,请考虑启用 net.ipv4.tcp_tw_reuse
sysctl -w net.ipv4.tcp_tw_reuse=1
这将启用一个功能,可以快速重用处于 TIME_WAIT 状态的连接,从而可能获得更高的吞吐量。
macOS/Linux
在 macOS 上,默认的临时端口范围是 49152 到 65535,共计 16384 个端口。您可以使用 sysctl 命令检查此范围:
$ sysctl net.inet.ip.portrange.first net.inet.ip.portrange.last
net.inet.ip.portrange.first: 49152
net.inet.ip.portrange.last: 65535
一旦临时端口耗尽,您通常需要等待 TIME_WAIT 状态过期(2 * 最大段生存期)才能重用特定的端口号。您可以通过将范围更改为从 32768 开始来将端口数量加倍,这是 Linux 和 Solaris 上的默认值。(最大端口号是 65535,因此您无法增加高端。)
$ sudo sysctl -w net.inet.ip.portrange.first=32768
net.inet.ip.portrange.first: 49152 -> 32768
请注意,IANA 指定的官方范围是 49152 到 65535,并且某些防火墙可能会假定动态分配的端口在此范围内。您可能需要重新配置防火墙以使用本地网络外部更大的范围。
通用优化
本节介绍了一些不一定依赖于您的操作系统但可能影响测试的优化。
RAM 使用量
根据特定的 k6 测试:使用的最大 VU 数、JavaScript 依赖项的数量和大小以及测试脚本本身的复杂性,k6 在测试执行期间可能会消耗大量系统 RAM。虽然开发工作致力于尽可能减少 RAM 使用量,但在某些场景下,单次测试运行可能会使用数十 GB 的 RAM。
作为基准,每个 VU 实例需要 1MB 到 5MB 的 RAM,具体取决于您的脚本复杂性和依赖项。对于 1,000 VU 的测试,这大致需要 1GB 到 5GB 的系统 RAM,因此请确保有足够的物理 RAM 来满足您的测试需求。
如果您需要减少 RAM 使用量,可以使用选项 --compatibility-mode=base
。更多信息请阅读JavaScript 兼容模式。
虚拟内存
除了物理 RAM,还要确保系统配置了适当数量的虚拟内存或交换空间,以防需要更高的内存使用峰值。
您可以使用 swapon 或 free 命令查看系统上可用交换空间的状态和数量。
这里我们不会详细介绍交换配置,但您可以在网上找到许多指南。
网络性能
由于 k6 可以生成并维持大量的网络流量,它也会给现代操作系统的网络堆栈带来压力。在某些负载或网络条件下,通过调整操作系统的部分网络设置或重新构建测试的网络条件,可以实现更高的吞吐量和更好的性能。
TCP TIME_WAIT 周期
TCP 网络应用(例如 Web 客户端和服务器)为每个传入或传出连接分配一个网络套接字对(本地地址、本地端口、远程地址和远程端口的唯一组合)。通常,此套接字对用于单个 HTTP 请求/响应会话,并在此后很快关闭。但是,即使应用程序成功关闭了连接,如果收到新的匹配 TCP 段,内核可能仍会保留资源以快速重新打开同一套接字。这也会发生在网络拥塞导致某些数据包在传输中丢失的情况下。这将套接字置于 TIME_WAIT 状态,并在 TIME_WAIT 周期过期后释放。此周期通常配置在 15 秒到 2 分钟之间。
像 k6 这样的应用程序可能会遇到的问题是导致大量连接最终处于 TIME_WAIT 状态,这会阻止新的网络连接创建。
在这些情况下,在更改系统网络配置之前(这可能对其他应用程序产生不利的副作用),最好先采取一些常见的测试预防措施。使用不同的服务器端口或 IP
由于套接字是为本地地址、本地端口、远程地址和远程端口的组合唯一创建的,因此避免 TIME_WAIT 拥塞的一个安全变通方法是使用不同的服务器端口或 IP 地址。
例如,您可以配置您的应用程序在端口 :8080、:8081、:8082 等上运行,并将您的 HTTP 请求分散到这些端点。