Valentin Bartenev 和 Nick Shadrin 等多位 NGINX 团队成员对本文亦有贡献。
在过去的几年里,我曾与一些合作伙伴合作,对他们来说,NGINX Plus 的性能是首要关注的问题。我们的对话往往从难以达到我们发布的性能基准开始。之所以会出现这样的问题,通常是因为他们会直接跳到特定的用例,例如使用现有的 SSL 密钥或针对非常大的文件负载,然后发现 NGINX Plus 的性能低于标准。
从某种程度上来说,这一结果并不意外。我总是向合作伙伴解释说,作为一个软件组件,NGINX Plus 在处理最基本的 HTTP 用例时,能够在任何可用的硬件上以近乎线性的速度运行。不过,想要在特定的用例中达到我们发布的理论性能,NGINX Plus 通常需要在配置、底层操作系统以及硬件设置方面进行一定的调整。
在迄今为止的每一个用例中,我们的合作伙伴只需根据特定的用例场景对操作系统组件和硬件设置进行一定的设置,并调整 NGINX Plus 与这些组件的交互方式,都能在非常独特的用例中让 NGINX Plus 达到理论性能。
经过多年的总结,我整理了这样一份关于如何调整 NGINX 配置、操作系统和硬件的建议与技巧清单,希望能够帮助我们的合作伙伴和客户在特定的用例中提高 NGINX 开源版和 NGINX Plus 的性能。
本文只是对可能影响性能的一部分配置设置提供了指导,并没有涉及 NGINX 性能优化的全部方面,也不一定适合在您的环境中更改下面讨论的每一项设置。
注意: 本博文自首次发布以来已经进行了修订,未来还将进一步审核以确保其尽可能完善。欢迎在下面的评论区中添加您的更改和补充建议,我们将视情况予以采纳。
调优工作流程
对于 NGINX 的性能调优问题,我通常建议采用以下工作流程:
-
尽量在最通用的 HTTP 用例中测试 NGINX Plus 的性能,这可以为您的环境确立正确的测试基线。
-
明确您的用例。例如,您的应用需要大文件上传,或者您要处理更大长度的 SSL 密钥,请先定义您用例的最终目标。
-
针对您的用例配置 NGINX Plus,并重新测试以确定在您环境中实际使用性能与理论性能的差值。
-
重点关注最适合您用例的设置,一次只调整一个设置。换言之,不要在添加新的 NGINX 指令的同时改变一堆
systemctl
设置。应该先从小处着手,尤其是那些与您的用例最相关的功能。例如,如果高安全性对您的环境至关重要,就先调整 SSL 密钥类型和长度。 -
如果某项更改对性能提升无效,就再改回到默认设置。在逐个调整的过程中,您会发现有些关联的设置会一起对性能产生的影响,那么在未来的优化中,您就可以根据需要将这些设置作为一个整体去调整。
值得注意的是,每个部署环境都是独一无二的,并且都有自己的网络和应用性能要求。我们不建议在生产环境中更改一些设置。对于下文所述的配置调整,其效果可能会因为应用类型和网络拓扑的不同而存在明显差异。
鉴于 NGINX 在开源社区中有着强大的基础,多年来许多人都为如何提升性能做出了回馈与贡献。本文视情况添加了一些外部资源的链接,介绍了人们在经过实际生产测试后得出的一些具体的性能调优建议。
调整 NGINX 配置
请参阅 NGINX 参考文档,详细了解每项设置所允许的的值、默认设置以及设置范围。
SSL
本节描述了如何从 OpenSSL 和 NGINX 中删除缓慢且不必要的密码 。
鉴于 SSL 性能如此重要,在环境中尝试不同的密钥长度和类型通常很有必要。这可以帮助您在满足特定安全需求的前提下,在“长密钥、高安全”与“短密钥、高性能”之间找到一个平衡点。有一种简单的测试办法:从更传统的 RSA 密钥转变成椭圆曲线密码算法 (ECC),后者使用更小的密钥长度,因此能够在实现相同的安全级别的情况下拥有更快的计算速度。
要想快速生成自签名的 ECC P-256 密钥进行测试,请运行以下命令:
# openssl ecparam -out ./nginx-ecc-p256.key -name prime256v1 -genkey
# openssl req -new -key ./nginx-ecc-p256.key -out ./nginx-ecc-p256-csr.pem -subj '/CN=localhost'
# openssl req -x509 -nodes -days 30 -key ./nginx-ecc-p256.key -in ./nginx-ecc-p256-csr.pem -out ./nginx-ecc-p256.pem
压缩
Gzip 参数可以对 NGINX 提供内容的方式进行细粒度的控制,因此设置不当可能会导致 NGINX 性能下降。启用 gzip 可以节省带宽,改善缓慢连接的页面加载速度。(在本地运行的综合基准测试中,启用 gzip 的效果可能与实际使用效果有所不同。)为了实现最高性能,您可以尝试以下设置:
- 仅针对相关内容启用 gzip,例如文本、JavaScript 和 CSS 文件;
- 请勿提高压缩级别,因为这会增加 CPU 资源的消耗,却不能带来与之匹配的吞吐量提升;
- 针对不同类型和大小的内容启用和禁用 gzip,以评估启用压缩的效果;
有关 gzip 细粒度控制的更多信息,请参考 NGINX gzip 模块的参考文档。
连接处理
下面介绍几个与连接处理相关的调优选项。关于正确的语法和适用的配置代码块 (http
、server
、location
)等信息,请参阅链接的参考文档。
accept_mutex
off
—— 所有 worker 进程都会收到关于新连接的通知(NGINX 1.11.3 及更高版本、NGINX Plus R10 及更高版本的默认设置)。如果启用该选项,worker 进程将轮流接收新连接。我们建议保持默认值(
off
),除非您对您的应用性能有着全面的了解,并有机会在各种条件下进行测试。但如果新连接的数量不多,这会导致无法有效利用系统资源。在某些高负载的情况下,将该值改为on
可能会比较有效。-
keepalive
128
—— 启用从 NGINX Plus 到上游服务器的 keepalive 连接,定义每个 worker 进程缓存中保留的最大空闲 keepalive 连接数。超过这个数值时,就关闭最近使用最少的连接。如果没有设置 keepalive,就会产生更多的开销,并且连接和临时端口都无法得到有效利用。对于 HTTP 流量,如果您在
upstream
代码块中使用该指令,还必须在配置中添加以下指令,以确保它们在上游服务器组的所有location
代码块中得到应用(您可以将它们放到各个location
代码块、父server
代码块或者http
级别上):proxy_http_version
1.1
—— NGINX Plus 使用 HTTP/1.1 处理代理的请求proxy_set_header
Connection
""
—— NGINX Plus 从代理请求中剥离所有Connection
请求头
-
multi_accept
off
—— 一个 worker 进程一次只接收一个新连接(默认)。如果启用,则一个 worker 进程将一次接收所有新连接。除非您确定修改有好处,否则我们建议保持默认值 (
off
)。用默认值进行性能测试,有助于更好地衡量可预测的结果。 -
proxy_buffering
on
—— NGINX Plus 会尽快接收来自代理服务器的响应,并进行缓冲(默认)。如果禁用,NGINX Plus 会在收到响应时同步传递给客户端,从而增加 NGINX Plus 的负载。只有需要立即访问数据流的应用才有必要禁用响应缓冲。
-
listen
80
reuseport
—— 启用端口分片 (port sharding),也就是为每个 worker 进程创建独立的监听套接字(使用SO_REUSEPORT
套接字选项),这允许内核为不同的 worker 进程分发接收到的连接。有关更多信息,请参阅我们的博文:《NGINX Release 1.9.1 的套接字分片功能》。
日志
日志是管理和审计系统的一个重要工具。记录大量数据并存储大量日志可能会使系统资源紧张,因此我们建议您仅在非常特别的情况下或在进行性能故障排除时禁用日志记录。
access_log
off
—— 禁用访问日志。access_log
/path/to/access.log
main
buffer=16k
—— 启用访问日志的缓冲功能。
很多开源项目和商用产品厂商都会提供基于 syslog protocol 的集中式日志记录系统,此系统可能会给您带来便利。但如果您需要 NGINX 和 NGINX Plus 服务器的指标(整合了最初在日志中记录的信息),您可以使用 NGINX Amplify。
线程池
线程池由一个任务队列和处理该队列的多个线程组成。当一个 worker 进程需要执行一个可能很长的操作时,它不会亲自处理这个操作,而是会将这个任务放在线程池的队列中,这样任何空闲线程都可以从中获取和处理。
要启用线程池,请添加 aio
threads
指令。请注意,线程池的管理方式可能会受到其他缓冲区相关配置设置的影响。有关调整其他设置以支持线程池的完整信息,请参见 我们的博文。
CPU 亲和性 (CPU Affinity)
CPU 亲和性 (CPU Affinity) 用于指定 NGINX Plus 为每个 worker 进程使用哪个或哪些 CPU 内核(有关背景信息,请参阅 worker_cpu_affinity
参考文档)。
在大多数情况下,我们建议使用默认的自动参数 worker_processes
指令,它设置了与可用 CPU 内核数量相匹配的 worker 进程的数量。
但是,当 NGINX Plus 在 Docker 等容器环境中运行时,系统管理员可能会选择为容器分配主机上可用的较少的内核资源。在这种情况下,NGINX Plus 会检测主机上可用的内核数量,并在容器实际可用的内核之间轮换 worker 进程。在这种情况下,应该指定 worker_processes
的数值为容器中实际可用的内核数量,以减少 worker 进程的数量。
测试 CPU 亲和性
通常在测试时,我们建议您使用与生产流量相似的流量。但在基础测试中,您可以使用 wrk
等流量生成器,如此处所述。
为 NGINX Plus 快速加载 wrk
会话:
# wrk -t 1 -c 50 -d 20s http://localhost/1k.bin
如有必要,您可以创建一个简单的 1k.bin 文件进行测试:
# dd if=/dev/zero of=1kb.bin bs=1024 count=1
在 CPU 监控模式下执行 top
指令(top
启动后按 1
)。
您可以使用不同数量的 worker 进程数量和亲和性绑定设置反复进行测试,来观察是否线性。这是一种有效的方法,可以将访问限制设置为合适的部分可用内核。
选型建议
这里粗略介绍了一些近似的选型建议,适用于普通 Web 服务和负载均衡,这些值可能不适用于 VOD 流媒体或 CDN 应用场景。
CPU
为每 1-2 Gbps 未加密流量分配 1 个 CPU 内核。
小型 (1–2 KB) 响应以及每个连接的 1 个响应会增加 CPU 负载。
RAM
为操作系统和其他一般需求分配 1GB RAM。
其余的分配给 NGINX Plus 缓冲区、套接字缓冲区和虚拟内存缓存,粗略估计每个连接需要 1MB。
详细信息
proxy_buffers
(每个连接)- 为避免磁盘 I/O ,应该指定
proxy_buffers
的大小。如果响应大小大于 (proxy_buffers
size +proxy_buffer_size
),响应可能会被写入磁盘,从而增加 I/O 和响应时间等等。
共享内存区
从表面上看,共享内存区用于存储多个上游服务器的共享数据,例如状态、指标、cookie、健康检查等。
然而,该区域也会影响 NGINX Plus 在多个组件(例如 worker 进程)之间分配负载的方式。有关共享内存区会对哪些组件产生影响的完整文档,请参见《NGINX Plus 管理员指南》。
由于使用模式差异性很大,我们无法规定确切的设置。每种功能都会影响共享内存区的大小,比如会话保持(使用 sticky
指令)、健康检查或 DNS 重解析等。举例来说,如果采用 sticky
route
会话保持方法并启用单项健康检查,那么一个 256KB 的内存区可以容纳以下指定数量的上游服务器的信息:
- 128 台服务器(每个被定义为一个 IP-address:port 对)
- 88 台服务器(每个被定义为一个 hostname:port 对,其中主机名解析到单个 IP 地址)
- 12 台服务器(每个被定义为一个 hostname:port 对,其中主机名解析到多个 IP 地址)
需要注意的是,在创建区域时,共享内存区是由区域名称控制的。如果所有共享内存区都使用相同的名称,则所有上游服务器的所有数据都将存储在该共享区中。这种情况下可能会超出大小限值。
磁盘 I/O
磁盘 I/O 受每秒 I/O 操作次数 (IOPS) 所限制。
NGINX Plus 的许多功能都依赖磁盘 I/O 和 IOPS,比如日志和缓存。