NGINX 和 NGINX Plus 被广泛应用于 Web 内容缓存,小到个人网站,大到一些世界大型内容交付网络 (CDN),例如 MaxCDN 和 CloudFlare。
微缓存通过将动态、非个性化内容缓存很短的时间,可以有效地加速这些内容的交付。在本文中,我们将展示如何借助微缓存技术将基于 WordPress 的应用提速高达 400 倍。
为什么要缓存内容?
缓存能够一举两得:通过更快地交付内容改善网站性能,同时减轻源服务器的负担。缓存的效率取决于内容的可缓存性。这些内容可以存储多长时间,如何检查更新,可以将同一缓存内容发送给多少用户?
缓存静态内容,例如图片、JavaScript 和 CSS 文件以及几乎不变的 Web 内容,是个相对简单的过程。缓存更新的处理方法包括常规过期和条件 get,必要时,还可以使用 cachebusting 技术来更改引用对象的 URL。
通常无法缓存个性化内容(即服务器应用为每位用户定制提供的内容),因为针对对同一资源的每次请求,服务器的响应不尽相同。服务器端引用 (SSI) 和页面片段缓存 (ESI) 等技术可用于组装页面,但是这些技术实施起来比较复杂,而且不一定能改善性能。
在以上两类内容中间,是另一种有趣的待缓存对象,即可能会发生意外更改,但并未针对每位用户进行个性化(或者在客户端使用 JavaScript 进行个性化)的动态内容。这类内容的生成开销较高,提供过时版本会引发一系列问题。
适合缓存的动态内容包括:
- 繁忙新闻或博客网站的首页,每隔几秒就有新文章发布
- 最近资讯的 RSS 源
- 持续集成 (CI) 或构建平台的状态页面
- 库存、状态或筹款计数
- 彩票结果
- 日历数据
- 在客户端设备上生成的个性化动态内容,例如使用 cookie 数据生成的广告内容或数据(“你好,(你的名字)”)
动态内容的微缓存
微缓存是一种缓存技术,只将内容缓存比如 1 秒左右的极短时间。这实际上意味着网站更新延迟不到 1 秒钟,这在很多情况下都是可以接受的。
这种短暂缓存能给网站性能带来明显的改观吗?让我们试试看!
测试应用
在本次测试中,我们将使用标准 WordPress install,并为其填充一些样本内容。
显而易见,即便是处理基本内容,WordPress 服务器也存在性能问题:使用 ab 进行基准测试时,它每秒只能服务 5.53 个请求:
root@nginx-client:~# >ab -c 10 -t 30 -k http://nginx-server/
Requests per second: 5.53 [#/sec] (mean)
Time per request: 1809.260 [ms] (mean)
Time per request: 180.926 [ms] (mean, across all concurrent requests)
Transfer rate: 319.74 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.3 0 3
Processing: 1430 1735 259.4 1580 2228
Waiting: 537 683 119.7 624 980
Total: 1430 1735 259.4 1580 2228
在测试期间,vmstat
显示造成瓶颈的主要原因是利用 PHP 生成页面的 CPU 消耗(在 cpu
下面的 us
一列,数值为 96 到 98):
root@nginx-server:/var/www/html# vmstat 3
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
10 0 0 136076 44944 585920 0 0 0 0 476 1665 96 4 0 0 0
10 0 0 140112 44952 585924 0 0 0 4 506 1773 98 2 0 0 0
10 0 0 136208 44952 585924 0 0 0 0 576 2057 97 3 0 0 0
top
命令显示,CPU 被 10 个执行 PHP 解释器的 Apache httpd
进程占用。
这种设置本身就存在问题——它限制网站每秒处理请求数不能超过 5 个,导致网站极易遭到 DOS 攻击,而通过添加 CPU 来解决这个问题则意味着每年托管费用增加 1,000 美元。
利用 NGINX 部署简单微缓存
利用 NGINX 来加速该服务只需两步。
第一步:通过 NGINX 进行代理
在 WordPress 服务器上安装 NGINX 或 NGINX Plus,并将其配置为接收入站流量并在内部转发给 WordPress 服务器:
NGINX 代理配置相对简单:
server {
listen external-ip:80; # External IP address
location / {
proxy_http_version 1.1; # Always upgrade to HTTP/1.1
proxy_set_header Connection ""; # Enable keepalives
proxy_set_header Accept-Encoding ""; # Optimize encoding
proxy_pass http://wordpress-upstreams;
}
status_zone wordpress; # NGINX Plus status monitoring
}
upstream wordpress-upstreams {
zone wordpress 128k;
keepalive 20; # Keepalive pool to upstream
server localhost:80;
}
我们还修改了 Apache 配置(监听端口号和虚拟服务器),以便 Apache 仅绑定到 localhost:80。
您可能会认为,添加额外的代理服务器会对性能产生不利影响,但实际上由此造成的性能影响可忽略不计:
root@nginx-client:~# ab -c 10 -t 30 -k http://nginx-server/
Requests per second: 5.63 [#/sec] (mean)
Time per request: 1774.708 [ms] (mean)
Time per request: 177.471 [ms] (mean, across all concurrent requests)
Transfer rate: 324.44 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.2 0 1
Processing: 1423 1709 341.3 1532 2794
Waiting: 554 703 165.0 608 1165
Total: 1423 1709 341.4 1532 2794
在较繁忙的服务器(处理更多并发请求)中,仅靠 NGINX 实现的 keepalive 优化就能带来显著的性能提升。
第二步:启用短期缓存
只需在服务器配置中添加两条指令,NGINX 或 NGINX Plus 就可以缓存所有可缓存的响应。带有 200
OK
状态码的响应只缓存 1 秒。
proxy_cache_path /tmp/cache keys_zone=cache:10m levels=1:2 inactive=600s max_size=100m;
server {
proxy_cache cache;
proxy_cache_valid 200 1s;
# ...
}
当我们再次运行基准测试时,可以看到显著的性能提升:
root@nginx-client:~# ab -c 10 -t 30 -k http://nginx-server/
Complete requests: 18022
Requests per second: 600.73 [#/sec] (mean)
Time per request: 16.646 [ms] (mean)
Time per request: 1.665 [ms] (mean, across all concurrent requests)
Transfer rate: 33374.96 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 1 0.5 1 10
Processing: 0 16 141.5 3 2119
Waiting: 0 6 54.6 1 818
Total: 1 17 141.5 4 2121
性能提升 120 倍,每秒处理请求数从 5 条增加到 600 条;这简直太棒了,但还有个问题。
虽然缓存运行平稳,每秒刷新一次内容(因此永不过时),但是计划之外的情况发生了。大家可以看到处理时间的标准偏差很大(141.5 毫秒)。CPU 使用率仍为 100%(用 vmstat
测量),top
显示有 10 个活跃的 httpd
进程。
我们可以从 NGINX Plus 的实时活动监控仪表盘中获得进一步的线索。测试前:
测试后:
仪表盘报告显示,NGINX 在测试期间处理了 18,032 条请求(ab
报告的 18,022 条请求,加上基准测试在 30 秒结束时未处理的 10 条请求)。但是,NGINX 将 150 条请求转发给了上游服务器,这远超我们的预期(在 30 秒测试期间内将内容缓存 1 秒钟)。
怎么回事?为什么 CPU 使用率很高,缓存更新次数超出预期?
这是因为每当缓存条目过期,NGINX 就会停止使用它。NGINX 将所有请求都转发给上游 WordPress 服务器,直到它收到响应,可以用新内容重新填充缓存。
这导致 WordPress 服务器收到的请求经常激增到 10 条。这些请求不光占用 CPU,而且比缓存响应的请求延迟更大,这就造成了测试结果中的高标准差。
使用 NGINX 优化微缓存
我们的策略很清晰:需要在确保缓存内容最新的情况下,尽可能少地向上游源服务器转发请求。在缓存内容进行更新时,可以从缓存中提供旧的响应(延后 1 到 2 秒)。为此,需要添加两条指令:
proxy_cache_lock
——限制填充缓存的并发尝试数,这样当一条缓存项创建后,对该资源的后续请求将会在 NGINX 中排队。proxy_cache_use_stale
——将 NGINX 配置为在缓存项进行更新时提供旧(最近缓存)内容。
加上之前添加的缓存指令,可得到如下服务器配置:
server {
proxy_cache one;
proxy_cache_lock on;
proxy_cache_valid 200 1s;
proxy_cache_use_stale updating;
# ...
}
基准测试结果变化显著。每秒请求数从 600 跃升至 2,200 左右:
root@nginx-client:~# ab -c 10 -t 30 -n 100000 -k http://nginx-server/
Concurrency Level: 10
Time taken for tests: 30.001 seconds
Complete requests: 65553
Failed requests: 0
Keep-Alive requests: 0
Total transferred: 3728905623 bytes
HTML transferred: 3712974057 bytes
Requests per second: 2185.03 [#/sec] (mean)
Time per request: 4.577 [ms] (mean)
Time per request: 0.458 [ms] (mean, across all concurrent requests)
Transfer rate: 121379.72 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 1 0.3 1 5
Processing: 1 4 8.1 3 661
Waiting: 0 1 2.6 1 250
Total: 1 5 8.1 4 661
CPU 使用率也低很多(注意 cpu
下面 id
一栏的空闲时间):
root@nginx-server:/var/www/html# vmstat 3
procs -----------memory---------- ---swap-- -----io---- -system--- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 0 106512 53192 641116 0 0 0 37 11016 3727 19 45 36 0 0
1 0 0 105832 53192 641808 0 0 0 68 17116 3521 13 56 31 0 0
1 0 0 104624 53192 643132 0 0 0 64 14120 4487 15 51 33 0 0
数据传输率(121379.72 千字节/秒,或 121 兆字节/秒)相当于 0.97 Gbps,因此该测试受网络限制。CPU 平均使用率为 66%,该服务器的峰值性能大概为 2185/0.66 = 3300 个请求/秒。
另外请注意 ab 报告的连续响应时间(标准偏差只有 8.1 毫秒),以及仪表盘显示的在 30 秒测试期间转发给上游服务器的请求数量 (16):
为什么只有 16 条请求?我们知道缓存到 1 秒钟时会超时,更新过程需要长达 0.661 秒(从 ab
结果来看),因此可以推测,更新频率不会快于每 1.66 秒一次。在 30 秒钟的时间内,预计收到最多 18 (30/1.66) 条请求。
了解更多信息
本文简单展示了将动态内容缓存较短时间的潜在好处,以及 NGINX Plus 的实时活动监测数据在缓存配置调优和诊断中的用处。如果您想在生产环境中使用微缓存,建议您创建并测试一个更为复杂的缓存策略,对动态和静态内容进行更长时间的微缓存。
NGINX Plus 还拥有一项缓存清除功能,可用来快速清除 NGINX 缓存中的指定内容。如果您想将内容缓存更长时间,但在有更改时即刻更新它们,就可以使用这项功能。
如欲了解更多信息,请查阅以下资源:
- NGINX 缓存指南——概述和方法介绍
- 内容缓存——NGINX Plus 管理指南
- 大规模可扩展的内容缓存——NGINX Plus 功能描述