应用和网站的性能,是影响它们能否成功的关键因素。但如何改善性能,并不总是清晰。代码质量与基础设施当然重要,但很多情况下,关注一些基本的应用交付技术,就能显著改善最终用户体验。例如,在应用栈中实现并优化缓存。本文介绍一些技术,帮助初学者和进阶用户更好地利用NGINX内容缓存功能。
概述
内容缓存位于客户端和“源站服务器”之间,保存经过它的内容副本。当客户端请求缓存已经存储的内容时,缓存直接返回内容,无须联系源站。由于缓存更接近客户端,这能改善性能;应用服务器也不必每次都从头生成页面,利用率更高。
浏览器与应用服务器之间可能存在多层缓存:浏览器缓存、中间缓存、内容分发网络(CDN),以及位于应用服务器之前的负载均衡器或反向代理。即使只在反向代理或负载均衡器层缓存,也能显著改善性能。
例如,作者在前一年优化一个加载缓慢的网站时,发现生成主页需要超过1秒。调试后发现,因为页面被标记为不可缓存,每个请求都会动态生成它。实际上,页面变化不频繁,也没有个性化内容,因此没有必要。作者试着让负载均衡器缓存主页5秒,仅这一项改动就带来了明显改善:首字节时间降到几毫秒,页面加载也肉眼可见地加快。
NGINX经常作为应用栈中的反向代理或负载均衡器部署,并提供完整的缓存功能。下一节介绍基本配置。
设置并配置基本缓存
启用基本缓存,只需要两个指令:proxy_cache_path 和 proxy_cache。前者设置缓存路径与配置,后者启用缓存。
proxy_cache_path /path/to/cache levels=1:2 keys_zone=my_cache:10m max_size=10g
inactive=60m use_temp_path=off;
server {
# ...
location / {
proxy_cache my_cache;
proxy_pass http://my_upstream;
}
}
proxy_cache_path 的各项参数定义如下:
- 本地磁盘缓存目录为
/path/to/cache/。 levels在缓存目录下建立两级目录结构。单个目录中的文件过多会减慢访问速度,因此大多数部署建议采用两级结构。不设置该参数时,NGINX将全部文件放在同一目录。keys_zone建立共享内存区域,用于存储缓存键和使用计时器等元数据。内存中的缓存键副本,让NGINX无须访问磁盘就能快速判断HIT或MISS。原文说明,1MB区域约可存储8000个键,因此示例中的10MB区域约可存储80000个键。max_size设置缓存大小上限,示例为10GB。该参数可选;不设置时,缓存可以增长到占用全部可用磁盘空间。达到上限后,名为cache manager的进程会删除最近最少使用的文件,使大小回到上限以内。inactive指定一个条目在没有被访问时能保留多久。示例中,60分钟没有被请求的文件,会由cache manager自动删除,不论它是否过期。默认值是10分钟(10m)。不活跃与过期不同:NGINX不会仅因内容超过缓存控制头定义的期限(例如Cache-Control:max-age=120)就自动删除。过期内容只有在未访问时间达到inactive时才会删除;访问过期内容时,NGINX向源站刷新内容,并重置不活跃计时器。- 准备写入缓存的文件会先写入临时存储区域。
use_temp_path=off让NGINX直接写入最终缓存目录。建议设置为off,避免跨文件系统的不必要复制。该参数从NGINX 1.7.10和NGINX Plus R6引入。
proxy_cache 启用与其父级 location 块URL匹配的内容缓存,示例为 /。也可以把它放在 server 块中,使其适用于该服务器中没有独立 proxy_cache 指令的所有 location 块。
源站故障时提供缓存内容
NGINX内容缓存的一项强大功能,是在无法从源站取得新内容时,提供缓存中的过期内容。例如,某个资源的全部源站可能故障或暂时繁忙。NGINX可以返回缓存中的旧文件,而不是把错误转给客户端,为被代理服务器增加容错能力,在服务器故障或流量突增时维持服务。使用 proxy_cache_use_stale 启用:
location / {
# ...
proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;
}
该示例中,如果NGINX从源站收到 error、timeout 或列出的任一 5xx 错误,并且缓存中存在所请求文件的过期版本,就返回旧文件,而不是把错误传给客户端。
精细调整缓存并改善性能
NGINX有丰富的可选设置。以下示例启用了其中几项:
proxy_cache_path /path/to/cache levels=1:2 keys_zone=my_cache:10m max_size=10g
inactive=60m use_temp_path=off;
server {
# ...
location / {
proxy_cache my_cache;
proxy_cache_revalidate on;
proxy_cache_min_uses 3;
proxy_cache_use_stale error timeout updating http_500 http_502
http_503 http_504;
proxy_cache_background_update on;
proxy_cache_lock on;
proxy_pass http://my_upstream;
}
}
这些指令配置如下行为:
proxy_cache_revalidate让NGINX刷新源站内容时使用条件GET。当请求的缓存条目按缓存控制头已经过期,NGINX会在发往源站的请求中加入If-Modified-Since。只有内容自最初缓存时Last-Modified所记录的时间以来发生变化,源站才需要发送完整内容,从而节约带宽。proxy_cache_min_uses指定条目被客户端请求多少次后才加入缓存。当缓存不断被填满时,它能确保只缓存较常访问的条目。默认值为1。proxy_cache_use_stale的updating参数与proxy_cache_background_update配合,使请求过期条目或正在从源站更新的条目时,仍返回旧内容。所有更新在后台完成;新文件完全下载前,所有请求都得到旧文件。- 开启
proxy_cache_lock后,如果多个客户端请求缓存中尚不存在的文件(MISS),只有第一个请求会到达源站。其他请求等待第一个请求完成,然后从缓存取文件。不开启时,所有未命中的请求都会直接进入源站。
将缓存分散到多个硬盘
如果有多个硬盘,可以用NGINX将缓存分散存储。以下示例根据请求URI,在两个硬盘之间平均划分请求:
proxy_cache_path /path/to/hdd1 levels=1:2 keys_zone=my_cache_hdd1:10m
max_size=10g inactive=60m use_temp_path=off;
proxy_cache_path /path/to/hdd2 levels=1:2 keys_zone=my_cache_hdd2:10m
max_size=10g inactive=60m use_temp_path=off;
split_clients $request_uri $my_cache {
50% “my_cache_hdd1”;
50% “my_cache_hdd2”;
}
server {
# ...
location / {
proxy_cache $my_cache;
proxy_pass http://my_upstream;
}
}
两个 proxy_cache_path 指令在不同硬盘定义 my_cache_hdd1 和 my_cache_hdd2。split_clients 配置块规定一半请求结果(50%)进入第一个缓存,另一半进入第二个。根据请求URI变量 $request_uri 计算的哈希,决定每个请求使用哪个缓存,所以同一个URI始终进入同一缓存。
常见问题
以下回答NGINX内容缓存的一些常见问题。
可以观测NGINX缓存状态吗
可以,使用 add_header:
add_header X-Cache-Status $upstream_cache_status;
它在发给客户端的响应中加入 X-Cache-Status HTTP头。$upstream_cache_status 的可能值包括:
MISS:缓存中未找到响应,因此从源站获取。响应随后可能已被缓存。BYPASS:请求匹配proxy_cache_bypass,因此从源站获取而不是从缓存提供。响应随后可能已被缓存,参见后文绕过缓存的说明。EXPIRED:缓存条目已过期,响应包含从源站获取的新内容。STALE:源站未正确响应,且配置了proxy_cache_use_stale,因此返回过期内容。UPDATING:此前请求正在更新条目,且配置了proxy_cache_use_stale updating,因此当前返回过期内容。REVALIDATED:启用了proxy_cache_revalidate,NGINX通过If-Modified-Since或If-None-Match验证缓存内容仍有效。HIT:响应直接来自有效且新鲜的缓存内容。
NGINX如何决定是否缓存
原文说明,只有源站响应包含一个未来日期的 Expires 头,或包含 max-age 为非零值的 Cache-Control 头,NGINX才缓存响应。
默认情况下,NGINX遵循 Cache-Control 中的其他指令:出现 Private、No-Cache 或 No-Store 时不缓存。包含 Set-Cookie 的响应也不缓存。默认仅缓存 GET 和 HEAD 请求的响应。后文介绍如何覆盖这些默认行为。
如果 proxy_buffering 设置为 off,NGINX不会缓存响应。默认值是 on。
能否忽略Cache-Control响应头
可以,使用 proxy_ignore_headers,例如:
location /images/ {
proxy_cache my_cache;
proxy_ignore_headers Cache-Control;
proxy_cache_valid any 30m;
# ...
}
NGINX会忽略 /images/ 下所有内容的 Cache-Control。proxy_cache_valid 强制规定缓存数据的有效期;忽略 Cache-Control 时,需要设置它。没有有效期的文件不会被缓存。
能否缓存响应头含Set-Cookie的内容
可以,使用上一问所述的 proxy_ignore_headers 指令。
能否缓存POST请求
可以,使用 proxy_cache_methods:
proxy_cache_methods GET HEAD POST;
该示例开启了 POST 请求的缓存。
能否缓存动态内容
可以,只要 Cache-Control 允许。即使只缓存很短时间,也能降低源站和数据库负载,改善首字节时间,因为不必每个请求都重新生成页面。
能否为特定请求绕过缓存
可以,使用 proxy_cache_bypass:
location / {
proxy_cache_bypass $cookie_nocache $arg_nocache;
# ...
}
它定义哪些请求直接向源站获取内容,而不先查缓存,有时称为在缓存中“打一个洞”。示例中的 nocache Cookie或参数触发绕过,例如 http://www.example.com/?nocache=true。NGINX仍可能缓存返回的响应,供未来未绕过缓存的请求使用。
NGINX使用什么缓存键
默认缓存键近似为以下NGINX变量组合的MD5哈希:$scheme$proxy_host$request_uri;实际算法略复杂。
proxy_cache_path /path/to/cache levels=1:2 keys_zone=my_cache:10m max_size=10g
inactive=60m use_temp_path=off;
server {
# ...
location / {
proxy_cache my_cache;
proxy_pass http://my_upstream;
}
}
该示例中,http://www.example.org/my_image.jpg 的缓存键按 md5(“http://my_upstream:80/my_image.jpg”) 计算。
注意,哈希使用 $proxy_host,而不是实际主机名 www.example.com。$proxy_host 是 proxy_pass 指令指定的被代理服务器名称与端口。
要修改构成缓存键的变量或其他项,使用 proxy_cache_key,也参见下一问。
能否把Cookie作为缓存键的一部分
可以,缓存键可设置为任意值,例如:
proxy_cache_key $proxy_host$request_uri$cookie_jessionid;
示例将 JSESSIONID Cookie值加入缓存键。URI相同但Cookie值不同的条目,将作为不同条目分别缓存。
NGINX使用ETag响应头吗
NGINX 1.7.3及NGINX Plus R5和更新版本完整支持 ETag 与 If-None-Match。
如何处理字节范围请求
如果文件在缓存中且仍有效,NGINX遵循字节范围请求,仅发送指定字节。如果文件未缓存或已过期,NGINX会从源站下载整个文件。请求单个字节范围时,下载流到达该范围后就立即把它发送给客户端;请求同一文件的多个范围时,则在下载完成后向客户端提供整个文件。
下载完成后,整个资源进入缓存,未来无论请求单个还是多个字节范围,都可以立即从缓存满足。
上游服务器本身必须支持字节范围请求,NGINX才能遵循针对该上游的范围请求。
支持缓存清除吗
NGINX Plus支持选择性清除缓存文件。当源站文件已更新,但NGINX Plus中的副本仍有效(Cache-Control:max-age 尚未到期,proxy_cache_path 的 inactive 超时也未到)时,这很有用。缓存清除功能可以删除它。更多信息参见“从缓存中清除内容”。
如何处理Pragma请求头
客户端加入 Pragma:no-cache,表示希望绕过所有中间缓存,直接从源站获取内容。NGINX默认不遵循它,但可以用以下 proxy_cache_bypass 配置:
location /images/ {
proxy_cache my_cache;
proxy_cache_bypass $http_pragma;
# ...
}
支持Cache-Control的stale-while-revalidate和stale-if-error扩展吗
支持,起始版本是NGINX 1.11.10及NGINX Plus R12。
stale-while-revalidate允许在缓存响应正在更新时继续使用旧响应。stale-if-error允许在发生错误时使用旧缓存响应。
这些响应头的优先级低于前文的 proxy_cache_use_stale 指令。
支持Vary响应头吗
支持,起始版本是NGINX Plus R5和NGINX 1.7.7。原文链接中提供了对 Vary 头的概述。
进一步阅读
NGINX缓存还有许多可定制和调优的方法。以下资源可供继续学习:
ngx_http_proxy_module参考文档,包含内容缓存的全部配置选项。- 两场可点播网络研讨会:Content Caching with NGINX Plus和High-Availability Content Caching with NGINX,逐步讲解本文的大部分内容。
- 电子书High-Performance Caching with NGINX and NGINX Plus,深入介绍内容缓存。










暂无评论内容