本文將介紹如何使用Apache HTTP Server的緩存功能來加速Web和代理服務,同時避免常見問題和錯誤配置。
Apache HTTP伺服器提供了一系列緩存功能,旨在以各種方式提高伺服器的性能。
三態RFC2616 HTTP緩存mod_cache
及其提供者模組mod_cache_disk
提供智能的HTTP感知緩存。內容本身存儲在緩存中,mod_cache
旨在遵守控制內容可緩存性的所有各種HTTP頭和選項。mod_cache
針對簡單和複雜的緩存配置,可以在其中處理代理內容,動態本地內容,或者需要加速對可能較慢的磁片上的本地檔的訪問。
雙狀態鍵/值共用對象緩存
共用對象緩存API(socache)及其提供程式模組提供基於伺服器範圍的鍵/值共用對象緩存。這些模組旨在緩存低級別數據,例如SSL會話和身份驗證憑據。後端允許數據在伺服器範圍記憶體儲在共用記憶體中,或者數據中心記憶體儲在緩存中,例如memcache
或distcache
。
專門的檔緩存
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-age
或s-maxage
字段聲明顯式新生命週期,或者包含Expires
標頭。
同時,當客戶端在請求中提供自己的Cache-Control
標頭時,客戶端可以覆蓋原始伺服器定義的新生命週期。在這種情況下,請求和回應之間的最低新生命週期獲勝。
當請求或回應中缺少此新生命週期時,將應用默認新生命週期。緩存實體的默認新生命週期為一小時,但使用CacheDefaultExpire
指令可以輕鬆覆蓋。
如果回應不包含Expires
標頭但包含Last-Modified
標頭,則mod_cache
可以基於啟發式推斷新生命週期,可以通過使用CacheLastModifiedFactor
指令來控制。
對於本地內容或未定義其自己的Expires
標頭的遠程內容,可以使用mod_expires
通過添加max-age
和Expires
來微調度生命週期。
還可以通過使用CacheMaxExpire
來控制最大新壽命。
有條件請求簡要說明
當內容從緩存過期並變得陳舊時,httpd
將修改請求以使其成為條件。
當原始緩存回應中存在ETag
標頭時,mod_cache
將向請求發送原始伺服器的If-None-Match
標頭。當原始緩存回應中存在Last-Modified
標頭時,mod_cache
將向原始伺服器的請求添加If-Modified-Since
標頭。執行這些操作之一會使請求成為條件。
當源伺服器接收到條件請求時,源伺服器應根據請求檢查ETag
或Last-Modified
參數是否已更改。如果不更改,原點應該以簡潔的“304 Not Modified”回應進行回應。這向緩存發出信號,表明舊內容仍然是新的,應該用於後續請求,直到再次達到內容的新的新生命週期。
如果內容已更改,則提供內容,就好像請求不是以條件開頭一樣。
有條件請求提供兩個好處。首先,當向源伺服器發出這樣的請求時,如果來自源的內容與高速緩存中的內容匹配,則可以容易地確定,並且沒有轉移整個資源的開銷。
其次,設計良好的源伺服器將以這樣的方式設計:條件請求的生成要比完整回應便宜得多。對於靜態檔,通常所涉及的是對stat()
或類似系統調用的調用,以查看檔的大小或修改時間是否已更改。因此,如果本地內容沒有改變,甚至可以從高速緩存中更快地提供本地內容。
原始伺服器應盡可能地支持條件請求,但是如果不支持條件請求,則源將回應,就好像請求不是有條件的,並且緩存將回應,就好像內容已更改並保存新內容到緩存。在這種情況下,緩存的行為類似於簡單的兩個狀態緩存,其中內容實際上是新鮮的或已刪除。
什麼可以緩存?
通過HTTP緩存哪些回應總結如下:
- 必須為此URL啟用緩存。請參閱
CacheEnable
和CacheDisable
指令。 - 如果回應的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-age
或s-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-language
和accept-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的檔的首碼,但首先根據CacheDirLevels
和CacheDirLength
指令將其拆分為目錄。
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子進程都將複製此記憶體,因此確保映射的檔不會太大而導致系統交換記憶體至關重要。