Nginx 快取使用官方教程及常見問題解答

jointforce發表於2015-08-04

我們都知道,應用程式和網站一樣,其效能關乎生存。但如何使你的應用程式或者網站效能更好,並沒有一個明確的答案。程式碼質量和架構是其中的一個原因,但是在很多例子中我們看到,你可以通過關注一些十分基礎的應用內容分發技術,來提高終端使用者的體驗。其中一個例子就是實現和調整應用棧(application stack)的快取。這篇文章,通過幾個例子來講述如何使用NGINX快取,此外,結尾處還列舉了一些常見問題及解答。

NGINX快取使用官方指南

基礎

一個web快取坐落於客戶端和“原始伺服器(origin server)”中間,它保留了所有可見內容的拷貝。如果一個客戶端請求的內容在快取中儲存,則可以直接在快取中獲得該內容而不需要與伺服器通訊。這樣一來,由於web快取距離客戶端“更近”,就可以提高響應效能,並更有效率的使用應用伺服器,因為伺服器不用每次請求都進行頁面生成工作。

在瀏覽器和應用伺服器之間,存在多種“潛在”快取,如:客戶端瀏覽器快取、中間快取、內容分發網路(CDN)和伺服器上的負載平衡和反向代理。快取,僅在反向代理和負載均衡的層面,就對效能提高有很大的幫助。

舉個例子說明,去年,我接手了一項任務,這項任務的內容是對一個載入緩慢的網站進行效能優化。首先引起我注意的事情是,這個網站差不多花費了超過1秒鐘才生成了主頁。經過一系列除錯,我發現載入緩慢的原因在於頁面被標記為不可快取,即為了響應每一個請求,頁面都是動態生成的。由於頁面本身並不需要經常性的變更,並且不涉及個性化,那麼這樣做其實並沒有必要。為了驗證一下我的結論,我將頁面標記為每5秒快取一次,僅僅做了這一個調整,就能明顯的感受到效能的提升。第一個位元組到達的時間降低到幾毫秒,同時頁面的載入明顯要更快。

並不是只有大規模的內容分發網路(CDN)可以在使用快取中受益——快取還可以提高負載平衡器、反向代理和應用伺服器前端web服務的效能。通過上面的例子,我們看到,快取內容結果,可以更高效的使用應用伺服器,因為不需要每次都去做重複的頁面生成工作。此外,Web快取還可以用來提高網站可靠性。當伺服器當機或者繁忙時,比起返回錯誤資訊給使用者,不如通過配置NGINX將已經快取下來的內容傳送給使用者。這意味著,網站在應用伺服器或者資料庫故障的情況下,可以保持部分甚至全部的功能運轉。

下一部分討論如何安裝和配置NGINX的基礎快取(Basic Caching)。

如何安裝和配置基礎快取

我們只需要兩個命令就可以啟用基礎快取: proxy_cache_pathproxy_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_hdd1my_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_staleupdating已被設定
  6. REVALIDATED——proxy_cache_revalidate命令被啟用,NGINX檢測得知當前的快取內容依然有效(If-Modified-Since或者If-None-Match)。
  7. HIT——響應包含來自快取的最新有效的內容。

NGINX 如何決定是否快取?

預設情況下,NGINX需要考慮從原始伺服器得到的Cache-Control標頭。當在響應頭部中Cache-Control被配置為PrivateNo-CacheNo-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進行快取清洗的概述,並提供了其他快取個性化配置的例子。

相關文章