前端靜態資源緩存最優解以及max-age的陷阱
合理的使用緩存可以極大地提高網站的性能優勢,還可以節約帶寬從而降低伺服器成本。但是很多站點有隻弄對了一半或者一半都沒有,如果是這樣,就完全沒有發揮出緩存的優勢。很大程度上產生會由於靜態資源的競爭關係而導致依賴的靜態資源不同步。
以下為兩個最佳靜態資源緩存實踐的例子。
一、資源內容不變 + 設置長時間max-age
資源的內容不會更改,所以。。。
瀏覽器/CDN可以緩存一年時間,在這期間資源不會出現問題。
可以在不請求伺服器的請情況下,一年內都使用緩存內容。
第一天
瀏覽器請求了、以及這三個資源。
第二天
這次瀏覽器請求了、以及這三個資源。
此處注意:和與第一天請求的版本號不同。
過了一年
在一年的時間裡,瀏覽器再也沒有請求過、以及這三個資源,瀏覽器緩存就會把它們給刪掉。
所以在這個例子中,為了讓緩存發揮最大效率,你要做的並不是更改文件的內容,而是應該更改資源的URL:
每一個靜態資源URL都應該跟隨其內容的修改而改變。例如示例中的,你對它的命名不需要有任何限制。它可以是一個,,或者根據內容計算出來的。
絕大多數伺服器端的框架都提供了工具來實現這一點,同樣的在nodejs中有很多優秀的庫來實現這個功能,比如gulp-rev、webpack、fis3。
二、對於經常修改的內容,始終需要進行伺服器認證
該URL下資源的內容可能經常修改,所以。。。
沒有伺服器的確認,任何本地緩存的版本內容都是不可信的。
第一天
第二天
注意:
並不意味著不緩存。它的意思是在使用緩存資源之前,它必須經過伺服器的檢查(也可以實現這個功能)。
才是告訴瀏覽器不要緩存它。此外,並不意味著,它的前提是資源還在的緩存期內,否則必須重新認證。
在此模式下 ,你也可以將或者日期添加到響應首部中。客戶端下次獲取資源時,他會分別通過(與ETage對應)和(與Last-Mofied對應)兩個請求首部將值發送給伺服器。如果伺服器發現兩次值都是對等的,就是返回一個。
如果沒有發送和,那麼伺服器將始終返回完整的資源內容。
但是這種方法有個缺點,就是它每次都會去伺服器做一次驗證,涉及到了網路提取,所以它不如第一個例子那樣可以完全繞過網路。
三、在經常修改內容的靜態資源上使用max-age是個錯誤的選擇
這種情況並不少見,例如它就實實在在地發生在了github的頁面上。
想像一下 :
/article/
/styles.css
/script.js
它們全部使用的是:
隨著內容的修改,URLs發生改變
在十分鐘內,瀏覽器將會一直使用緩存住的內容,而不會去伺服器請求最新的資源 。
超過十分鐘,在可用的前提下使用和重新進行伺服器認證。
第一次請求:
六七分鐘過後:
最終:
這種情況在測試中經常出現。但是想像一下,在線上環境你永遠不知道瀏覽器前面坐著的是什麼樣的人,他很有可能無意中胡亂地用滑鼠點點點,就打亂了瀏覽器的靜態資源緩存機制,導致頁面發生了錯亂,而且真的很難追蹤。
在上面的例子中,伺服器實際上已經更新了HTML、CSS和JS,但是頁面最後使用的是緩存中舊的HTML和JS,以及剛從伺服器下載的最新的CSS。多個靜態資源版本之間不匹配的問題隨之出現。
通常,當我們對HTML進行重大修改時,我們可能會更改CSS文件來適配新的DOM結構,並且更新JS來配置樣式和DOM的修改。這些資源都是相互依賴的,但可不管你這些有的沒的。最終,用戶很有可能會得到一個/兩個靜態資源新版本,而其他資源都是舊版本。
max-age是相對於伺服器響應時間的,所以如果所有上述資源都在同一時間請求,即便它們都被設置為了相同的max-age時長,它們仍然存在很小的競爭可能性(畢竟有的資源先返回有的資源後返回)。如果你的某些頁面不包含JS,或者包含不同的CSS,它們的緩存失效時間就有可能會不同步。更噁心的是,瀏覽器始終會從緩存中刪除和獲取資源,它並不知道這些資源中哪個是相互依賴的,只要過了緩存時間它就會毫不猶豫地刪掉一個,並不會刪掉這個過期文件所依賴的其他資源。把上面的種種可能性加在一起,就會大概率出現靜態資源版本不匹配的問題。
不過還好,我們還有法子來解決這個問題:
強制刷新瀏覽器或者清除緩存
在強制刷新瀏覽器或者清除緩存後,請求的頁面以及頁面內的所有資源會忽略之前的,去伺服器做重新認證。因此,如果用戶由於max-age出現問題之後,只需要強制刷新或者清緩存就可以修復問題。當然,強迫用戶這樣做只會讓它們降低對你網站的信任度,認為你的網站不靠譜。。。
使用serviceWorker減少這種錯誤的出現幾率
service Worker的執行時機:
註冊serviceWorker:
執行serviceworker.js:
將script和styles緩存起來。
如果有匹配到的緩存就從緩存中獲取,如果沒有就從伺服器獲取。
如果我們修改了JS/CSS,只需修改就可以讓service worker觸發更新。
你也可以在service worker中跳過緩存:
不過很不巧的是,選項在和safari和opera中都不支持 ,只有firefox和chrome最近才開始支持。但是你可以這樣做:
你可以使用上面代碼中的隨機字元串,也可以使用散列值。這有點像在javascript中實現文章剛開始第一小節的方法,不過僅僅是在server worker中使用。
四、service worker和HTTP cache也可以很好的共存
通過上個的例子,你可以看到service worker可以很好的處理一些糟糕的緩存情況。但是僅僅是做一些hack處理而已,最重要的是再根源上解決問題。正確的使用緩存不僅可以更好地使用service worker,還可以很好地在那些不支持service worker的瀏覽器(IE/Safari/Opera)上提高網站的性能。除此之外,對你的CDN也是大有益處。
正確的使用緩存,可以大量簡化service worker的代碼:
所以,我們可以使用第二小節的方法(伺服器重新認證)來緩存根HTML頁面。並使用第一小節的方法(不同的內容使用不同的URL)來緩存其他資源。每次service worker更新世都會去請求網站的根HTML頁面,其他資源只有在更改URL時才會去下載,從而提高網站的性能。
雖然service worker擅長提高網站的性能,但它並不是一個完整的解決方案。因此要和HTTP cache配合使用才可以顯著地提高性能。
五、max-age和『內容經常修改但是URL不變的靜態資源』搭配使用
在內容經常修改但是URL不變的靜態資源上使用在通常意義上來說不是一個好點子,但事實卻不總是如此。
假如一個頁面的為三分鐘,並且在這個頁面上不需要考慮靜態資源的競爭關係(靜態資源之間存在相互依賴,見第三小節),所以在這個頁面上不存在任何的靜態資源依賴。在這種情況下就可以盡情使用。不過這也意味著網站的修改要再三分鐘之後才可以被看到。
不過要是頁面存在靜態資源競爭關係的話,這種法子不好用了,比如我現在有兩個文章A和B,我現在文章A中添加一個新的章節,然後在文章B中增加了一個指向文章A新增章節的超鏈接。然後我從文章B中訪問這個鏈接,假如文章A的max-age沒有過期,那麼我訪問到的文章A里將會發現文章並沒有那個新增的章節。此時只能等過期或者強制刷新瀏覽器,再或者清除緩存了。所以,一定要謹慎使用這種方法。
正確使用緩存可以代理巨大的性能收益並且有效節省伺服器帶寬。既支持版本號類型的靜態資源緩存方式也支持伺服器重新認證(no-cache、304)的方式。如果你覺得自己很勇敢,那麼大可混合使用max-age和『內容經常修改但是URL不變的靜態資源』,但是前提你得確定自己的HTML中沒有靜態資源競爭關係。


TAG:Web手藝人 |