nginx proxy_cache

从nginx-0.7.44版开始,nginx支持了类似squid较为正规的cache功能,这个缓存是把链接用md5编码hash后保存,所以它可以支持任意链接,同时也支持404/301/302这样的非200状态。

 

开启缓存

使用 proxy_cache_path 配置缓存空间(必须放在 http 上下文的顶层位置),然后在目标上下文中使用 proxy_cache 指令。

proxy_cache_path /path/to/cache levels=1:2 keys_zone=NAME:10m inactive=5m max_size=10g clean_time=1m;

1、levels指定该缓存空间有两层hash目录,第一层目录是1个字母,第二层为2个字母,保存的文件名就会类似/path/to/cache/c/29/b7f54b2df7773722d382f4809d65029c;

2、keys_zone 设置一个共享内存区,该内存区用于存储缓存键和元数据,有些类似计时器的用途。将键的拷贝放入内存可以使NGINX在不检索磁盘的情况下快速决定一个请求是HIT还是MISS,这样大大提高了检索速度。一个1MB的内存空间可以存储大约8000个key,那么上面配置的10MB内存空间可以存储差不多80000个key。

3、inactive 指定项目在不被访问的情况下能够在内存中保持的时间。上述例子,如果一个文件在5分钟之内没有被请求,则缓存管理将会自动将其在内存中删除,不管该文件是否过期。该参数默认值为10分钟(10m)。注意,非活动内容有别于过期内容。NGINX不会自动删除由缓存控制头部指定的过期内容(例Cache-Control:max-age=120)。过期内容只有在inactive指定时间内没有被访问的情况下才会被删除。如果过期内容被访问了,那么NGINX就会将其从原服务器上刷新,并更新对应的inactive计时器。

4、max_size设置了缓存的上限(在上面的例子中是10G);这是一个可选项,如果不指定就允许缓存不断增长,占用所有可用的磁盘空间。当缓存达到这个上限,处理器便调用cache manager来移除最近最少被使用的文件,这样把缓存的空间降低至这个限制之下。

5、clean_time指定一分钟清理一次缓存。

6、NGINX最初会将注定写入缓存的文件先放入一个临时存储区域, use_temp_path=off命令指示NGINX将在缓存这些文件时将它们写入同一个目录下。强烈建议将参数设置为off来避免在文件系统中不必要的数据拷贝。use_temp_path在NGINX1.7版本中有所介绍

 

访问过期缓存

NGINX内容缓存的一个非常强大的特性是:当无法从原始服务器获取最新的内容时,NGINX可以分发缓存中的陈旧(stale,编者注:即过期内容)内容。这种情况一般发生在关联缓存内容的原始服务器宕机或者繁忙时。比起对客户端传达错误信息,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错误,并且在其缓存中有请求文件的陈旧版本,则会将这些陈旧版本的文件而不是错误信息发送给客户端。

 

配置后端服务器组:

upstream my_upstream {

server 192.168.61.1:9080max_fails=10 fail_timeout=10s weight=5;

}

 

配置例子:

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;
}
}

1、proxy_cache_revalidate指示NGINX在刷新来自服务器的内容时使用GET请求。如果客户端的请求项已经被缓存过了,但是在缓存控制头部中定义为过期,那么NGINX就会在GET请求中包含If-Modified-Since字段,发送至服务器端。这项配置可以节约带宽,因为对于NGINX已经缓存过的文件,服务器只会在该文件请求头中Last-Modified记录的时间内被修改时才将全部文件一起发送。

2、proxy_cache_min_uses设置了在NGINX缓存前,客户端请求一个条目的最短时间。当缓存不断被填满时,这项设置便十分有用,因为这确保了只有那些被经常访问的内容才会被添加到缓存中。该项默认值为1。

3、proxy_cache_use_stale中的updating参数告知NGINX在客户端请求的项目的更新正在原服务器中下载时发送旧内容,而不是向服务器转发重复的请求。第一个请求陈旧文件的用户不得不等待文件在原服务器中更新完毕。陈旧的文件会返回给随后的请求直到更新后的文件被全部下载。

4、当proxy_cache_lock被启用时,当多个客户端请求一个缓存中不存在的文件(或称之为一个MISS),只有这些请求中的第一个被允许发送至服务器。其他请求在第一个请求得到满意结果之后在缓存中得到文件。如果不启用proxy_cache_lock,则所有在缓存中找不到文件的请求都会直接与服务器通信。

 

指定哪些方法的请求被缓存

例如 proxy_cache_methods GET HEAD POST;

 

缓存跳过

例如 proxy_cache_bypass $cookie_nocache $arg_nocache$arg_comment;

如果任何一个参数值不为空,或者不等于0,nginx就不会查找缓存,直接进行代理转发

例子:

location / {
proxy_pass http://www.sudone.com/;

proxy_cache NAME;#使用NAME这个keys_zone

proxy_cache_valid 200 302 1h;#200和302状态码保存1小时
proxy_cache_valid 301 1d;#301状态码保存一天
proxy_cache_valid any 1m;#其它的保存一分钟
}

 

应用案例:

server {
listen 80;
server_name _;
server_tokens off;
location / {
proxy_pass http://127.0.0.1:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_cache my-cache;
proxy_cache_valid 3s;
proxy_no_cache $cookie_PHPSESSID;
proxy_cache_bypass $cookie_PHPSESSID;
proxy_cache_key "schemehost$request_uri";
add_header X-Cache $upstream_cache_status;
}
}

server {
listen 8080;
server_name _;
root /var/www/your_document_root/;
index index.php index.html index.htm;
server_tokens off;
location ~ \.php$ {
try_files $uri /index.php;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME documentroot fastcgi_script_name;
include /etc/nginx/fastcgi_params;
}
location ~ /\.ht {
deny all;
}
}

 

配置汇总:

1.add_header cache-status $upstream_cache_status在响应头中添加缓存命中的状态。

HIT:缓存命中,直接返回缓存中内容,不回源到后端。

MISS:缓存未命中,回源到后端获取最新的内容。

EXPIRED:缓存命中但过期了,回源到后端获取最新的内容。

UPDATING:缓存已过期但正在被别的Nginx Worker进程更新,配置了proxy_cache_use_stale updating指令时会存在该状态。

STALE:缓存已过期,但因后端服务出现了问题(比如后端服务挂了)返回过期的响应,配置了如proxy_cache_use_stale error timeout指令后会出现该状态。

REVALIDATED:启用proxy_cache_revalidate指令后,当缓存内容过期时,Nginx通过一次if-modified-since的请求头去验证缓存内容是否过期,此时会返回该状态。

BYPASS:proxy_cache_bypass指令有效时,强制回源到后端获取内容,即使已经缓存了。

4.proxy_cache_min_uses

用于控制请求多少次后响应才被缓存。默认“proxy_cache_min_uses1;”,如果缓存热点比较集中、存储有限,则可以通过修改该参数来来减少缓存数量和写磁盘次数。

5.proxy_no_cache

用于控制什么情况下响应不被缓存。比如配置“proxy_no_cache$args_nocache”,如果带的nocache参数值至少有一个不为空或者为0,则响应将不被缓存。

6.proxy_cache_bypass

类似于proxy_no_cache,但是,其控制什么情况不使用缓存的内容,而是直接到后端获取最新的内容。如果命中,则$upstream_cache_status为BYPASS。

7.proxy_cache_use_stale

当对缓存内容的过期时间不敏感,或者后端服务出问题时,即使缓存的内容不新鲜也总比返回错误给用户强(类似于托底),此时可以配置该参数,如“proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504”,即如果出现超时、后端连接出错、500、502、503等错误时,则即使缓存内容已过期也先返回给用户,此时$upstream_cache_status为STALE。还有一个updating表示缓存已过期但正在被别的Nginx Worker进程更新,但先返回了过期内容,此时$upstream_cache_status为UPDATING。

8.proxy_cache_revalidate

当缓存过期后,如果开启了proxy_cache_revalidate,则会发出一次if-modified-since或if-none-match条件请求,如果后端返回304,则此时$upstream_cache_status为REVALIDATED,我们将得到两个好处,节省带宽和减少写磁盘的次数。

9.proxy_cache_lock

当多个客户端同时请求同一份内容时,如果开启proxy_cache_lock(默认off),则只有一个请求被发送至后端。其他请求将等待该请求的返回。当第一个请求返回后,其他相同请求将从缓存中获取内容返回。当第一个请求超过了proxy_cache_lock_timeout超时时间(默认为5s),则其他请求将同时请求到后端来获取响应,且响应不会被缓存(在1.7.8版本之前是被缓存的)。启用proxy_cache_lock可以应对Dog-pile effect(当某个缓存失效时,同时有大量相同的请求没命中缓存,而同时请求到后端,从而导致后端压力太大,此时限制一个请求去拿即可)。

proxy_cache_lock_age是1.7.8新添加的,如果在proxy_cache_lock_age指定的时间内(默认为5s),最后一个发送到后端进行新缓存构建的请求还没有完成,则下一个请求将被发送到后端来构建缓存(因为1.7.8版本之后,proxy_cache_lock_timeout超时之后返回的内容是不缓存的,需要下一次请求来构建响应缓存)。

 

缓存清理

有时缓存的内容是错误的,需要手工清理,Nginx企业版提供了purger功能,对于社区版Nginx可以考虑使用ngx_cache_purge(https://github.com/FRiCKLE/ngx_cache_purge)模块进行清理缓存。

location ~ /purge(/.*) {

allow 127.0.0.1;

deny all;

proxy_cache_purge cache$1$is_args$args;

}

 

NGINX SQUID 分流:

bbs运行在缓存上,用户每发布一张帖子,都需要使用purge指令清除该帖子的缓存,如果是squid在最前端,那么每次发布一张帖子,都需要在所有的squid中调用purge指令,这样在机器比较多的时候,purge将成为一个巨大的压力。

所以在这里将Nginx squid架构放在最前端并使用手工url_hash的方式分流,将经常需要purge的帖子页面和列表页面按一个url对应一台squid的策略,分布到各台squid上,并提供了一台或一组backup的squid,个别squid出现异常时将自动使用backup的机器继续提供一段时间的服务直到其正常。在这样的架构下,purge就不再是关键问题,因为一个url只会对应到一台机器上,所以purge的时候,后端app_server找到对应的机器就可以了。

可以看到在前端中还有一台Nginx(purge)的机器,这台机器是专用于purge的,只要发送purge指令和需要清除的url到这台机器,就可以找到相应的服务器并清除缓存了。另外,purge时还需要清理backup机器上的缓存,所以无论前端机器增加到多少,purge指令只会在2台机器上执行,如果backup机器使用到2-3台,purge指令就会在3-4台机器上执行,仍然在可接受范围之内。

Nginx squid架构作为前端,另有的好处:

1/使用Nginx的日志统计点击量非常方便

 

 

 

聊聊:

 

一个很悲剧的事实
对动态网页使用CDN,无论squid还是varnish都不能直接用,都需定制代码。
例如varnish会判断response的header,如果发现里面有set-cookie项,它就认为这个页面不应该被缓存。对于规模庞大/OOP封装严密的网站,普通程序员根本意识不到调用哪一个fucntion会输出set-cookie,这个会导致CDN命中率急剧降低。但你也无力去对每行代码做code review,没有办法,只能去修改varnish代码了,这又引入一个新的维护成本. Squid也有这个问题

purge效率
purge就是CDN删除缓存项的接口,国内的UGC网站,因为严厉的内容检查制度和泛滥的垃圾广告,删帖子删图片特别频繁,某些网站可能高达40%(发100个贴,有40个帖子可能被删除或者修改),所以对purge的效率有要求。
squid和varnish的purge效率都达不到国内这种强度要求,nginx+memcache purge性能要好很多。
在当前的中国,遇到突发事件后,要是不及时删除指定的链接或内容,后果可能会很严重(小到个人被炒,大到公司被关都有可能)
某门户网站曾经发生过,某个链接怎么也删不掉,一慌张把CDN所有缓存都删了重启,导致内网流量瞬间暴涨,各业务线的服务器全线报警

 

..

此条目发表在web server分类目录,贴了标签。将固定链接加入收藏夹。