[译]NGINX和NGINX Plus缓存指南

Published On February 13, 2017

category server | tags nginx cache cdn


CDN的核心就是一个NGINX服务器加上面的一对lua也写的业务代码,NGIN作为静态资源服务器非常适合用作缓存,通过简单的配置即可成为一个mini的CDN。本文翻译自A Guide to Caching with NGINX and NGINX Plus

翻译自:A Guide to Caching with NGINX and NGINX Plus

我们知道应用程序和网站的性能是成功的关键因素。然而,提升应用程序或网站性能的过程并不总是很清晰。代码质量和基础设施当然很关键,但很多情况你能通过关注一些基本的应用交付技术来极大得提升你的应用程序的用户体验。一个这种例子就是通过在你的应用程序堆栈中实现和优化缓存。这片博客涉及的技术能够帮助新手以及高级用户利用nginx中的web缓存功能来提升性能。

基础

web缓存位于客户端和“原始服务器”之间,保存它看到的内容的副本。如果一个客户端请求的内容已经存储在缓存中,缓存直接返回内容,并不和原始服务器联系。这样就提升了性能,因为web缓存服务器离客户端更近,并且省掉了每次都从头生成页面的工作所以更高效的利用应用服务器。 web浏览器和应用服务器之间可能存在多个缓存:客户端的浏览器缓存,中间缓存,内容交付网络(CDNs),和负载均衡器或位于应用服务器前面的反向代理。缓存,即使是在反向代理/负载均衡层面,也能极大得提升性能。 举个例子,去年我承担了一个任务,对一个加载缓慢的网站进行性能调优。我注意到的第一件事是首页要花1秒钟来生成。经过一番调试,我发现因为这个页面被标记为不能缓存,它对每个请求都是动态生成的。这个页面本身不会经常改变,而且不是定制化的,所以没有必要每次都重新生成。我做了一个实验,将首页标记为被负载均衡器缓存5秒,仅仅这点就带来了显著的提升。获取到第一个字节的时间下降到了几毫秒,页面的加载速度明显更快。 NGINX通常在应用程序堆栈中部署为反向代理或负载均衡器,它有一套完善的缓存功能。下一部份讨论如何在nginx中配置基本的缓存。

如何搭建和配置基本的缓存功能

要开启基本的缓存功能,只有两条指令是必须的:proxy_cache_pathproxy_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设置一个共享内存空间,用来存储缓存keys和使用次数等元数据。在内存中保存一份keys的副本使NGINX能够快速判断一个请求是HIT还是MISS,而不用访问磁盘,大大加快了检查。一个MB的空间能够存储8000个key的数据,所以例子中配置10MB的空间可以存储大约80000个key。 * max_size设置缓存大小的上限(这个例子中是10gb)。它是可选的;不指定一个值允许缓存增长以使用所有可用的磁盘空间。当缓存的大小达到了上限,一个叫做缓存管理器的进程会移除那些最近最少使用的文件,从而使缓存的大小不超过上限。 * 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(这个例子,/)的内容激活缓存。你可以在server块中包含proxy_cache指令;它作用于该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_lock on;

        proxy_pass http://my_upstream;
    }
}
这些指令配置以下行为: * proxy_cache_revalidate指示NGINX从源站刷新内容时使用有条件的GET请求。如果客户端了一个被缓存了但是根据缓存控制头判断为过期的项目时,NGINX在它发送给源站的GET请求头中加上If‑Modified‑Since field字段。这样可以节省带宽,因为只有在NGINX最初缓存某个文件所带的Last‑Modified头记录的时间以来源站修改了它才会发送完整的内容。 * proxy_cache_min_uses设置一个项目必须被客户端访问多少次后NGINX才缓存它。这个设置在缓存经常被填满的情况下很有用,因为它可以保证只有最频繁被访问的项目才会被加到内存。proxy_cache_min_uses默认被设置为1. * proxy_cache_use_stale指令的更新参数指示NGINX在客户端请求一个正在从源站下载更新的项目时提供陈旧的内容,而不是向服务器发起重复的请求。请求陈旧内容的第一个用户不得不等待从源站更新完。在更新被完全下载之前,陈旧的内容将直接返回给所有后续的请求。 * proxy_cache_lock开启后,如果多个客户端请求一个不在缓存(一个MISS)的文件时,只允许这些请求中的第一个到达源站,其余请求则等待,当第一个请求完成后再从缓存中取出文件。proxy_cache_lock没有开启时,所有导致在缓存未命中的请求都会直接到达源站。

将缓存拆分到多个硬盘驱动器

使用NGINX就没必要搭建一个RAID。如果有多个硬盘,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配置块指定了一半的请求结果缓存到my_cache_hdd1上,另一半缓存到my_cache_hdd2上。通过$request_uri变量(请求URI)计算出来的哈希值决定了每个请求应被缓存到哪个缓存里,结果就是对给定的URI的请求总是被缓存到同一个缓存里。

常问问题(FAQ)

这一章节回答一些关于NGINX内容缓存常问的问题。

NGINX缓存能被装配吗?

可以的,使用add_header指令:

add_header X-Cache-Status $upstream_cache_status;
这个例子在给客户端的响应中添加了一个X‑Cache‑Status头。下面是一些$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。

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头,则为必需。NGINX不会缓存没有到期时间的文件。

可以的,使用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使用什么cache key?

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的cache key类似md5(“http://my_upstream:80/my_image.jpg”)。 注意在哈希值中使用了$proxy_host变量而非真正的主机名(www.example.com)。$proxy_host被定义为proxy_pass指令指定的被代理服务器的名字和端口。 要更改生成cache key的变量(或其他术语),使用proxy_cache_key指令(见下面的问题)。

我能将Cookie作为cache key的一部分吗?

可以的,cache key可以被配置为使用任意值,例如:

proxy_cache_key $proxy_host$request_uri$cookie_jessionid;
该例将JSESSIONID cookie结合到cache key中。同一个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有了cache-purge功能后,这个文件可以很容易被删除。要了解详情,请看Purging Content from the Cache

NGINX是如何处理Pragma头的?

客户端通过添加Pragma:no‑cache头来绕过所有中间缓存,直接到达源站请求资源。NGINX默认不支持Pragma头,但是你可以使用下面的proxy_cache_bypass指令来配置这个功能:

location /images/ {
    proxy_cache my_cache;
    proxy_cache_bypass $http_pragma;
    ...
}

NGINX支持Vary头吗?

可以的,在NGINX Plus R5和NGINX 1.7.7以及更高的版本中支持。这里是一个很好的Vary头的概述

更多阅读

有很多的方法可以定制和调优NGINX缓存。要进一步学习使用NGINX来做缓存,请参考以下资源:


qq email facebook github
© 2018 - Xurui Yan. All rights reserved
Built using pelican