我们都知道应用程序和网站的性能是他们成功的关键因素。但是应用程序或网站表现更好的过程并不总是很清楚。代码质量和基础架构当然至关重要,但在许多情况下,可以通过专注于一些非常基本的应用程序的交付技术,对应用程序的最终用户体验进行大量改进。
其中一个例子是在应用程序栈中实现和优化缓存。在教程中介绍的技术可以帮助新手和高级用户使用 Nginx 中包含的内容缓存功能,从而获得更好的性能。
概览
内容缓存位于客户端和源服务器 (upstream) 之间,并保存它看到的所有内容的副本。如果客户端请求缓存已存储的内容,则它会直接返回内容而不连接源服务器。这提高了性能,因为内容缓存更靠近客户端,并且更有效地使用应用程序服务器,因为它们不必每次都从头开始生成页面。
Web 浏览器和应用程序服务器之间可能存在多个缓存:客户端的浏览器缓存,中间缓存,内容交付网络(CDN)以及位于应用程序服务器前面的负载平衡器或反向代理。即使在反向代理/负载均衡器级别,缓存也可以极大地提高性能。
这里举一个例子,比如我的站点使用 Next.js 服务器端口渲染,由于服务器性能比较差,当然 $5 的服务器,并不能期望好到那里去,能用就已经非常了不起,能进入这个局域网就好了,别期望太多。
每次打开页面将近花费 7 秒左右,当这其中包含网络延迟,但当我直接在服务器端(127.0.0.1) 发起请求时,时间接近 5 秒,然后再排除从数据库获取数据时间,服务器端渲染时间用了 4.5 秒,实在太慢,此时我能想到最快解决问题答案就是缓存,但在那里加入缓存,从每一步时间看来,在 Nginx 加入缓存最快解决问题
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;
}
}
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 千兆字节)。它是可选的; 不指定值允许缓存增长以使用所有可用磁盘空间。当缓存大小达到限制时,一个称为缓存管理器的进程将删除最近最少使用的缓存,将大小恢复到限制之下的文件。
-
inactive 指定项目在未被访问的情况下可以保留在缓存中的时间长度。在此示例中,缓存管理器进程会自动从缓存中删除 60 分钟未请求的文件,无论其是否已过期。默认值为 10 分钟(10m)。非活动内容与过期内容不同。Nginx 不会自动删除缓存 header 定义为已过期内容(例如 Cache-Control:max-age=120)。过期(陈旧)内容仅在指定时间内未被访问时被删除。访问过期内容时,Nginx 会从原始服务器刷新它并重置 inactive 计时器。
Nginx 首先将发往高速缓存的文件写入临时存储区域,use_temp_path=off 指令指示 NGINX 将它们写入将被高速缓存的相同目录。我们建议您将此参数设置 off 为避免在文件系统之间进行不必要的数据复制。use_temp_path 在 Nginx 1.7.10 中引入。
最后,该 proxy_cache 指令激活与父 location 块的 URL 匹配的所有内容的缓存(在示例中为/)。您还可以在 server 块中包含 proxy_cache 指令; 它适用于没有自己的 location 指令的服务器的所有块。
当上游服务器关闭()时提供缓存内容
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 具有丰富的可选设置,可用于微调缓存的性能。这是一个激活其中一些的例子:
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 字段包含在 GET 请求的标头中将它发送到源服务器。因为服务器只有在 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 硬盘设置可以处理硬盘故障。
如何对 Nginx Cache 进行检测
可以在响应头中加入 $upstream_cache_status 变量以进行检测
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 尊重 Cache-Control 源服务器的标头。它不缓存响应 Cache-Control 设置为 Private,No-Cache 或 No-Store 或 Set-Cookie 在响应头。Nginx 只缓存 GET 和 HEAD 客户端请求。您可以按照以下答案中的说明覆盖这些默认值。
如果 proxy_buffering 设置为 off,Nginx 不会缓存响应。on 默认的。
Nginx 是否可以忽略 Cache-Control
使用 proxy_ignore_headers 指令可以忽略 Cache-Control
location /images/ {
proxy_cache my_cache;
proxy_ignore_headers Cache-Control;
proxy_cache_valid any 30m;
# ...
}
Nginx 忽略 /images/ Cache-Control 下所有内容的标题。该指令强制缓存数据到期,如果忽略标头则需要。Nginx 不会缓存没有过期的文件。
Nginx 是否可以忽略 Set-Cookie
使用 proxy_ignore_headers 指令即可。
Nginx 如何缓存 POST 请求
使用 proxy_cache_methods 指令:
proxy_cache_methods GET HEAD POST;
此示例启用了POST请求的缓存。
Nginx 如何缓存动态内容
只要 Cache-Control 标头允许。即使在很短的时间内缓存动态内容也可以减少原始服务器和数据库的负载,从而缩短第一个字节的时间,因为不必为每个请求重新生成页面。
如何不使用 Nginx 缓存
proxy_cache_bypass 指令
location / {
proxy_cache_bypass $cookie_nocache $arg_nocache;
# ...
}
该指令定义了 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 及更高版本中,ETag 标头完全支持 If-None-Match。
Nginx 如何处理字节范围请求
如果文件在高速缓存中是最新的,则 Nginx 遵循字节范围请求并仅向项目客户端提供项目的指定字节。如果文件未缓存,或者文件过时,Nginx 会从原始服务器下载整个文件。
如果请求是针对单个字节范围的,则 Nginx 会在下载流中遇到该范围后立即将该范围发送到客户端。如果请求在同一文件中指定了多个字节范围,则 Nginx 会在下载完成时将整个文件传送到客户端。
下载完成后,Nginx 会将整个资源移动到缓存中,以便从缓存中立即满足所有未来的字节范围请求,无论是单个范围还是多个范围。
请注意,upstream 服务器必须支持 Nginx 的字节范围请求,以支持对该 upstream 服务器的字节范围请求。
Nginx 如何处理 Pragma 标头
在 Pragma:no-cache 报头由客户加入到绕过所有中间缓存,直接进入到源服务器的请求的内容。Pragma 默认情况下,Nginx 不支持标头,但您可以使用以下 proxy_cache_bypass 指令配置该功能:
location /images/ {
proxy_cache my_cache;
proxy_cache_bypass $http_pragma;
# ...
}
Nginx 是否支持标头 stale-while-revalidate 和 stale-if-error 以及扩展的 Cache-Control
Nginx 1.11.10 及更高版本中支持。这些扩展做了什么:
如果当前正在更新 stale-while-revalidate,Cache-Control HTTP 标头的扩展允许使用陈旧的缓存响应。HTTP 标头的 stale-if-error 扩展 Cache-Control 允许在发生错误时使用陈旧的缓存响应。这些头具有比较低优先级, proxy_cache_use_stale 指令如上所述。
Nginx 是否支持 Vary 标头
Nginx 1.7.7 以及更高版本中是支持 Vary 标头的 。
结论
至此,您应该很好地理解 Nginx 代理缓存的工作原理以及如何正确配置 Nginx 代理缓存。如果您有任何问题或反馈,请随时发表评论。