NGINX和NGINX Plus缓存指南

NGINX | 2015年7月23日

众所周知,应用和网站的性能是决定其能否成功的关键因素。然而,如何提升应用或网站的性能并不总是一目了然。代码质量和基础设施固然至关重要,但在许多情况下,只需重点采用一些非常基础的应用交付技术,就能显著改善应用的最终用户体验。例如,在应用堆栈中实施并优化缓存。本篇博客介绍了一些相关技术,无论是新手还是资深用户,都可以利用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-Control HTTP标头的扩展,允许在缓存响应当前正在更新时使用其过期版本。
  • HTTP标头stale-if-error是Cache-Control的扩展,允许在发生错误时使用过期的缓存响应。

这些标头的优先级低于上述proxy_cache_use_stale指令。

NGINX是否支持Vary标头?

是的,NGINX Plus R5和 NGINX 1.7.7 及更高版本均支持。以下是关于 Vary 标头的详细概述。

延伸阅读

您还可以通过多种方式自定义和调优NGINX缓存。要进一步了解如何使用NGINX进行缓存,请参阅以下资源:


分享

相关博客文章

NGINX Plus R26 正式发布
NGINX | 2022/02/15

NGINX Plus R26 正式发布

NGINX Plus R26通过缓存JSON Web密钥集加快JWT验证,并强化TLS握手,从而提升安全性。NGINX JavaScript的新功能包括增强对异步函数的支持,以及实现WebCrypto API。

使用NGINX App Protect DoS保护gRPC应用,抵御严重的DoS攻击
NGINX | 2022/02/10

使用NGINX App Protect DoS保护gRPC应用,抵御严重的DoS攻击

gRPC具备开发人员设计和部署现代应用所需的速度与灵活性,但其开放性也使其极易受到第7层DoS攻击。NGINX App Protect DoS可保护gRPC应用免受此类破坏性攻击。

Ingress Controller 选型指南(第 2 部分):风险与面向未来的规划
NGINX | 2021/09/13

Ingress Controller 选型指南(第 2 部分):风险与面向未来的规划

了解选择错误的Ingress控制器可能带来的风险,以及如何从关键方面着手,让您的选择更好地适应未来需求。

NGINX Plus R19 正式发布
NGINX | 2019/09/03

NGINX Plus R19 正式发布

NGINX Plus R19扩展了监控功能,不仅提供更多指标,还新增了指标分析方式;增加试运行模式,用于测试限速效果;增强键值存储,以支持IP地址范围;并使限速更加动态。

NGINX现已正式加入F5
NGINX | 2019/05/09

NGINX现已正式加入F5

NGINX现已正式成为F5的一部分。Gus Robertson现任F5 NGINX业务部门负责人。他阐述了两家公司在人才、文化和愿景方面融合的价值,以及双方将如何携手增强NGINX的开源产品和商业产品。

使用NGINX和NGINX Plus实现高性能缓存
NGINX | 2016/08/24

使用NGINX和NGINX Plus实现高性能缓存

我将介绍NGINX在内容缓存和性能方面的工作原理,并分享一些控制NGINX内容缓存方式的技巧。