NGINX 緩存使用官方指南
原文: A Guide to Caching with NGINX
作者: Faisal Memon
譯者: 杰微刊 - 張帆
我們都知道,應用程序和網站一樣,其性能關乎生存。但如何使你的應用程序或者網站性能更好,并沒有一個明確的答案。代碼質量和架構是其中的一個原因,但是在很多例子中我們看到,你可以通過關注一些十分基礎的應用內容分發技術( basic application delivery techniques ),來提高終端用戶的體驗。其中一個例子就是實現和調整應用棧( application stack )的緩存。這篇文章,通過幾個例子來講述如何使用 NGINX 緩存,此外,結尾處還列舉了一些常見問題及解答。
基礎
一個 web 緩存坐落于客戶端和“原始服務器( origin server )”中間,它保留了所有可見內容的拷貝。如果一個客戶端請求的內容在緩存中存儲,則可以直接在緩存中獲得該內容而不需要與服務器通信。這樣一來,由于 web 緩存距離客戶端 “ 更近 ” ,就可以提高響應性能,并更有效率的使用應用服務器,因為服務器不用每次請求都進行頁面生成工作。
在瀏覽器和應用服務器之間,存在多種“潛在”緩存,如:客戶端瀏覽器緩存、中間緩存、內容分發網絡( CDN )和服務器上的負載平衡和反向代理。緩存,僅在反向代理和負載均衡的層面,就對性能提高有很大的幫助。
舉個例子說明,去年,我接手了一項任務,這項任務的內容是對一個加載緩慢的網站進行性能優化。首先引起我注意的事情是,這個網站差不多花費了超過 1 秒鐘才生成了主頁。經過一系列調試,我發現加載緩慢的原因在于頁面被標記為不可緩存,即為了響應每一個請求,頁面都是動態生成的。由于頁面本身并不需要經常性的變更,并且不涉及個性化,那么這樣做其實并沒有必要。為了驗證一下我的結論,我將頁面標記為每 5 秒緩存一次,僅僅做了這一個調整,就能明顯的感受到性能的提升。第一個字節到達的時間降低到幾毫秒,同時頁面的加載明顯要更快。
并不是只有大規模的內容分發網絡( CDN )可以在使用緩存中受益 —— 緩存還可以提高負載平衡器、反向代理和應用服務器前端 web 服務的性能。通過上面的例子,我們看到,緩存內容結果,可以更高效的使用應用服務器,因為不需要每次都去做重復的頁面生成工作。此外, Web 緩存還可以用來提高網站可靠性。當服務器宕機或者繁忙時,比起返回錯誤信息給用戶,不如通過配置 NGINX 將已經緩存下來的內容發送給用戶。這意味著,網站在應用服務器或者數據庫故障的情況下,可以保持部分甚至全部的功能運轉。
下一部分討論如何安裝和配置 NGINX 的基礎緩存( Basic Caching )。
如何安裝和配置基礎緩存
我們只需要兩個命令就可以啟用基礎緩存: proxy_cache_path 和 proxy_cache 。 proxy_cache_path 用來設置緩存的路徑和配置, proxy_cache 用來啟用緩存。
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_pass http://my_upstream;
}
}
proxy_cache_path 命令中的參數及對應配置說明如下:
1.用于緩存的本地磁盤目錄是 /path/to/cache/
2. levels 在 /path/to/cache/ 設置了一個兩級層次結構的目錄。將大量的文件放置在單個目錄中會導致文件訪問緩慢,所以針對大多數部署,我們推薦使用兩級目錄層次結構。如果 levels 參數沒有配置,則 NGINX 會將所有的文件放到同一個目錄中。
3. keys_zone 設置一個共享內存區,該內存區用于存儲緩存鍵和元數據,有些類似計時器的用途。將鍵的拷貝放入內存可以使 NGINX 在不檢索磁盤的情況下快速決定一個請求是 `HIT` 還是 `MISS` ,這樣大大提高了檢索速度。一個 1MB 的內存空間可以存儲大約 8000 個 key ,那么上面配置的 10MB 內存空間可以存儲差不多 80000 個 key 。
4. max_size 設置了緩存的上限(在上面的例子中是 10G )。這是一個可選項;如果不指定具體值,那就是允許緩存不斷增長,占用所有可用的磁盤空間。當緩存達到這個上線,處理器便調用 cache manager 來移除最近最少被使用的文件,這樣把緩存的空間降低至這個限制之下。
5. inactive 指定了項目在不被訪問的情況下能夠在內存中保持的時間。在上面的例子中,如果一個文件在 60 分鐘之內沒有被請求,則緩存管理將會自動將其在內存中刪除,不管該文件是否過期。該參數默認值為 10 分鐘( 10m )。注意,非活動內容有別于過期內容。 NGINX 不會自動刪除由緩存控制頭部指定的過期內容(本例中 Cache-Control:max-age=120 )。過期內容只有在 inactive 指定時間內沒有被訪問的情況下才會被刪除。如果過期內容被訪問了,那么 NGINX 就會將其從原服務器上刷新,并更新對應的 inactive 計時器。
6.NGINX 最初會將注定寫入緩存的文件先放入一個臨時存儲區域, use_temp_path=off 命令指示 NGINX 將在緩存這些文件時將它們寫入同一個目錄下。我們強烈建議你將參數設置為 off 來避免在文件系統中不必要的數據拷貝。 use_temp_path 在 NGINX1.7 版本和 NGINX Plus R6中有所介紹。
最終, proxy_cache 命令啟動緩存那些 URL 與 location 部分匹配的內容(本例中,為 `/` )。你同樣可以將 proxy_cache 命令添加到 server 部分,這將會將緩存應用到所有的那些 location 中未指定自己的 proxy_cache 命令的服務中。
陳舊總比沒有強
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 錯誤,并且在其緩存中有請求文件的陳舊版本,則會將這些陳舊版本的文件而不是錯誤信息發送給客戶端。
緩存微調
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,則所有在緩存中找不到文件的請求都會直接與服務器通信。
跨多硬盤分割緩存
使用 NGINX ,不需要建立一個 RAID (磁盤陣列)。如果有多個硬盤, NGINX 可以用來在多個硬盤之間分割緩存。下面是一個基于請求 URI 跨越兩個硬盤之間均分緩存的例子:
proxy_cache_path /path/to/hdd1 levels=1:2 keys_zone=my_cache_hdd1:10m max_size=10g
inactive=60m use_temp_path=off;
proxy_cache_path /path/to/hdd2 levels=1:2 keys_zone=my_cache_hdd2:10m max_size=10g
inactive=60m use_temp_path=off;
split_clients $request_uri $my_cache {
50% “my_cache_hdd1”;
50% “my_cache_hdd2”;
}
server {
...
location / {
proxy_cache $my_cache;
proxy_pass http://my_upstream;
}
}
上例中的兩個 proxy_cache_path 定義了兩個緩存( my_cache_hdd1 和 my_cache_hd22 )分屬兩個不同的硬盤。 split_clients 配置部分指定了請求結果的一半在 my_cache_hdd1 中緩存,另一半在 my_cache_hdd2 中緩存。基于 $request_uri (請求 URI )變量的哈希值決定了每一個請求使用哪一個緩存,對于指定 URI 的請求結果通常會被緩存在同一個緩存中。
常見問題解答
這部分內容回答了一些關于 NGINX 內容緩存的常見問題。
可以檢測 NGINX 緩存狀態嗎?
可以,使用 add_header 指令:
add_header X-Cache-Status $upstream_cache_status;
上面的例子中,在對客戶端的響應中添加了一個 `X-Cache-Status`HTTP 響應頭,下面是 $upstream_cache_status 的可能值:
1.MISS—— 響應在緩存中找不到,所以需要在服務器中取得。這個響應之后可能會被緩存起來。
2.BYPASS—— 響應來自原始服務器而不是緩存,因為請求匹配了一個 proxy_cache_bypass (見下面 我可以 在 緩存中打個洞嗎? )。這個響應之后可能會被緩存起來。
3.EXPIRED—— 緩存中的某一項過期了,來自原始服務器的響應包含最新的內容。
4.STALE—— 內容陳舊是因為原始服務器不能正確響應。需要配置 proxy_cache_use_stale 。
5.UPDATING—— 內容過期了,因為相對于之前的請求,響應的入口( entry )已經更新,并且 proxy_cache_use_stale 的 updating 已被設置 。
6.REVALIDATED—— proxy_cache_revalidate 命令被啟用, NGINX 檢測得知當前的緩存內容依然有效( If-Modified-Since 或者 If-None-Match )。
7.HIT—— 響應包含來自緩存的最新有效的內容。
NGINX 如何決定是否緩存?
默認情況下, NGINX 需要考慮從原始服務器得到的 Cache-Control 標頭。當在響應頭部中 Cache-Control 被配置為 Private , No-Cache , No-Store 或者 Set-Cookie , NGINX 不進行緩存。 NGINX 僅僅緩存 GET 和 HEAD 客戶端請求。你也可以參照下面的解答覆蓋這些默認值。
Cache-Control 頭部可否被忽略?
可以,使用 proxy_ignore_headers 命令。如下列配置:
location /images/ {
proxy_cache my_cache;
proxy_ignore_headers Cache-Control;
proxy_cache_valid any 30m;
...
}
NGINX 會忽略所有 /images/ 下的 Cache-Control 頭。 proxy_cache_valid 命令強制規定緩存數據的過期時間,如果忽略 Cache-Control 頭,則該命令是十分必要的。 NGINX 不會緩存沒有過期時間的文件。
當在頭部設置了 Set-Cookie 之后 NGINX 還能緩存內容嗎?
可以,使用 proxy_ignore_headers 命令,參見之前的解答。
NGINX 能否緩存 POST 請求?
可以,使用 proxy_cache_methods 命令:
proxy_cache_methods GET HEAD POST;
這個例子中可以緩存 POST 請求。其他附加的方法可以依次列出來的,如 PUT 。
NGINX 可以緩存動態內容嗎?
可以,提供的 Cache-Control 頭部可以做到。緩存動態內容,甚至短時間內的內容可以減少在原始數據庫和服務器中加載,可以提高第一個字節的到達時間,因為頁面不需要對每個請求都生成一次。
我可以再緩存中打個洞( Punch a Hole )嗎?
可以,使用 proxy_cache_bypass 命令:
location / {
proxy_cache_bypass $cookie_nocache $arg_nocache;
}
這個命令定義了哪種類型的請求需要向服務器請求而不是嘗試首先在緩存中查找。有些時候又被稱作在內存中“打個洞”。在上面的例子中, NGINX 會針對 nocache cookie 或者參數進行直接請求服務器,如: http://www.example.com/?nocache=true 。 NGINX 依然可以為將那些沒有避開緩存的請求緩存響應結果。
NGINX 使用哪些緩存鍵?
NGINX 生成的鍵的默認格式是類似于下面的 NGINX 變量 的 MD5 哈希值 : $scheme$proxy_host$request_uri ,實際的算法有些復雜。
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_pass http://my_upstream;
}
}
按照上面的配置, http://www.example.org/my_image.jpg 的緩存鍵被計算為 md5(“http://my_upstream:80/my_image.jpg”) 。
注意, $proxy_host 變量用于哈希之后的值而不是實際的主機名( www.example.com )。 $proxy_host 被定義為 proxy_pass 中指定的代理服務器的主機名和端口號。
為了改變變量(或其他項)作為基礎鍵,可以使用 proxy_cache_key 命令(下面的問題會講到)。
可以使用 Cookie 作為緩存鍵的一部分嗎?
可以,緩存鍵可以配置為任意值,如:
proxy_cache_key $proxy_host$request_uri$cookie_jessionid;
NGINX 使用 Etag 頭部嗎?
在 NGINX 1.7.3 和 NGINX Plus R5 及之后的版本,配合使用 If-None-Match , Etag 是完全支持的。
NGINX 如何處理字節范圍請求?
如果緩存中的文件是最新的, NGINX 會對客戶端提出的字節范圍請求傳遞指定的字節。如果文件并沒有被提前緩存,或者是陳舊的,那么 NGINX 會從服務器上下載完整文件。如果請求了單字節范圍, NGINX 會盡快的將該字節發送給客戶端,如果在下載的數據流中剛好有這個字節。如果請求指定了同一個文件中的多個字節范圍, NGINX 則會在文件下載完畢時將整個文件發送給客戶端。
一旦文件下載完畢, NGINX 將整個數據移動到緩存中,這樣一來,無論將來的字節范圍請求是單字節還是多字節范圍, NGINX 都可以在緩存中找到指定的內容立即響應。
NGINX 支持緩存清洗嗎?
NGINX Plus 支持有選擇性的清洗緩存。當原始服務器上文件已經被更新,但是 NGINX Plus 緩存中文件依然有效( Cache-Control:max-age 依然有效, proxy_cache_path 命令中 inactive 參數設置的超時時間沒有過期),這個功能便十分有用。使用 NGINX Plus 的緩存清洗特性,這個文件可以被輕易的刪除。詳細信息,參見 Purging Content from the Cache 。
NGINX 如何處理 Pragma 頭部?
當客戶端添加了 Pragma:no-cache 頭部,則請求會繞過緩存直接訪問服務器請求內容。 NGINX 默認不考慮 Pragma 頭部,不過你可以使用下面的 proxy_cache_bypass 的命令來配置該特性:
location /images/ {
proxy_cache my_cache;
proxy_cache_bypass $http_pragma;
...
}
NGINX 支持 Vary 頭部嗎?
是的,在 NGINX Plus R5 、 NGINX1.7.7 和之后的版本中是支持的。看看這篇不錯的文章: good overview of the Vary header 。
延伸閱讀
有非常多的方式對 NGINX 進行個性化定制和調優。要了解更多關于 NGINX 緩存,請看下面的資源:
NGINX 文檔中的 ngx_http_proxy_module 部分包含所有內容緩存的可選項。
NGINX 內容緩存研討會 全程可以根據自己的需要查看。這篇博客包含了研討會的部分內容。
NGINX Plus 管理員指南中的 Content Caching 部分有更多關于調優 NGINX 緩存的配置案例和信息內容。
Content Caching with NGINX Plus 產品頁包含如何配置 NGINX Plus 進行緩存清洗的概述,并提供了其他緩存個性化配置的例子。
----------------------------------------------
相信愛閱讀的您,最近已經注意到了我們。
我們將陸續推出一系列關于業務設計和技術架構方面的好內容。
也歡迎您提出意見、推薦文章,為讓別人更了解這個世界,作出自己的貢獻。
歡迎任何目的的聯系。
我的郵箱: weikan@jointforce.com 。
我的QQ:3272840549。