博客 | NGINX

NGINX 和 NGINX Plus 缓存指南

NGINX-F5-horiz-black-type-RGB 的一部分
Faisal Memon 缩略图
费萨尔·梅蒙
2015 年 7 月 23 日发布

我们都知道,应用和网站的性能是其成功的关键因素。 然而,使您的应用或网站性能更好的过程并不总是很清楚。 代码质量和基础设施当然至关重要,但在很多情况下,您可以通过关注一些非常基本的应用交付技术来极大地改善应用的最终用户体验。 一个这样的例子是通过在应用堆栈中实现和优化缓存。 这篇博文介绍了一些技术,可以帮助新手和高级用户利用 NGINX 和 NGINX Plus 中包含的内容缓存功能获得更好的性能。

概述

内容缓存位于客户端和“原始服务器”之间,并保存其看到的所有内容的副本。 如果客户端请求缓存已存储的内容,它将直接返回内容而无需联系原始服务器。 由于内容缓存更靠近客户端,因此这可以提高性能,并且更有效地使用应用服务器,因为它们不必每次都从头开始生成页面。

Web 浏览器和应用服务器之间可能存在多个缓存:客户端的浏览器缓存、中间缓存、内容分发网络 (CDN) 以及位于应用服务器前面的负载均衡器或反向代理。 即使在反向代理/负载均衡器级别,缓存也可以大大提高性能。

举个例子,去年我承担了一个对加载缓慢的网站进行性能调整的任务。 我注意到的第一件事是生成主页花了 1 秒多的时间。 经过一些调试后,我发现由于该页面被标记为不可缓存,因此它会根据每个请求动态生成。 页面本身变化不频繁,也没有个性化,所以没有必要。 作为一项实验,我将主页标记为由负载均衡器缓存 5 秒,这样做就带来了明显的改善。 第一个字节的时间减少到几毫秒,页面加载速度明显更快。

NGINX 通常部署为应用堆栈中的反向代理或负载均衡器,并具有一整套缓存功能。 下一节讨论如何使用 NGINX 配置基本缓存。

如何设置和配置基本缓存

只需要两个指令就可以启用基本缓存: proxy_cache_pathproxy_cacheproxy_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; 服务器 { # ... 位置 / { proxy_cache my_cache; proxy_pass http://my_upstream; } }

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 会从原始服务器刷新它并重置非活动计时器。
  • NGINX 首先将要缓存的文件写入临时存储区域,然后use_temp_path=off指令指示 NGINX 将它们写入将要缓存的相同目录。 我们建议您将此参数设置为off ,以避免在文件系统之间不必要的数据复制。 use_temp_path是在 NGINX 版本 1.7.10 和NGINX Plus R6中引入的。

最后, proxy_cache指令激活与父位置块的 URL 匹配的所有内容的缓存(在示例中为/ )。 您还可以在服务器块中包含proxy_cache指令;它适用于没有自己的proxy_cache指令的服务器的所有位置块。

当源服务器宕机时提供缓存内容

NGINX内容缓存的一个强大功能是,当 NGINX 无法从原始服务器获取新内容时,可以将其配置为从其缓存中提供陈旧内容。 如果缓存资源的所有原始服务器都关闭或暂时繁忙,就会发生这种情况。 NGINX 不会将错误转发给客户端,而是从其缓存中提供文件的过时版本。 这为 NGINX 代理的服务器提供了额外的容错级别,并确保了服务器故障或流量高峰时的正常运行。 要启用此功能,请包含proxy_cache_use_stale指令:

位置 / { #... proxy_cache_use_stale错误超时 http_500 http_502 http_503 http_504; }

使用此示例配置,如果 NGINX 从原始服务器收到错误超时或任何指定的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错误超时更新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。
  • 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_hdd1my_cache_hdd2 )。 split_clients配置块指定一半请求的结果( 50% )缓存在my_cache_hdd1中,另一半缓存在my_cache_hdd2中。 基于$request_uri变量(请求 URI)的哈希确定每个请求使用哪个缓存,结果是针对给定 URI 的请求始终缓存在同一个缓存中。

请注意,此方法不能替代 RAID 硬盘设置。 如果硬盘出现故障,可能会导致系统出现不可预测的行为,包括用户看到针对故障硬盘的请求的 500 响应代码。 适当的 RAID 硬盘设置可以处理硬盘故障。

常见问题 (FAQ)

本节回答有关 NGINX 内容缓存的一些常见问题。

NGINX 缓存可以被检测吗?

是的,使用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 更新
  • REVALIDATED —— proxy_cache_revalidate指令已启用,并且 NGINX 验证了当前缓存的内容仍然有效( If-Modified-SinceIf-None-Match )。
  • 命中——响应包含直接来自缓存的有效、新鲜的内容。

NGINX 如何确定是否缓存某些内容?

仅当原始服务器包含带有未来日期和时间的Expires标头,或将max-age指令设置为非零值的Cache-Control标头时,NGINX 才会缓存响应。

默认情况下,NGINX 尊重Cache-Control标头中的其他指令:当标头包含PrivateNo-CacheNo-Store指令时,它不会缓存响应。 它也不会缓存带有Set-Cookie标头的响应。 此外,它只缓存对GETHEAD请求的响应。 您可以按照下面的答案所述覆盖这些默认值。

如果proxy_buffering设置为off ,那么 NGINX 不会缓存响应。 默认情况下它是开启的

可以忽略Cache-Control标头吗?

是的,使用proxy_ignore_headers指令。 例如,使用以下配置:

位置 /images/ { proxy_cache my_cache; proxy_ignore_headers Cache-Control; proxy_cache_valid any 30m; # ... }

NGINX 忽略/images/下的所有内容的Cache-Control标头。 proxy_cache_valid指令强制缓存数据的过期,如果忽略Cache-Control标头则需要该指令。 NGINX 不会缓存没有过期的文件。

NGINX 可以使用标头中的Set-Cookie来缓存内容吗?

是的,使用proxy_ignore_headers指令,如前面的答案所讨论的那样。

NGINX 可以缓存POST请求吗?

是的,使用proxy_cache_methods指令:

proxy_cache_methods获取头部信息;

此示例启用了POST请求的缓存。

NGINX 可以缓存动态内容吗?

是的,只要Cache-Control标头允许。 即使只是短时间缓存动态内容也可以减少原始服务器和数据库的负载,从而缩短第一个字节的传输时间,因为不需要为每个请求重新生成页面。

我可以在我的缓存上打个洞吗?

是的,使用proxy_cache_bypass指令:

位置 / { 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;

服务器 {
# ...
位置 / {
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 会将整个资源移入缓存,以便所有未来的字节范围请求(无论是单个范围还是多个范围)都可以立即从缓存中得到满足。

请注意,上游服务器必须支持 NGINX 的字节范围请求,才能满足该上游服务器的字节范围请求。

NGINX 是否支持缓存清除?

NGINX Plus支持选择性清除缓存文件。 如果文件已在原始服务器上更新但在 NGINX Plus 缓存中仍然有效( Cache-Control:max-age仍然有效并且proxy_cache_path指令的inactive参数设置的超时尚未过期),则此功能很有用。 使用 NGINX Plus 的缓存清除功能,可以轻松删除该文件。 有关更多详细信息,请参阅从缓存中清除内容

NGINX 如何处理Pragma Header?

客户端添加Pragma:no-cache标头以绕过所有中间缓存并直接转到原始服务器获取请求的内容。 NGINX 默认不支持Pragma标头,但您可以使用以下proxy_cache_bypass指令配置该功能:

位置 /图像/ { proxy_cache my_cache; proxy_cache_bypass $http_pragma; # ... }

NGINX 是否支持Cache-Control标头的stale-while-revalidatestale-if-error扩展?

是的,在NGINX Plus R12和 NGINX 1.11.10 及更高版本中。 这些扩展的作用:

  • 如果当前正在更新,则Cache-Control HTTP 标头的stale-while-revalidate扩展允许使用过时的缓存响应。
  • Cache-Control HTTP 标头的stale-if-error扩展允许在发生错误时使用过时的缓存响应。

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

NGINX 是否支持Vary Header?

是的,在NGINX Plus R5和 NGINX 1.7.7 及更高版本中。 以下是Vary标头的良好概述

进一步阅读

您可以使用更多方法来自定义和调整 NGINX 缓存。 要了解有关使用 NGINX 缓存的更多信息,请查看以下资源:


“这篇博文可能引用了不再可用和/或不再支持的产品。 有关 F5 NGINX 产品和解决方案的最新信息,请探索我们的NGINX 产品系列。 NGINX 现在是 F5 的一部分。 所有之前的 NGINX.com 链接都将重定向至 F5.com 上的类似 NGINX 内容。”