上次寫了一篇數據庫緩存,由于快餐式的風格,遭到了廣大讀友的吐槽。上篇風格有點“虛”,我本身是一個技術控,偏向經驗/干貨的分享,本文主要描述靜態緩存方面的一些心得及分享。作為系列二,有所不足之處,依舊希望大家勇于“亮磚”。
說起靜態緩存技術,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>

</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,則默認訪問北京服務器上面的緩存數據,實現就近訪問的策略,大大提升了訪問速度。
我為自己帶鹽,原創作者:喬銳杰