使用nginx缓存服务器上的静态文件

一、nginx缓存的优点


如图所示,nginx缓存,可以在一定程度上,减少源服务器的处理请求压力。
因为静态文件(比如css,js, 图片)中,很多都是不经常更新的。
nginx使用proxy_cache将用户的请求缓存到本地一个目录。下一个相同请求可以直接调取缓存文件,就不用去请求服务器了。
毕竟,IO密集型服务的处理是nginx的强项。

二、如何进行设置

先上个栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
http{
proxy_connect_timeout 10;
proxy_read_timeout 180;
proxy_send_timeout 5;
proxy_buffer_size 16k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 96k;
proxy_temp_file_write_size 96k;
proxy_temp_path /tmp/temp_dir;
proxy_cache_path /tmp/cache levels=1:2 keys_zone=cache_one:100m inactive=1d max_size=10g;


server {
listen 80 default_server;
server_name localhost;
root /mnt/blog/;

location / {

}

#要缓存文件的后缀,可以在以下设置。
location ~ .*\.(gif|jpg|png|css|js)(.*) {
proxy_pass http://ip地址:90;
proxy_redirect off;
proxy_set_header Host $host;
proxy_cache cache_one;
proxy_cache_valid 200 302 24h;
proxy_cache_valid 301 30d;
proxy_cache_valid any 5m;
expires 90d;
add_header wall "hey!guys!give me a star.";
}
}

# 无nginx缓存的blog端口
server {
listen 90;
server_name localhost;
root /mnt/blog/;

location / {

}
}
}

因为我是在一台服务器上做试验(敲重点,做试验),所以用了两个端口80和90进行模拟两台服务器之间的交互。

80端口对接的是普通的域名(http://wangxiaokai.vip)访问。
90端口负责处理80端口代理过来的资源访问。
相当于90端口是源服务器,80端口是nginx反向缓存代理服务器。

接下来讲一下配置项:

2.1 http层设置

1
2
3
4
5
6
7
8
9
proxy_connect_timeout 10;
proxy_read_timeout 180;
proxy_send_timeout 5;
proxy_buffer_size 16k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 96k;
proxy_temp_file_write_size 96k;
proxy_temp_path /tmp/temp_dir;
proxy_cache_path /tmp/cache levels=1:2 keys_zone=cache_one:100m inactive=1d max_size=10g;
  • proxy_connect_timeout 服务器连接的超时时间
  • proxy_read_timeout 连接成功后,等候后端服务器响应时间
  • proxy_send_timeout 后端服务器数据回传时间
  • proxy_buffer_size 缓冲区的大小
  • proxy_buffers 每个连接设置缓冲区的数量为number,每块缓冲区的大小为size
  • proxy_busy_buffers_size 开启缓冲响应的功能以后,在没有读到全部响应的情况下,写缓冲到达一定大小时,nginx一定会向客户端发送响应,直到缓冲小于此值。
  • proxy_temp_file_write_size 设置nginx每次写数据到临时文件的size(大小)限制
  • proxy_temp_path 从后端服务器接收的临时文件的存放路径
  • proxy_cache_path 设置缓存的路径和其他参数。被缓存的数据如果在inactive参数(当前为1天)指定的时间内未被访问,就会被从缓存中移除

2.2 server层设置

2.2.1 反向缓存代理服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
server {
listen 80 default_server;
server_name localhost;
root /mnt/blog/;

location / {

}

#要缓存文件的后缀,可以在以下设置。
location ~ .*\.(gif|jpg|png|css|js)(.*) {
proxy_pass http://ip地址:90;
proxy_redirect off;
proxy_set_header Host $host;
proxy_cache cache_one;
proxy_cache_valid 200 302 24h;
proxy_cache_valid 301 30d;
proxy_cache_valid any 5m;
expires 90d;
add_header wall "hey!guys!give me a star.";
}
}
  • proxy_pass nginx缓存里拿不到资源,向该地址转发请求,拿到新的资源,并进行缓存
  • proxy_redirect 设置后端服务器“Location”响应头和“Refresh”响应头的替换文本
  • proxy_set_header 允许重新定义或者添加发往后端服务器的请求头
  • proxy_cache 指定用于页面缓存的共享内存,对应http层设置的keys_zone
  • proxy_cache_valid 为不同的响应状态码设置不同的缓存时间
  • expires 缓存时间

这里我设置了图片、css、js静态资源进行缓存。
当用户输入http://wangxiaokai.vip域名时,解析得到ip:port的访问地址。port默认为80。所以页面请求会被当前server截取到,进行请求处理。
当解析到上述文件名结尾的静态资源,会到缓存区获取静态资源。
如果获取到对应资源,则直接返回数据。
如果获取不到,则将请求转发给proxy_pass指向的地址进行处理。

2.2.2 源服务器

1
2
3
4
5
6
7
8
9
server {
listen 90;
server_name localhost;
root /mnt/blog/;

location / {

}
}

这里直接处理90端口接受到的请求,到服务器本地目录/mnt/blog下抓取资源进行响应。

三、如何验证缓存是否有效

细心的读者应该发现,我在第二段的栗子里,留了个彩蛋 add_header wall "hey!guys!give me a star."
add_header是用于在报头设置自定义的信息。
所以,如果缓存有效的话,那么静态资源返回的报头,一定会带上这个信息。
访问http://wangxiaokai.vip结果如下:

四、参考
1、nginx文档
2、nginx反向缓存代理详解
3、Nginx缓存服务器静态文件


Nginx缓存配置inactive time与proxy_cache_valid time的关系

Nginx缓存配置缓存时有两个与时间相关的参数,inactive和proxy_cache_valid,他们之间的关系如何?

inactive

inactive指定了一个时间长度,在这个时间内缓存未被访问,则将从缓存中删除。该参数声明了缓存数据被保存的时间。

proxy_cache_valid

缓存有效时间。

之间的关系?

inactive声明的是缓存数据的保存时间, proxy_cache_valid 声明的是缓存有效时间。

假设有资源:http://localhost/site.js,做如下测试:

测试一:
inactive 2m, proxy_cache_valid 1m
第一次访问该资源,cache status返回 MISS。
过了1分钟但少于2分钟之内再次访问该资源,此时缓存数据还存在(没有超过inactive指定的时间),但是缓存已经失效(超过proxy_cache_valid指定的时间),cache status返回EXPIRED,此时proxy会将请求发送给后台服务器获取新的内容,并更新缓存数据

测试二:
inactive 1m, proxy_cache_valid 2m
第一次访问该资源,cache status返回 MISS。
过了1分钟但少于2分钟之内再次访问该资源,此时缓存数据不存在(超过inactive指定的时间),缓存还没有失效(没有超过proxy_cache_valid指定的时间),但是从缓存数据中找不到对应的key,cache status返回MISS,此时proxy会将请求发送给后台服务器获取新的内容,并更新缓存数据

总结

缓存是否命中,取决于 inactive 和 proxy_cache_valid 中设定的最小值。


如果要删除缓存中的某张图片的话,需要使用proxy_cache_purge模块,此模块只在NGINX 1.15.7以上版本支持,而且是NGINX PLUS版本提供,要收费。

另外还有一个免费的第三方模块ngx_cache_purge,不过已经停止维护了,而且需要手动编译安装,没这方面的经验。

在缓存目录里找到每个图片对应的缓存文件,手工删除也是一个办法。但是缓存文件全都是32位的字符串,要删除对应某一文件的缓存,就要搞清楚文件缓存的命名规则。

先看下proxy_cache_path的常见配置:
proxy_cache_path /usr/local/nginx/cache levels=1:2 keys_zone=one:10m max_size=1g;

在上面这行配置中定义了一个反向代理缓存路径:

  1. nginx反向代理缓存的目录为/usr/local/nginx/cache
  2. 缓存文件的key和其它信息放在一个10M的共享内存中,命名为one;
  3. 缓存文件最大占用1G磁盘空间;
    那还有一个level=1:2是什么意思呢?举个例子吧:
    比如有一个URL是http://netexr.blog.51cto.com/1.png,那么这个图片如果被缓存那他的路径就是:
    /usr/local/nginx/cache/9/ad/e0bd86606797639426a92306b1b98ad9

计算方法:

  1. nginx先把请求地址/1.png用md5进行哈希,得到e0bd86606797639426a92306b1b98ad9
  2. level=1:2就是把最后一位数9拿出来建一个目录,然后再把9前面的2位建一个目录,最后把刚才得到的这个缓存文件放到9/ad目录中。
    同样的方法推理,如果配置level=1:1,那么缓存文件的路径就是/usr/local/nginx/cache/9/d/e0bd86606797639426a92306b1b98ad9

上面的例子只是最简单的URL,如果带参数呢?
比如http://netexr.blog.51cto.com/1.png?v=1,那么缓存路径还是一样吗?

先对比下这个两个配置:

1
2
proxy_cache_key $uri;
proxy_cache_key $uri$is_args$args;

第一个配置只根据不带参的$uri进行哈希,所以这时候加了参数和没加参数是一样的结果;
第二个配置就是把域名之后所有的内容(也就是$request_uri)都进行哈希。

proxy_cache_key默认值: $scheme$proxy_host$request_uri;
这条指令的默认值等同于:$scheme$proxy_host$uri$is_args$args;
可以在设置缓存的时候自定义,比如:proxy_cache_key "$host$request_uri $cookie_user";

nginx内置变量使用说明及具体意义
Nginx中文文档

知道了缓存路径的计算方法,我们就可以进行缓存清理了(以level=1:2为例):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/env php
<?php
$cache_dir = '/usr/local/nginx/cache/';
$request_uri = $argv[1];
$url_hash = md5($request_uri);
$dir1 = substr($url_hash,-1,1) . '/';
$dir2 = substr($url_hash,-3,2) . '/';
$cached_file = $cache_dir . $dir1 . $dir2 . $url_hash;
if (is_file($cached_file)) {
if (unlink($cache_dir . $dir1 . $dir2 . $url_hash)) {
echo $request_uri . " 缓存清除成功\n";
} else {
echo $request_uri . " 缓存清除失败\n";
}
} else {
echo $request_uri . " 未被缓存\n";
}

python3版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import hashlib,os

cache_dir = '/tmp/cache/'
while 1:
file_name = input("请输入要删除的文件名(退出-exit):")
if file_name == 'exit':
break
m = hashlib.md5(file_name.encode(encoding='utf-8'))
file_hash = m.hexdigest()
dir1 = file_hash[-1]
dir2 = file_hash[-3:-1]
cached_file = '{}{}/{}/{}'.format(cache_dir,dir1,dir2,file_hash)
if os.path.exists(cached_file):
os.remove(cached_file)
print('删除成功')
else:
print('文件不存在')

说明:

  1. MD5哈希过之后的路径是十六进制的,对于nignx来说查询速度更快;
  2. level=1:2会比level=1:1建立更多的目录,适合缓存海量文件,因为单个目录下的文件太多会降低IO性能;
  3. 缓存会先被写入写入临时文件,所以建议proxy_cache_path和proxy_temp_path放在同一个文件系统当中,避免不通文件系统之间的磁盘IO消耗;
  4. 有时候缓存图片使用了服务商的对象存储功能,可以在读取图片的时候设置图片的样式,比如http://a.com/test/a.jpg,对图片压缩只需要改成http://a.com/test/a.jpg@!ios,如果有针对苹果和安卓的网站设置了不同的图片处理样式,比如http://a.com/test/a.jpg@!ioshttp://a.com/test/a.jpg@!android,那么实际上是保存了两份缓存,都要删除掉。或者在设置proxy_cache_key的时候,只保留@之前的部分。(具体操作见本网站的文章使用正则表达式来截取nginx中的内置变量