張開濤:Nginx HTTP緩存設(shè)置
Nginx提供了expires、etag、if-modified-since指令來實現(xiàn)瀏覽器緩存控制。
一、expires
如果我們使用Nginx作為靜態(tài)資源服務(wù)器,那么可以使用expires進(jìn)行緩存控制。
- location /img {
- alias /export/img/;
- expires 1d;
- }
當(dāng)我們訪問靜態(tài)資源時,如http://192.168.61.129/img/1.jpg,將得到類似如下的響應(yīng)頭。
對于靜態(tài)資源會自動添加ETag,可以通過添加etag off指令禁止生成ETag。如果是靜態(tài)文件,那么Last-Modified值為文件的***修改時間。Expires是根據(jù)當(dāng)前服務(wù)器系統(tǒng)時間算出來的。如上Nginx配置的計算邏輯(實際計算邏輯比這個多,具體參考官方文檔)。
- if (expires == NGX_HTTP_EXPIRES_ACCESS||r->headers_out.last_modified_ time == -1) {
- max_age = expires_time;
- expires_time += now;
- }
二、if-modified-since
此指令用于指定Nginx如何拿服務(wù)端的Last-Modified和瀏覽器端的if-modified- since時間進(jìn)行比較,默認(rèn)“if_modified_since exact”表示精確匹配,也可以使用“if_modified_since _before”表示只要文件的***修改時間早于或等于瀏覽器端的if-modified-since時間,就返回304。
三、nginx proxy_pass
使用Nginx作為反向代理時,請求會先進(jìn)入Nginx,然后Nginx將請求轉(zhuǎn)發(fā)給后端應(yīng)用,如下圖所示。
首先配置upstream。
- upstream backend_tomcat {
- server 192.168.61.1:9080max_fails=10 fail_timeout=10s weight=5;
- }
接著配置location。
- location = /cache {
- proxy_pass http://backend_tomcat/cache$is_args$args;
- }
接下來,我們可以通過如http://192.168.61.129/cache?millis=1471349916709訪問Nginx,Nginx會將請求轉(zhuǎn)發(fā)給后端Java應(yīng)用。也就是說Nginx只是做了相關(guān)的轉(zhuǎn)發(fā)(負(fù)載均衡),并沒有對請求和響應(yīng)做什么處理。
假設(shè)對后端返回的過期時間需要調(diào)整,可以添加Expires指令到location。
- location = /cache {
- proxy_pass http://backend_tomcat/cache$is_args$args;
- expires 5s;
- }
然后再請求相關(guān)的URL,將得到如下響應(yīng)。
過期時間相關(guān)的響應(yīng)頭被Expires指令更改了,但是last-modified是沒有變的。
即使我們更改了緩存過期頭,但Nginx自己沒有對這些內(nèi)容做代理層緩存,每次請求還是要到后端驗證的,假設(shè)在過期時間內(nèi),這些驗證在Nginx這一層驗證就可以了,不需要到后端驗證,這樣可以減少后端很大的壓力。即整體流程如下。
1.瀏覽器發(fā)起請求,首先到Nginx,Nginx根據(jù)URL在Nginx本地查找是否有代理層本地緩存。
2.Nginx沒有找到本地緩存,則訪問后端獲取***的文檔,并放入到Nginx本地緩存中,返回200狀態(tài)碼和***的文檔給瀏覽器。
3.Nginx找到本地緩存,首先驗證文檔是否過期(Cache-Control:max-age=5),如果過期,則訪問后端獲取***的文檔,并放入Nginx本地緩存中,返回200狀態(tài)碼和***的文檔給瀏覽器;如果文檔沒有過期,即if-modified-since與緩存文檔的last-modified匹配,則返回304狀態(tài)碼給瀏覽器。
內(nèi)容不需要訪問后端,即不需要后端動態(tài)計算/渲染等,直接Nginx代理層就把內(nèi)容返回了,速度更快,內(nèi)容越接近于用戶速度越快。像ApacheTraffic Server、Squid、Varnish、Nginx等技術(shù)都可以用來進(jìn)行內(nèi)容緩存。還有CDN技術(shù)就是用來加速用戶訪問的。
即用戶首先訪問到全國各地的CDN節(jié)點(使用如ATS、Squid實現(xiàn)),如果CDN沒***,則會回源到中央Nginx集群,該集群做二級緩存,如果沒有***緩存(該集群的緩存不是必須的,要根據(jù)實際***情況等決定),則***回源到后端應(yīng)用集群。
像我們商品詳情頁的一些服務(wù)就大量使用了Nginx緩存減少回源到后端的請求量,從而提升訪問速度??梢詤⒖?“ 構(gòu)建需求響應(yīng)式億級商品詳情頁”和“ 京東商品詳情頁服務(wù)閉環(huán)實踐”。
四、Nginx代理層緩存
1. Nginx代理層緩存配置
HTTP模塊配置
- proxy_buffering on;
- proxy_buffer_size 4k;
- proxy_buffers 5124k;
- proxy_busy_buffers_size 64k;
- proxy_cache_path /export/cache/proxy_cachelevels=1:2 keys_zone=cache:512m inactive=5m max_size=8g use_temp_path=off;
- #proxy timeout
- proxy_connect_timeout 3s;
- proxy_read_timeout 5s;
- proxy_send_timeout 5s;
proxy_cache_path指令配置:
- levels=1:2:表示創(chuàng)建兩級目錄結(jié)構(gòu),緩存目錄的***級目錄是1個字符,第二級目錄是2個字符,比如/export/cache/proxy_cache/7/3c/,如果將所有文件放在一級目錄下的話,文件量很大,會導(dǎo)致文件訪問慢。
- keys_zone=cache:512m :設(shè)置存儲所有緩存key和相關(guān)信息的共享內(nèi)存區(qū),1M大約能存儲8000個key。
- inactive=5m :inactive指定被緩存的內(nèi)容多久不被訪問將從緩存中移除,以保證內(nèi)容的新鮮,默認(rèn)為10分鐘。
- max_size=8g :***緩存閥值,“cache manager”進(jìn)程會監(jiān)控***緩存大小,當(dāng)緩存達(dá)到該閥值時,該進(jìn)程將從緩存中移除最近最少訪問的內(nèi)容。
- use_temp_path:如果為on,則內(nèi)容首先被寫入臨時文件(proxy_temp_path ),然后重命名到proxy_cache_path指定的目錄;如果設(shè)置為off,則內(nèi)容直接被寫入到proxy_cache_path指定的目錄,如果需要cache建議off,則該特性是1.7.10提供的。
2. location配置
- location = /cache {
- proxy_cache cache;
- proxy_cache_key$scheme$proxy_host$request_uri;
- proxy_cache_valid 200 5s;
- proxy_passhttp://backend_tomcat/cache$is_args$args;
- add_header cache-status$upstream_cache_status;
- }
3. 緩存相關(guān)配置
- proxy_cache:指定使用哪個共享內(nèi)存區(qū)存儲緩存信息。
- proxy_cache_key :設(shè)置緩存使用的key,默認(rèn)為完整的訪問URL,根據(jù)實際情況設(shè)置緩存key。
- proxy_cache_valid :為不同的響應(yīng)狀態(tài)碼設(shè)置緩存時間。如果是proxy_cache_valid5s,則200、301、302響應(yīng)都將被緩存。
4. proxy_cache_valid不是唯一設(shè)置緩存時間的,還可以通過如下方式(優(yōu)先級從上到下)。
- 以秒為單位的“X-Accel-Expires”響應(yīng)頭來設(shè)置響應(yīng)緩存時間。
- 如果沒有“X-Accel-Expires”,則可以根據(jù)“Cache-Control”、“Expires”來設(shè)置響應(yīng)緩存時間。
- 否則,使用proxy_cache_valid設(shè)置緩存時間。
如果響應(yīng)頭包含Cache-Control:private/no-cache/no-store、Set-Cookie或者只有一個Vary響應(yīng)頭且其值為*,則響應(yīng)內(nèi)容將不會被緩存。可以使用proxy_ignore_headers來忽略這些響應(yīng)頭。
add_header cache-status $upstream_cache_status在響應(yīng)頭中添加緩存***的狀態(tài)。
- HIT:緩存***,直接返回緩存中內(nèi)容,不回源到后端。
- MISS:緩存未***,回源到后端獲取***的內(nèi)容。
- EXPIRED:緩存***但過期了,回源到后端獲取***的內(nèi)容。
- UPDATING:緩存已過期但正在被別的Nginx Worker進(jìn)程更新,配置了proxy_cache_use_stale updating指令時會存在該狀態(tài)。
- STALE:緩存已過期,但因后端服務(wù)出現(xiàn)了問題(比如后端服務(wù)掛了)返回過期的響應(yīng),配置了如proxy_cache_use_stale error timeout指令后會出現(xiàn)該狀態(tài)。
- REVALIDATED:啟用proxy_cache_revalidate指令后,當(dāng)緩存內(nèi)容過期時,Nginx通過一次if-modified-since的請求頭去驗證緩存內(nèi)容是否過期,此時會返回該狀態(tài)。
- BYPASS:proxy_cache_bypass指令有效時,強(qiáng)制回源到后端獲取內(nèi)容,即使已經(jīng)緩存了。
5. proxy_cache_min_uses
用于控制請求多少次后響應(yīng)才被緩存。默認(rèn)“proxy_cache_min_uses1;”,如果緩存熱點比較集中、存儲有限,則可以通過修改該參數(shù)來來減少緩存數(shù)量和寫磁盤次數(shù)。
6. proxy_no_cache
用于控制什么情況下響應(yīng)不被緩存。比如配置“proxy_no_cache$args_nocache”,如果帶的nocache參數(shù)值至少有一個不為空或者為0,則響應(yīng)將不被緩存。
7. proxy_cache_bypass
類似于proxy_no_cache,但是,其控制什么情況不使用緩存的內(nèi)容,而是直接到后端獲取***的內(nèi)容。如果***,則$upstream_cache_status為BYPASS。
8. proxy_cache_use_stale
當(dāng)對緩存內(nèi)容的過期時間不敏感,或者后端服務(wù)出問題時,即使緩存的內(nèi)容不新鮮也總比返回錯誤給用戶強(qiáng)(類似于托底),此時可以配置該參數(shù),如“proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504”,即如果出現(xiàn)超時、后端連接出錯、500、502、503等錯誤時,則即使緩存內(nèi)容已過期也先返回給用戶,此時$upstream_cache_status為STALE。還有一個updating表示緩存已過期但正在被別的Nginx Worker進(jìn)程更新,但先返回了過期內(nèi)容,此時$upstream_cache_status為UPDATING。
9. proxy_cache_revalidate
當(dāng)緩存過期后,如果開啟了proxy_cache_revalidate,則會發(fā)出一次if-modified-since或if-none-match條件請求,如果后端返回304,則此時$upstream_cache_status為REVALIDATED,我們將得到兩個好處,節(jié)省帶寬和減少寫磁盤的次數(shù)。
10. proxy_cache_lock
當(dāng)多個客戶端同時請求同一份內(nèi)容時,如果開啟proxy_cache_lock(默認(rèn)off),則只有一個請求被發(fā)送至后端。其他請求將等待該請求的返回。當(dāng)***個請求返回后,其他相同請求將從緩存中獲取內(nèi)容返回。當(dāng)***個請求超過了proxy_cache_lock_timeout超時時間(默認(rèn)為5s),則其他請求將同時請求到后端來獲取響應(yīng),且響應(yīng)不會被緩存(在1.7.8版本之前是被緩存的)。啟用proxy_cache_lock可以應(yīng)對Dog-pile effect(當(dāng)某個緩存失效時,同時有大量相同的請求沒***緩存,而同時請求到后端,從而導(dǎo)致后端壓力太大,此時限制一個請求去拿即可)。
proxy_cache_lock_age是1.7.8新添加的,如果在proxy_cache_lock_age指定的時間內(nèi)(默認(rèn)為5s),***一個發(fā)送到后端進(jìn)行新緩存構(gòu)建的請求還沒有完成,則下一個請求將被發(fā)送到后端來構(gòu)建緩存(因為1.7.8版本之后,proxy_cache_lock_timeout超時之后返回的內(nèi)容是不緩存的,需要下一次請求來構(gòu)建響應(yīng)緩存)。
五、清理緩存
有時緩存的內(nèi)容是錯誤的,需要手工清理,Nginx企業(yè)版提供了purger功能,對于社區(qū)版Nginx可以考慮使用ngx_cache_purge(https://github.com/FRiCKLE/ngx_cache_purge)模塊進(jìn)行清理緩存。
- location ~ /purge(/.*) {
- allow 127.0.0.1;
- deny all;
- proxy_cache_purge cache$1$is_args$args;
- }
該方法要限制其訪問權(quán)限,如只允許內(nèi)網(wǎng)可以訪問或者需要密碼才能訪問。
到此代理層緩存就介紹完了,通過代理層緩存可以解決很多問題,可以參考“京東商品詳情頁服務(wù)閉環(huán)實踐”。
【本文是51CTO專欄作者張開濤的原創(chuàng)文章,作者微信公眾號:開濤的博客( kaitao-1234567)】