众所周知,应用和网站的性能是决定其能否成功的关键因素。然而,如何提升应用或网站的性能并不总是一目了然。代码质量和基础设施固然至关重要,但在许多情况下,只需重点采用一些非常基础的应用交付技术,就能显著改善应用的最终用户体验。例如,在应用堆栈中实施并优化缓存。本篇博客介绍了一些相关技术,无论是新手还是资深用户,都可以利用NGINX和NGINX Plus中包含的内容缓存功能来提升性能。
概述
内容缓存位于客户端和“源服务器”之间,并保存其接收到的所有内容的副本。如果客户端请求的内容已存储在缓存中,缓存会直接返回该内容,无需联系源服务器。由于内容缓存更靠近客户端,因此可以提升性能;同时,应用服务器无需每次都从头生成页面,因而能够得到更高效的利用。
Web浏览器与应用服务器之间可能存在多级缓存,包括客户端的浏览器缓存、中间缓存、内容分发网络(CDN),以及位于应用服务器前端的负载均衡器或反向代理。即使只在反向代理或负载均衡器层面进行缓存,也能显著提升性能。
举例来说,去年我负责对一个加载缓慢的网站进行性能调优。我首先注意到的问题之一是,生成主页需要超过1秒。经过一番调试,我发现该页面被标记为不可缓存,因此每次收到请求时都会动态生成。但页面本身并不经常变化,也没有个性化内容,所以没有必要这样做。为了验证效果,我将主页设置为由负载均衡器缓存5秒,仅此一项调整就带来了明显改善。首字节响应时间缩短至几毫秒,页面加载速度也明显加快。
NGINX通常在应用程序栈中部署为反向代理或负载均衡器,并提供一整套缓存功能。下一节将介绍如何使用NGINX配置基本缓存。
如何设置和配置基本缓存
只需两个指令即可启用基本缓存:proxy_cache_path和proxy_cache。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;
}
}The parameters to the proxy_cache_path指令的参数定义了以下设置:
- 缓存的本地磁盘目录名为/path/to/cache/。
levels会在 /path/to/cache/ 下设置两级目录层次结构。单个目录中包含大量文件可能会降低文件访问速度,因此建议大多数部署采用两级目录层次结构。如果未包含levels参数,NGINX会将所有文件放在同一目录中。keys_zone用于设置共享内存区,以存储缓存键和使用计时器等元数据。在内存中保留一份键的副本后,NGINX无需访问磁盘即可快速确定请求是缓存HIT还是缓存MISS,从而显著加快检查速度。1 MB的内存区可存储约8,000个键的数据,因此示例中配置的10 MB内存区可存储约80,000个键的数据。max_size设置缓存大小上限(本例中为 10 GB)。此参数为可选参数;如果未指定值,缓存可增长至占用所有可用磁盘空间。当缓存大小达到上限时,名为“缓存管理器”的进程会删除最近最少使用的文件,使缓存大小恢复到上限以下。inactive指定缓存项在未被访问的情况下可在缓存中保留多长时间。在本例中,无论文件是否已过期,60 分钟内未被请求的文件都会由缓存管理器进程自动从缓存中删除。默认值为 10 分钟(10m)。非活动内容与过期内容不同。NGINX不会自动删除缓存控制标头所定义的过期内容(例如Cache-Control:max-age=120)。只有当过期(陈旧)内容在inactive指定的时间内未被访问时,才会被删除。访问过期内容时,NGINX会从源服务器刷新该内容,并重置inactive计时器。- NGINX首先将要缓存的文件写入临时存储区域,
use_temp_path=off指令会让NGINX将这些文件写入其缓存所在的同一目录。建议将此参数设置为off,以避免在文件系统之间进行不必要的数据复制。use_temp_path自NGINX 1.7.10版和NGINX Plus R6起引入。
最后,proxy_cache指令会启用缓存,缓存与父级location块的URL匹配的所有内容(在本例中为/)。也可以在proxy_cache指令包含在server块中;该指令适用于此服务器中未单独设置location指令的所有proxy_cache块。
源站停机时提供缓存内容
NGINX的一项强大功能是内容缓存。当NGINX无法从源服务器获取新内容时,可以将其配置为从缓存中提供过期内容。如果缓存资源的所有源服务器均已停机或暂时繁忙,就可能出现这种情况。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会提供该过期文件,而不是将错误转发给客户端。
精细调整缓存并提升性能
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会在发送到源服务器的GET请求标头中包含If-Modified-Since字段。这样可以节省带宽,因为只有当该项目在NGINX最初缓存时文件所附Last-Modified标头记录的时间之后发生过修改,服务器才会发送完整项目。proxy_cache_min_uses用于设置客户端必须请求某个项目多少次后,NGINX才会缓存该项目。如果缓存不断被填满,此设置会很有用,因为它可以确保只有访问最频繁的项目才会添加到缓存中。默认情况下,proxy_cache_min_uses设置为 1。- 为
updating参数配置proxy_cache_use_stale指令,并启用proxy_cache_background_update指令后,当客户端请求已过期或正在从源服务器更新的内容时,NGINX会返回过期内容。所有更新都将在后台进行。在更新后的文件完全下载之前,所有请求都会收到过期文件。 - 启用
proxy_cache_lock后,如果多个客户端请求缓存中不是最新版本的文件(即MISS),则仅允许其中第一个请求发送到源服务器。其余请求会等待该请求完成,然后从缓存中获取文件。如果未启用proxy_cache_lock,所有导致缓存未命中的请求都会直接发送到源服务器。
将缓存拆分到多个硬盘
如果有多个硬盘,可以使用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%)的结果缓存在my_cache_hdd1中,另一半缓存在my_cache_hdd2中。基于$request_uri变量(请求URI)的哈希值决定每个请求使用哪个缓存,因此,针对同一URI的请求始终会缓存在同一个缓存中。
请注意,此方法不能替代RAID硬盘配置。如果硬盘发生故障,可能会导致系统出现不可预测的行为,包括用户向故障硬盘发出的请求收到500响应代码。正确配置的RAID硬盘可以应对硬盘故障。
常见问题(FAQ)
本节解答有关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如何确定是否缓存内容?
仅当源服务器包含以下任一内容时,NGINX才会缓存响应:Expires标头,其中指定了未来的日期和时间;或Cache-Control标头,其中的max-age指令设置为非零值。
默认情况下,NGINX会遵循Cache-Control标头中的其他指令:当该标头包含Private、No-Cache或No-Store指令时,NGINX不会缓存响应。NGINX也不会缓存包含Set-Cookie标头的响应。此外,NGINX仅缓存对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;
# ...
}对于Cache-Control下的所有内容,NGINX都会忽略/images/标头。proxy_cache_valid指令会强制为缓存数据设置过期时间;如果忽略Cache-Control标头,则必须使用该指令。NGINX不会缓存未设置过期时间的文件。
NGINX能否缓存标头中包含Set-Cookie的内容?
可以。如上一条回答所述,可使用proxy_ignore_headers指令。
NGINX能否缓存POST请求?
可以,使用“proxy_cache_methods”指令即可:
proxy_cache_methods GET HEAD POST;此示例启用对POST请求的缓存。
NGINX能否缓存动态内容?
可以,前提是Cache-Control标头允许缓存。即使只短暂缓存动态内容,也能减轻源服务器和数据库的负载。由于无需为每个请求重新生成页面,因此可以缩短首字节响应时间。
可以绕过缓存吗?
可以,使用proxy_cache_bypass指令即可:
location / {
proxy_cache_bypass $cookie_nocache $arg_nocache;
# ...
}该指令定义了以下请求类型:对于此类请求,NGINX会立即从源服务器请求内容,而不是先尝试在缓存中查找。这有时称为在缓存中“打孔”。在此示例中,NGINX会对包含nocache Cookie或参数的请求执行此操作,例如http://www.example.com/?nocache=true。NGINX仍可缓存由此产生的响应,供以后未绕过缓存的请求使用。
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相同但JSESSIONID值不同的项目会作为唯一项目分别缓存。
NGINX 是否使用ETag标头?
从NGINX 1.7.3和NGINX Plus R5开始的后续版本均完全支持ETag标头以及If-None-Match。
NGINX如何处理字节范围请求?
如果缓存中的文件是最新的,NGINX会处理字节范围请求,仅向客户端提供该项目中指定的字节。如果文件未缓存或已过期,NGINX会从源服务器下载整个文件。如果请求指定单个字节范围,NGINX会在下载流中读取到该范围后立即将其发送给客户端。如果请求指定同一文件中的多个字节范围,NGINX会在下载完成后将整个文件发送给客户端。
下载完成后,NGINX会将整个资源移入缓存,以便后续所有字节范围请求(无论是单范围请求还是多范围请求)都能立即从缓存中得到满足。
请注意,upstream服务器必须支持字节范围请求,NGINX才能处理向该upstream服务器发出的字节范围请求。
NGINX 是否支持缓存清除?
NGINX Plus支持选择性清除缓存文件。如果文件已在源服务器上更新,但在NGINX Plus缓存中仍然有效(Cache-Control:max-age仍然有效,并且inactive参数为proxy_cache_path指令设置的超时时间尚未到期),此功能会很有用。借助NGINX Plus的缓存清除功能,可以轻松删除该文件。有关更多详细信息,请参阅从缓存中清除内容。
NGINX如何处理Pragma标头?
客户端添加Pragma:no-cache标头后,可以绕过所有中间缓存,直接从源服务器获取所请求的内容。NGINX默认不处理Pragma标头,但可以使用以下proxy_cache_bypass指令配置此功能:
location /images/ {
proxy_cache my_cache;
proxy_cache_bypass $http_pragma;
# ...
}NGINX 是否支持 stale-while-revalidate 和 stale-if-error 这两项 Cache-Control 标头扩展?
是的,NGINX Plus R12和NGINX 1.11.10及更高版本均支持。这些扩展的作用如下:
- The
stale-while-revalidate是Cache-ControlHTTP标头的扩展,允许在缓存响应当前正在更新时使用其过期版本。 - HTTP标头
stale-if-error是Cache-Control的扩展,允许在发生错误时使用过期的缓存响应。
这些标头的优先级低于上述proxy_cache_use_stale指令。
NGINX是否支持Vary标头?
是的,NGINX Plus R5和 NGINX 1.7.7 及更高版本均支持。以下是关于 Vary 标头的详细概述。
延伸阅读
您还可以通过多种方式自定义和调优NGINX缓存。要进一步了解如何使用NGINX进行缓存,请参阅以下资源:
- The ngx_http_proxy_module参考文档包含内容缓存的所有配置选项。
- 有两场网络研讨会可供点播,其中详细介绍了本博文中的大部分内容:使用NGINX Plus进行内容缓存和使用NGINX实现高可用性内容缓存。
- 《NGINX Plus 管理指南》提供了更多配置示例,以及有关调优NGINX缓存的信息。
- The NGINX Plus缓存产品页面概述了如何使用NGINX Plus配置缓存清除,并提供了其他缓存自定义示例。
- 《使用NGINX和NGINX Plus实现高性能缓存》电子书深入详解了NGINX内容缓存。
关于作者
相关博客文章
NGINX Plus R26 正式发布
NGINX Plus R26通过缓存JSON Web密钥集加快JWT验证,并强化TLS握手,从而提升安全性。NGINX JavaScript的新功能包括增强对异步函数的支持,以及实现WebCrypto API。
使用NGINX App Protect DoS保护gRPC应用,抵御严重的DoS攻击
gRPC具备开发人员设计和部署现代应用所需的速度与灵活性,但其开放性也使其极易受到第7层DoS攻击。NGINX App Protect DoS可保护gRPC应用免受此类破坏性攻击。
Ingress Controller 选型指南(第 2 部分):风险与面向未来的规划
了解选择错误的Ingress控制器可能带来的风险,以及如何从关键方面着手,让您的选择更好地适应未来需求。
NGINX Plus R19 正式发布
NGINX Plus R19扩展了监控功能,不仅提供更多指标,还新增了指标分析方式;增加试运行模式,用于测试限速效果;增强键值存储,以支持IP地址范围;并使限速更加动态。
NGINX现已正式加入F5
NGINX现已正式成为F5的一部分。Gus Robertson现任F5 NGINX业务部门负责人。他阐述了两家公司在人才、文化和愿景方面融合的价值,以及双方将如何携手增强NGINX的开源产品和商业产品。

