上次写了一篇数据库缓存,由于快餐式的风格,遭到了广大读友的吐槽。上篇风格有点“虚”,我本身是一个技术控,偏向经验/干货的分享,本文主要描述静态缓存方面的一些心得及分享。作为系列二,有所不足之处,依旧希望大家勇于“亮砖”。
说起静态缓存技术,CDN是金典代表之作。静态缓存技术面非常广,涉及的开源技术包含apache、Lighttpd、nginx、varnish、squid等。
静态缓存,一般指web类应用中,将图片、js、css、视频、html等静态文件/资源通过磁盘/内存等缓存方式,提高资源响应方式,减少服务器压力/资源开销的一门缓存技术。
本文中主要通过:浏览器缓存、磁盘缓存、内存缓存、nginx的内存缓存、CDN五个方面围绕静态缓存而展开。
一、浏览器缓存
浏览器缓存,也称为客户端缓存,是静态缓存中最常见最直接的表现形式,很多时候都往往被人忽略掉。
案例1:
我们经常在nginx的配置文件中看到以下缓存配置:
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
{
expires 1d;
}
location ~ .*\.(js|css)?$
{
expires 15d;
}
案例2:
在经常写jsp的时候,html标签中关于http头信息也可以注意到“expires”的字样:
<title>My JSP 'index.jsp' starting page</title>
<meta http-equiv="expires" content="60">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
对于案例1和案例2中(nginx设置的expires优先级大于代码中设置的expires优先级),expires是给一个资源设定一个过期时间,也就是说无需去服务端验证,直接通过浏览器自身确认是否过期即可,所以不会产生额外的流量。此种方法非常适合不经常变动的资源。如果文件变动较频繁,不要使用expires来缓存。
比如对于常见类web网站来说,css样式和js脚本基本已经定型,所以最适合的方法是expires来缓存一些内容到访问者浏览器。
案例3:
通过chrome访问服务器端的一张图片,通过F12键打开开发者前端调试工具(如图1):
第一次访问,响应200状态,当第二次及后续访问的时候,变成304状态(如图2),客户端已经开始取浏览器缓存内容,而不需要去服务器端获取对应的请求内容,即nginx中expires参数设置已经生效。等待客户端缓存时间过期后,会再次请求服务器端内容来更新本地缓存。
介绍到这里,突然想起一个有意思的需求。比如,访问一张静态文件,不想客户端缓存,需要每次都去服务器端取数据。我们可以用“last-modified”参数来实现,即“last-modified”是根据文件更新时间来确定是否再次发送加载。
Nginx核心配置如下:
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
{
add_header Last-modified 10;
expires 1d;
}
我们更改掉服务器传回客户端的“last-modified”文件修改时间参数的值,这样导致客户端本地保存的文件时间每次跟服务器端传回来的时间不一致,所以每次客户端“误认为”服务器端有静态文件更新,所以每次都会去服务器端取“所谓的最新数据”。这样我们可以看到,不管在浏览器访问多少次,返回的http状态都是200,再也找不到304状态了。
误区:在nginx中设置expires,并不是指把静态内容缓存在nginx中,而是设置客户端浏览器缓存的时间,这是很多人误区所在。
二、磁盘缓存
除了存储在客户端的静态缓存(浏览器静态)技术外,在服务器端的静态缓存技术主要分为磁盘缓存类和内存缓存类两大类。单纯围绕nginx的squid、varnish等一类中间件,处理静态数据的性能十分优秀。核心是nginx基于epoll网络模型,而相比apache基于select网络模型。所以apache的优势在于密计算型,稳定性好。而nginx偏向静态处理,反向代理,高并发。比如apache+php的稳定性比nginx+php要好,而性能是明显nginx要优秀许多。
以上仅单纯是对磁盘中静态数据处理的能力,所谓磁盘缓存,指另外的一种缓存静态文件的技术。以nginx配置为例:
#levels设置目录层次
#keys_zone设置缓存名字和共享内存大小
#inactive在指定时间内没人访问则被删除在这里是1天
#max_size最大缓存空间
proxy_cache_path /alidata/www/default/cache_dir/ levels=1:2 keys_zone=cache_one:200m inactive=1d max_size=30g;
server {
listen 82;
server_name _;
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|html)$
{
proxy_cache cache_one; # keys_zone后的内容对应
proxy_cache_valid 200 304 301 302 10d; #哪些状态缓存多长时间
proxy_cache_valid any 1d; #其他的缓存多长时间
proxy_cache_key $host$uri$is_args$args; #通过key来hash,定义KEY的值
proxy_pass http://10.168.247.180:81;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
access_log /alidata/nginx/logs/default-cache.log;
可以看出nginx主要通过proxy_cache来实现web cache,熟悉nginx的同学,不难看出,以上配置在location这里,不仅可以实现静态文件的缓存,还可以实现动态文件的缓存(这里放在下章节详细介绍)。我们编写个test.html测试文件,然后并访问。test.html源码如下:
<html>
<head>
<title>test cache</title>
<meta http-equiv="expires" content="-1">
</head>
<body>
<br>
![](eg_tulip.jpg)
</body>
</html>
我们发现服务器的cache目录里面,多了两个缓存文件:
/alidata/www/default/cache_dir/c/68/b0ad5d3e7f099bfff9e4fc6a159d868c
/alidata/www/default/cache_dir/f/fa/53edc39ed253e14415a29412cfc01faf
有意思的,这两个文件里面的内容分别为(通过less命令查看):
所以不难看出,nginx把html内容和图片二进制全部缓存到本地磁盘上了。下次用户再次来访问test.html的时候,nginx直接将缓存在本地磁盘的文件返回给用户。特别是后端如若是部署的tomcat、iis等,nginx强大的静态缓存能力,有效减少了服务器压力。
三、内存缓存
紧接上面描述的磁盘缓存,内存缓存顾名思义,就是把静态文件缓存在服务器端的内存中。所以这种缓存,如若命中缓存的话,取内存中的缓存数据返回比取磁盘中的缓存数据返回,性能要高很多。以varnish为例,varnish核心配置如下:
启动命令:
varnishd -f default.vcl -s malloc,2G -a 0.0.0.0:80 -w 1024,51200,10 -t 3600 -T 192.168.100.2:3500
参数简介:
-a address:port 监听端口
-f 指定配置文件
-s 指定缓存类型 malloc为内存, file 文件缓存
-t 默认TTL
-T address:port 管理端口
-w 最小线程,最大线程,超时时间
default.vcl核心配置如下:
sub vcl_fetch {
if (req.request == "GET" && req.url ~ "\.(gif|jpg|jpeg|png|bmp|swf|html)$") {
set obj.ttl = 3600s;
}
}
Varnish对.gif、.jpg、.jpeg、.png等结尾的URL缓存时间设置1小时。varnish设置完毕后,我们用命令行方式,通过查看网页头来查看命中情况:
[root@test-varnish ~]# curl -I http://***.***.***.***/test.html
HTTP/1.1 200 OK
Server: Apache/2.2.27 (Unix) PHP/5.3.18 mod_perl/2.0.4 Perl/v5.10.1
Last-Modified: Sat, 10 Jul 2016 11:25:15 GMT
ETag: "55dh9-233f-33d23c3758dg2"
Content-Type: text/html
Content-Length: 23423
Date: Fri, 18 May 2016 21:29:16 GMT
X-Varnish: 1364285597
Age: 0
Via: 1.1 varnish
Connection: keep-alive
X-Cache: MISS from ***.***.***.*** #这里的“MISS”表示此次访问没有从缓存读取
[root@test-varnish ~]# curl -I http://***.***.***.***/test.html
HTTP/1.1 200 OK
Server: Apache/2.2.27 (Unix) PHP/5.3.18 mod_perl/2.0.4 Perl/v5.10.1
Last-Modified: Sat, 10 Jul 2010 11:25:15 GMT
ETag: "55dh9-233f-33d23c3758dg2"
Content-Type: text/html
Content-Length: 23423
Date: Fri, 18 May 2016 21:40:23 GMT
X-Varnish: 1364398612 1364285597
Age: 79
Via: 1.1 varnish
Connection: keep-alive
X-Cache: HIT from ***.***.***.*** #由“HIT”可知,第二次访问此页面时,从缓存中读取内容,也就是缓存命中
最后,我们可以通过varnishadm命令来清理缓存,也可以通过varnishstat命令来查看varnish系统缓存状态。
四、Nginx的内存缓存
以上主要以Varnish为例,介绍了内存缓存静态资源的方法。其实nginx也有内存缓存,相比squid、varnish而言,nginx的内存缓存需要通过编码实现。如下配置:
location /images/ {
set $memcached_key $request_uri;
add_header X-mem-key $memcached_key;
memcached_pass 127.0.0.1:11211;
default_type text/html;
error_page 404 502 504 = @app;
}
memcached_pass指定服务器地址,使用变量$memcache_key为key查询值,去memcache查询对应value值。
如我们访问:http://.../image/test.jpg ,则nginx去memcache中查询key为“test.jpg”的value值并返回。如果没有相应的值,则返回error_page 404。介绍到这里,关键在于存储在memcache中的静态文件,需要通过代码写入memcache中。怎么样通过php/java等代码把静态资源的数据写入memcache中,关于这块的示例就不再过多介绍。
Nginx的内存缓存因为需要通过编码实现,所以灵活性特别高。这块可以结合自身业务系统的特点,让静态缓存的灵活性和效率都能得到保障。可能唯一的缺陷就是,通过编码实现的方式,给我们维护管理带来了负担。在之前我曾参与的一个电商系统,就是把客户的订单照片通过php代码写入memcache,客户访问取图的时候,从memcache中获取,速度效率特别高。Nginx作为一款在七层无所不能且轻量级高性能的中间件,能够直接去memcache中取数据,来实现静态缓存的效果,这块相应的功能是其他软件无法相媲美的。
五、CDN
说起CDN,大家都不陌生,它是静态缓存加速最典型的代表。CDN技术并不是一门新的技术,它是基于传统nginx、squid、varnish等web 缓存技术,结合DNS智能解析的静态缓存加速技术。值得注意的是,他对动态链接访问并没有加速效果。架构原理图如下图5:
所以CDN的静态缓存技术核心主要在于两点:
节点缓存:对需要加速的网站应用,相应的静态资源通过内存缓存+磁盘缓存的方式缓存在服务器端。
精准调度:对访问的用户ip进行智能解析调度,实现就近缓存节点访问。比如以上图例中,北京用户访问www.a.com。通过dns解析的时候,分析用户ip,发现是北京用户。则dns返回对应北京缓存节点的ip地址给到用户,则用户www.a.com,则默认访问北京服务器上面的缓存数据,实现就近访问的策略,大大提升了访问速度。
我为自己带盐,原创作者:乔锐杰