Apache快取組態


本文將介紹如何使用Apache HTTP Server的快取功能來加速Web和代理服務,同時避免常見問題和錯誤組態。

Apache HTTP伺服器提供了一系列快取功能,旨在以各種方式提高伺服器的效能。

三態RFC2616 HTTP快取
mod_cache及其提供者模組mod_cache_disk提供智慧的HTTP感知快取。內容本身儲存在快取中,mod_cache旨在遵守控制內容可快取性的所有各種HTTP頭和選項。mod_cache針對簡單和複雜的快取組態,可以在其中處理代理內容,動態本地內容,或者需要加速對可能較慢的磁碟上的本地檔案的存取。

雙狀態鍵/值共用物件快取

共用物件快取API(socache)及其提供程式模組提供基於伺服器範圍的鍵/值共用物件快取。這些模組旨在快取低階別資料,例如SSL對談和身份驗證憑據。後端允許資料在伺服器範圍記憶體儲在共用記憶體中,或者資料中心記憶體儲在快取中,例如memcachedistcache

專門的檔案快取

mod_file_cache提供了在伺服器啟動時將檔案預載入到記憶體中的功能,並且可以改善存取時間並儲存經常存取的檔案上的檔案控制代碼,因為不需要在每個請求上轉到磁碟。

三態RFC2616 HTTP快取

HTTP協定包含對RFC2616第13節描述的內聯快取機制的內建支援,mod_cache模組可用於利用此功能。

與簡單的兩個狀態鍵/值快取不同,其中內容在不再新鮮時完全消失,HTTP快取包括保留陳舊內容的機制,並詢問源伺服器此陳舊內容是否已更改,如果不是則再次重新整理。

HTTP快取中的條目存在以下三種狀態之一:

Fresh
如果內容足夠新(比其新鮮壽命更年輕),則認為是Fresh。HTTP快取可以免費提供新內容,而無需對源伺服器進行任何呼叫。

Stale
如果內容太舊(早於其新鮮度生命週期),則認為是Stale。HTTP快取應聯絡原始伺服器,並在向用戶端提供過時內容之前檢查內容是否仍然是新的。如果原始伺服器仍然無效,則原始伺服器將使用替換內容進行響應,或者理想情況下,源伺服器將使用程式碼進行響應以告知快取內容仍然是新的,而無需再次生成或傳送內容。內容再次變得新,迴圈繼續。

HTTP協定允許快取在某些情況下提供過時資料,例如當嘗試使用源伺服器重新整理資料時出現5xx錯誤,或者另一個請求已經在重新整理給定條目的過程中。在這些情況下,會在響應中新增警告檔頭。

Non Existent
如果快取已滿,則保留從快取中刪除內容以騰出空間的選項。內容可以隨時刪除,可以是舊或新。htcacheclean工具可以一次性執行,或者作為守護程式部署,以使快取的大小保持在給定大小或給定數量的inode內。在嘗試刪除新內容之前,該工具會嘗試刪除舊內容。

與伺服器的互動
mod_cache模組在兩個可能的位置掛鉤到伺服器,具體取決於CacheQuickHandler指令的值:

快速處理階段

這個階段在請求處理期間很早就發生,就在解析請求之後。如果在快取中找到內容,則立即提供該內容,並且幾乎所有請求處理都被繞過。

在這種情況下,快取的行為就像它已經「閂上」到伺服器的前面一樣。

此模式提供最佳效能,因為繞過了大多數伺服器處理。但是,此模式也會繞過伺服器處理的身份驗證和授權階段,因此在重要時應謹慎選擇此模式。

mod_cache在此階段執行時,具有「授權」檔頭(例如,HTTP基本身份驗證)的請求既不可快取也不從快取提供。

正常處理階段

在所有請求階段完成之後,此階段在請求處理的後期發生。在這種情況下,快取的行為就像它已經「閂上」到伺服器的後面一樣。
此模式提供了最大的靈活性,因為快取可能存在於過濾器鏈中的精確控制點,並且快取的內容可以在傳送到用戶端之前進行過濾或個性化。

如果在快取中找不到URL,mod_cache將向篩選器堆疊新增一個篩選器,以便記錄對快取的響應,然後停止,允許正常的請求處理繼續。如果確定內容是可快取的,則將內容儲存到快取中以供將來服務,否則將忽略該內容。

如果在快取中找到的內容是舊的,則mod_cache模組將請求轉換為條件請求。如果源伺服器以正常響應響應,則快取正常響應,替換已快取的內容。如果源伺服器響應304 Not Modified響應,則內容將再次標記為新的,快取的內容由過濾器提供,而不是儲存。

提高快取命中率

當一個虛擬主機是由許多不同的伺服器別名已知,確保將UseCanonicalName設定為On可以顯著提高快取命中率。這是因為服務於內容的虛擬主機的主機名在快取金鑰中使用。設定為On具有多個伺服器名稱或別名的虛擬主機將不會生成不同的快取實體,而是根據規範主機名快取內容。

新壽命

要快取的格式良好的內容應使用Cache-Control檔頭的max-ages-maxage欄位宣告顯式新生命週期,或者包含Expires檔頭。

同時,當用戶端在請求中提供自己的Cache-Control檔頭時,用戶端可以覆蓋原始伺服器定義的新生命週期。在這種情況下,請求和響應之間的最低新生命週期獲勝。

當請求或響應中缺少此新生命週期時,將應用預設新生命週期。快取實體的預設新生命週期為一小時,但使用CacheDefaultExpire指令可以輕鬆覆蓋。

如果響應不包含Expires檔頭但包含Last-Modified檔頭,則mod_cache可以基於啟發式推斷新生命週期,可以通過使用CacheLastModifiedFactor指令來控制。

對於本地內容或未定義其自己的Expires檔頭的遠端內容,可以使用mod_expires通過新增max-ageExpires來微排程生命週期。

還可以通過使用CacheMaxExpire來控制最大新壽命。

有條件請求簡要說明

當內容從快取過期並變得陳舊時,httpd將修改請求以使其成為條件。

當原始快取響應中存在ETag檔頭時,mod_cache將向請求傳送原始伺服器的If-None-Match檔頭。當原始快取響應中存在Last-Modified檔頭時,mod_cache將向原始伺服器的請求新增If-Modified-Since檔頭。執行這些操作之一會使請求成為條件。

當源伺服器接收到條件請求時,源伺服器應根據請求檢查ETagLast-Modified引數是否已更改。如果不更改,原點應該以簡潔的「304 Not Modified」響應進行響應。這向快取發出信號,表明舊內容仍然是新的,應該用於後續請求,直到再次達到內容的新的新生命週期。

如果內容已更改,則提供內容,就好像請求不是以條件開頭一樣。

有條件請求提供兩個好處。首先,當向源伺服器發出這樣的請求時,如果來自源的內容與快取記憶體中的內容匹配,則可以容易地確定,並且沒有轉移整個資源的開銷。

其次,設計良好的源伺服器將以這樣的方式設計:條件請求的生成要比完整響應便宜得多。對於靜態檔案,通常所涉及的是對stat()或類似系統呼叫的呼叫,以檢視檔案的大小或修改時間是否已更改。因此,如果本地內容沒有改變,甚至可以從快取記憶體中更快地提供本地內容。

原始伺服器應盡可能地支援條件請求,但是如果不支援條件請求,則源將響應,就好像請求不是有條件的,並且快取將響應,就好像內容已更改並儲存新內容到快取。在這種情況下,快取的行為類似於簡單的兩個狀態快取,其中內容實際上是新鮮的或已刪除。

什麼可以快取?

通過HTTP快取哪些響應總結如下:

  • 必須為此URL啟用快取。請參閱CacheEnableCacheDisable指令。
  • 如果響應的HTTP狀態程式碼不是200,203,300,301或410,則它還必須指定「Expires」或「Cache-Control」檔頭。
  • 請求必須是HTTP GET請求。
  • 如果響應包含「Authorization:」檔頭,則它還必須在「Cache-Control:」檔頭中包含「s-maxage」,「must-revalidate」或「public」選項,否則它將不會被快取。
  • 如果URL包含查詢字串(例如來自HTML表單GET方法),則除非響應通過包含「Expires:」檔頭或「Cache」的max-age或s-maxage指令指定顯式過期,否則不會快取它 - Control:「標題。
  • 如果響應的狀態為200(OK),則響應還必須包括「Etag」,「Last-Modified」或「Expires」標題中的至少一個,或者max-ages-maxage指令。「Cache-Control:」檔頭,除非已使用CacheIgnoreNoLastMod指令另外要求。
  • 如果響應在「Cache-Control:」檔頭中包含「private」選項,則除非已使用CacheStorePrivate請求,否則不會儲存該選項。
  • 同樣,如果響應在「Cache-Control:」檔頭中包含「no-store」選項,則除非已使用CacheStoreNoStore,否則不會儲存該選項。
  • 如果響應包含一個包含全部匹配*的「Vary:」標題,則不會儲存響應。

什麼不應該快取?

應該由建立請求的用戶端或構建響應的原始伺服器通過正確設定Cache-Control頭來決定內容是否應該是可快取的,並且應該保留mod_cache以滿足其意願。適當的用戶端或伺服器。

不應快取時間敏感的內容,或者根據HTTP協商未涵蓋的請求的詳細資訊而變化的內容。此內容應使用Cache-Control檔頭宣告自己不可快取。

如果內容經常更改,以分鐘或秒的新鮮度生命週期表示,則內容仍然可以快取,但是非常希望源伺服器正確支援條件請求以確保不必定期生成完整響應。

可以通過智慧使用Vary響應頭來快取基於用戶端提供的請求頭而變化的內容。

可變/協商的內容

當原始伺服器設計為根據請求中的檔頭值響應不同的內容時,例如,為了在同一URL上提供多種語言,HTTP的快取機制可以在同一URL上快取同一頁面的多個變體。

這是由原始伺服器新增Vary檔頭來完成的,以指示在確定兩個變體是否彼此不同時,快取記憶體必須考慮哪些檔頭。

如果收到帶有變化標題的響應,例如;

Vary: negotiate,accept-language,accept-charset

mod_cache僅向具有與原始請求匹配的accept-languageaccept-charset檔頭的請求者提供快取內容。

內容的多個變體可以併行快取,mod_cache使用Vary頭和Vary列出的請求頭的相應值來決定返回到用戶端的許多變體中的哪一個。

快取設定範例

快取到磁碟

mod_cache模組依賴於特定的後端儲存實現來管理快取,並且為了快取到磁碟,提供mod_cache_disk來支援這一點。

通常,模組這樣組態 -

CacheRoot   "/var/cache/apache/"
CacheEnable disk /
CacheDirLevels 2
CacheDirLength 1

重要的是,由於快取檔案是本地儲存的,因此作業系統記憶體快取通常也會應用於它們的存取。因此,雖然檔案儲存在磁碟上,但如果頻繁存取它們,作業系統很可能會確保它們實際上是從記憶體中提供的。

了解快取儲存

要將專案儲存在快取中,mod_cache_disk會建立所請求URL的22個字元的雜湊值。此雜湊包含主機名,協定,埠,路徑和URL的任何CGI引數,以及由Vary頭定義的元素,以確保多個URL不會相互衝突。

每個字元可以是64個不同字元中的任何一個,這意味著整體上有64 ^ 22個可能的雜湊值。例如,URL可能會被雜湊到xyTGxSMO2b68mBCykqkp1w。此雜湊用作在快取中命名特定於該URL的檔案的字首,但首先根據CacheDirLevelsCacheDirLength指令將其拆分為目錄。

CacheDirLevels指定應該有多少級別的子目錄,CacheDirLength指定每個目錄中應該有多少個字元。使用上面給出的範例設定,雜湊將變為檔案名字首為/var/cache/apache/x /y/TGxSMO2b68mBCykqkp1w

此技術的總體目標是減少可能在特定目錄中的子目錄或檔案的數量,因為大多數檔案系統隨著此數量的增加而減慢。對於CacheDirLength設定為1,在任何特定級別最多可以有64個子目錄。設定為2時,可以有64 * 64個子目錄,依此類推。除非你有充分的理由不這樣做,否則建議將CacheDirLength設定為1

設定CacheDirLevels取決於您預計要在快取中儲存的檔案數。通過上例中使用設定值為2,最終可以建立總共4096個子目錄。快取了100萬個檔案,每個目錄大約有245個快取的URL。

每個URL在快取儲存中至少使用兩個檔案。通常存在.header檔案,其包括關於URL的元資訊,例如何時到期以及.data檔案,其是要提供的內容的逐字副本。

在通過Vary檔頭協商的內容的情況下,將為所討論的URL建立.vary目錄。該目錄將具有與不同協商內容相對應的多個.data檔案。

維護磁碟快取

mod_cache_disk模組不會嘗試調節快取使用的磁碟空間量,儘管它會優雅地停止任何磁碟錯誤,並且表現得好像快取從未出現過一樣。

相反,提供httpd的是htcacheclean工具,它用於定期清除快取。確定執行htcacheclean的頻率以及用於快取的目標大小有點複雜,可能需要嘗試並選擇最佳值。

htcacheclean有兩種操作模式。它可以作為持久守護行程執行,也可以定期從cron執行。htcacheclean可能需要一個小時或更長時間才能處理非常大(幾十千兆位元組)的快取,如果從cron執行它,建議您確定典型執行需要多長時間,以避免一次執行多個範例。

還建議為htcacheclean選擇適當的nice級別,以便在伺服器執行時該工具不會導致過多的磁碟io。

因為mod_cache_disk本身並不注意使用多少空間,所以應該確保htcacheclean組態為在清理後留下足夠的「增長空間」。

快取到memcached
使用mod_cache_socache模組,mod_cache可以快取來自各種實現的資料(aka:「providers」)。例如,使用mod_socache_memcache模組,可以指定將memcached用作為後端儲存機制。

通常,模組的組態為:

CacheEnable socache /
CacheSocache memcache:memcd.example.com:11211

可以通過將它們附加到由逗號分隔的多個CacheSocache memcache伺服器,行的末尾來指定其他memcached伺服器:

CacheEnable socache /
CacheSocache memcache:mem1.example.com:11211,mem2.example.com:11212

此格式還與其他各種mod_cache_socache提供程式一起使用。例如:

CacheEnable socache /
CacheSocache shmcb:/path/to/datafile(512000)
CacheEnable socache /
CacheSocache dbm:/path/to/datafile

專用檔案快取

在檔案系統可能很慢或檔案控制代碼很昂貴的平台上,可以選擇在啟動時將檔案預載入到記憶體中。
在開啟檔案較慢的系統上,存在在啟動時開啟檔案並快取檔案控制代碼的選項。這些選項可以幫助對靜態檔案的存取速度較慢的系統。

檔案控制代碼快取

開啟檔案的行為本身可能是延遲的來源,特別是在網路檔案系統上。通過維護常用檔案的開啟檔案描述符的快取,httpd可以避免這種延遲。目前httpd提供了File-Handle Caching的一個實現。

CacheFile

httpd中最基本的快取形式是mod_file_cache提供的檔案控制代碼快取。此快取不是快取檔案內容,而是維護一個開啟檔案描述符的表。使用CacheFile指令在組態檔案中指定以這種方式快取的檔案。

CacheFile指令指示httpd在啟動時開啟檔案,並重新使用此檔案控制代碼以便隨後存取此檔案。

CacheFile /usr/local/apache2/htdocs/index.html

如果打算以這種方式快取大量檔案,則必須確保正確設定作業系統對開啟檔案數的限制。

雖然使用CacheFile不會導致檔案內容本身被快取,但它確實意味著如果檔案在httpd執行時發生更改,則這些更改將不會被選中。該檔案將始終像httpd啟動時那樣提供服務。

如果在httpd執行時刪除了該檔案,它將繼續維護一個開啟的檔案描述符,並像啟動httpd時那樣提供檔案。這通常也意味著雖然該檔案已被刪除,並且未顯示在檔案系統上,但在httpd停止並且檔案描述符關閉之前,將無法恢復額外的可用空間。

記憶體快取

直接從系統記憶體提供服務是普遍提供內容的最快方法。從磁碟控制器讀取檔案,或者更糟糕的是,從遠端網路讀取檔案的速度要慢幾個數量級。磁碟控制器通常涉及物理過程,網路存取受可用頻寬的限制。另一方面,記憶體存取只需幾納秒。

雖然系統記憶體並不便宜,但是位元組數位是迄今為止最昂貴的儲存型別,確保它有效使用非常重要。通過在記憶體中快取檔案,可以減少系統上可用的記憶體量。正如我們所看到的,在作業系統快取的情況下,這不是一個問題,但是當使用httpd自己的記憶體中快取時,確保不為快取分配太多記憶體是很重要的。否則系統將被迫換掉記憶體,這可能會降低效能。

作業系統快取
幾乎所有現代作業系統都將檔案資料快取在核心直接管理的記憶體中。這是一個強大的功能,並且在大多數情況下作業系統都是正確的。例如,在Linux上,讓我們看看第一次和第二次讀取檔案所花費的時間差別-

colm@coroebus:~$ time cat testfile > /dev/null
real    0m0.065s
user    0m0.000s
sys     0m0.001s
colm@coroebus:~$ time cat testfile > /dev/null
real    0m0.003s
user    0m0.003s
sys     0m0.000s

即使對於這個小檔案,讀取檔案所需的時間也存在巨大差異。這是因為核心已將檔案內容快取在記憶體中。

通過確保系統上有「備用」記憶體,可以確保將越來越多的檔案內容儲存在此快取中。這可以是記憶體快取的一種非常有效的方法,並且根本不涉及httpd的額外組態。

此外,由於作業系統知道何時刪除或修改檔案,因此可以在必要時自動從快取中刪除檔案內容。與httpd的記憶體中快取相比,這是一個很大的優勢,它無法知道檔案何時發生了變化。

儘管自動作業系統快取具有效能和優點,但在某些情況下,httpd可以更好地執行記憶體快取。

MMapFile快取
mod_file_cache提供了MMapFile指令,它允許在開始時讓httpd將靜態檔案的內容對映到記憶體中(使用mmap系統呼叫)。httpd將使用記憶體中的內容來存取此檔案的所有後續內容。

MMapFile /usr/local/apache2/htdocs/index.html

CacheFile指令一樣,httpd啟動後將不會獲取這些檔案中的任何更改。

MMapFile指令不跟蹤它分配的記憶體量,因此您必須確保不要過度使用該指令。每個httpd子進程都將複製此記憶體,因此確保對映的檔案不會太大而導致系統交換記憶體至關重要。