nginx—web緩存問題探究

寫在前面
隨著項目的推進,漸漸的要上https,要從tomcat服務器轉換到nginx+tomcat組合的方式了。 nginx有個問題就是緩存太”嚴重”,我認為這其實是一個大的優化,如果用好了可以對項目體驗起到很好的催化,這也是nginx作為服務器的前端的意義所在。 但是往往當開發人員對http理解薄弱的,就會對此很搓火。因為過強的緩存會導致頁面滯後,debug時候很多東西無法及時出來,只能動不動強制清緩存,大大破壞了開發體驗。

由來
其實一直一來都在做強緩存。新起的項目都做好了資源的hash。但是如果不是這次錯配的nginx導致個人尋根結底找原因,也不會有這篇關於http緩存的文章。 HTTP的文章其實網上已經有很多了。個人完全不認為會比那些無比詳細和成系列的雄文對比。僅僅是個人對知識體系的一個總結吧。

言歸正傳。問題的由來其實是因為nginx的響應頭配置有問題,它裡面既沒有 Cache-Control,也沒有Expires。導致很多資源無端200(from cache)而不304,使得資源存在更新不及時的問題.

舉個栗子,這是清空緩存後第一次訪問資源的http響應頭:

這是對應的請求頭:

然後在地址欄敲回車以後以後,返回的200(from cache),而我不是猜想的304 總之一句話就是這個問題的關鍵在於:響應頭裡既沒有 Cache-Control也沒有Expires的時候,是如何在新鮮度檢測階段直接認為新鮮度沒有問題的。

問題的拋出
在搞清整個問題之前我覺得還是要將HTTP緩存機制好好整理處理作為問題解決的前提和疑問拋出的前提。

用最簡單的話來說,服務器緩存啟用是存在兩個判斷機制的:

新鮮度檢測階段 需要依賴響應頭的Cache-Control和Expires,通過才會返回200(from cache)
資源二次校驗階段 校驗資源一致性,如果一致返回304,如果不一致返回200,當然,如果沒有找到返回404
現在的問題就拋出來了,為什麼,響應頭裡面沒有Cache-Control和Expires,那麼按照上訴的緩存啟用機制,返回的狀態嗎可能是304,可能是200,可能是404或者更多的,但是就是不可能是200(from cache)。但是現實用慘淡的現實告訴我們,結果就是200(from cache)!這是為什麼呢?

HTTP緩存
用戶操作對緩存的影響
用戶不同的操作是對緩存存在固有影響,正常鏈接點入、新tab打開網址/窗口、F5刷新、前進後退、Ctrl+F5強刷,對緩存都會不同的表現

用戶操作 Exprires/Cache-Control Last-Modified/Etag
地址欄回車 有效 有效
頁面鏈接轉跳 有效 有效
新開窗口 有效 有效
前進後退 無效 有效
Ctrl+F5強刷 無效 無效
緩存機制
緩存總體上的流程是這樣的(原圖來自《http權威指南》,個人有塗鴉):

新鮮度檢測
Cache-Control與Expires 是新鮮度檢測的關鍵:

Cache-Control與Expires的作用一致,都是指明當前資源的有效期。只不過Cache-Control的選擇更多,設置更細緻,如果同時設置的話,其優先級高於Expires。
詳細一點說明的話,Expires是http/1.0定義的資源有效期,它是一個歷史遺留產物。它的值是一個絕對值,它顯示指定了一個日期作為過期時間。然而經過一段時間實踐之後,Bug就出來了:當客戶端可服務器時間不一致,那麼到底怎麼處理呢?

Cache-Control是http/1.1定義的,功能很多,但是核心的我認為還是還是改變了資源有效期的表達方式,我認為最大的意義是Cache-Control是Expires的bugfix產物(作為bugfix優先級當然會更高)。它使用max-age來根據最後時間加上這個max-age動態計算了過期時間,這樣就不存在Expires的Bug了。

Cache-Control可選值:(偷懶摘自《翻譯:web製作、開發人員需知的Web緩存知》)

max-age=[秒]:表示在這個時間範圍內緩存是新鮮的無需更新。類似Expires時間,不過這個時間是相對的,而不是絕對的。也就是某次請求成功後多少秒內緩存是新鮮的。
s-maxage=[秒]:類似max-age, 除了僅應用於共享緩存(如代理)。
public:標記認證的響應才能夠被緩存。一般而言,需要認證HTTP請求內容會自動私有化(不會被緩存Add)。


private:允許緩存專門為某一個用戶存儲響應,比方說在瀏覽器中;共享緩存一般不會,例如在代理中。
no-cache:每次在釋放緩存副本之前都強制發送請求給源服務器進行驗證,這在確保認證有效性上很管用(和public結合使用)或者保證內容必須是即時的,不得無視緩存的所有優點,如國內的微博、twitter等的刷新顯示Add。
no-store:強制緩存在任何情況下都不要保留任何副本。
must-revalidate:告訴緩存,我給你準備了一些關於新鮮度的信息,在表現的時候要嚴格遵循之。HTTP允許緩存在某些特定情況下返回過期數據,指定了這個屬性,相對於告訴緩存,你丫必須嚴格遵循我的規則。
proxy-revalidate:類似must-revalidate,除了只能應用於代理緩存。
個人注:理想的情況下,Cache-Control與Expires可以都標明以達到最優兼容,但是這不是必須都有的,多數可以只有其中一個也不會有什麼大問題(如果引起BUG就另說了),但是——不要兩個都沒有,不然會引起相對詭異的現象,如果本文的例子。
——但是你硬要這麼幹,確實可以兩個都沒有.

資源二次校驗
Last-Modified/ETag 這兩個數據用於確定數據是否改變
Last-Modified嘛,很好理解,文件最後修改時間。如果這個東西那麼好用的話其實Etag不過畫蛇添足罷了,但是很可惜的Last-Modified的時間度量單位是秒——這意味著一秒內的修改服務器分辨不出來——股票行情繫統和金融系統對此有利益攸關的需求。


所以就有了ETag,這個東西就是類似文件hash的東西,在服務器上用於分辨文件是不是同一個。

Last-Modified與ETag是可以一起使用的,服務器會優先驗證ETag。一致的情況下,才會繼續比對Last-Modified,最後才決定是否返回304

個人注:如果你想用304,Last-Modified和ETag是很重要的屬性。如果你還想用304,那麼就不要刪除它們(刪除其中一個會不會有影響個人還未驗證)。

新鮮度校驗的補充
試探性過期(本章摘自HTTP權威指南Page193)
如果響應中沒有 Cache-Control: max-age 首部,也沒有 Expires 首部,緩存可 以 計算出一個試探性最大使用期。可以使用任意算法,但如果得到的最大使用期大於24小時,就應該向響應首部添加一個 Heuristic Expiration Warning首部。據我們所知,很少有瀏覽器會為用戶提供這種警告信息。
LM-Factor 算法是一種很常用的試探性過期算法,如果文檔中包含了最後修改日期, 就可以使用這種算法。LM-Factor算法 將最後修改日期作為依據,來估計文檔有多麼易變。算法的邏輯如下所示。
• 如果已緩存文檔最後一次修改發生在很久以前,它可能會是一份穩定的文檔,不太會突然發生變化,因此將其繼續保存在緩存中會比較安全。
• 如果已緩存文擋最近被修改過,就說明它很可能會頻繁地發生變化,因此在與服 務器進行再驗證之前,只應該將其緩存很短一段時間。

這段描述已經解決了個人的疑惑了,簡單說算法猜你近期沒有改的不會突然改了,反之認為你會突然改。在《HTTP權威指南》內有簡單的公式和圖例,有興趣的可以自行前往翻閱。

個人注:《HTTP權威指南》有這樣一段話:

HTTP 有一組非常複雜的新鮮度檢測規則,緩存產品支持的大量配置選項,以及與 非 HTTP 新鮮度標準進行互通的需要則使問題變得更加嚴重了。本章其餘的大部分 篇幅都用於解釋新鮮度的計算問題。

所以,這個新鮮度校驗是非常複雜的,遠不是網上大多數文章提到的那麼簡略,如果以後在新鮮度校驗這裡出現疑問,不妨翻一翻。

結尾
問題到這裡算是解決了,解決方案很簡單,加上了Cache-Control和Expires。但是這次的服務器錯配的導致的問題的探索,著實是很大程度上提高了個人對HTTP緩存的理解。


分享到:


相關文章: