靜態(tài)資源文件的緩存問題

前一陣被人問到一個(gè)問題:

開發(fā)人員修改一文件,版本下發(fā)后期望用戶可以訪問到修改的最新文件,而不是被瀏覽器緩存過的歷史文件,請問Http有機(jī)制可以保證用戶訪問到最新的文件嗎?如果沒有,在考慮性能的前提下,如何設(shè)計(jì)一種可行方案呢?

相信不少人第一直覺會(huì)想到和瀏覽器緩存有關(guān)的一些緩存頭,例如:

  • 與請求內(nèi)容新鮮度有關(guān)的:expires,cache-control
  1. expires指定了文檔的失效時(shí)間,但是前提要求客戶端和服務(wù)器端的時(shí)鐘是同步的,不然就不準(zhǔn)確了
  2. cache-control頭比實(shí)際想象的要復(fù)雜的多,cache-control:no-cache表明不應(yīng)使用緩存文件,而應(yīng)該直接從服務(wù)器重新獲取,cache-control:max-age=3600表明從服務(wù)器將文檔傳來之時(shí)起,可以認(rèn)為此文檔處于新鮮狀態(tài)的秒數(shù)。
  • 與條件請求有關(guān)的頭,If-Modified-Since,If-None-Match,Last-Modified,Etag。
    瀏覽器認(rèn)定文檔新鮮度過期后,需要重新請求服務(wù)器,此時(shí)可以附帶一些條件參數(shù),例如文檔最近一次修改的時(shí)間,文檔的實(shí)體標(biāo)記etag值,服務(wù)器會(huì)拿請求報(bào)文中的值與服務(wù)器中保存的值進(jìn)行比較,如果兩者一致,表明文檔還可以繼續(xù)使用,此時(shí)以304(文檔未修改)狀態(tài)碼作為回應(yīng),否則將新的內(nèi)容返回客戶端。

我們把問題細(xì)化一下,修改的文件存在兩種情況:

  1. 該文件的內(nèi)容是需要?jiǎng)討B(tài)填充的,這時(shí)緩存的策略為不緩存,每次請求都去服務(wù)器重新驗(yàn)證

  2. 對于靜態(tài)文件的修改,舉幾個(gè)例子看看:
    下面這個(gè)是github頁面上公共圖標(biāo)的緩存情況,cache-control配置了一個(gè)很大的失效時(shí)間,同時(shí)結(jié)合last-modified頭實(shí)施緩存策略。


    github頁面上公共圖標(biāo)

下面這個(gè)是知乎中個(gè)人頭像的緩存情況,可以看到采用了cache-control和etag控制緩存


知乎個(gè)人頭像

現(xiàn)在的問題是:上述圖標(biāo)要是發(fā)生了改變,用戶瀏覽器如何才能及時(shí)得到更新呢?

因?yàn)閏ache-control配置了一個(gè)很大的失效時(shí)間間隔,在用戶本地存在緩存的情況下,瀏覽器是不會(huì)再次發(fā)起請求的

  • 對于github的圖標(biāo)還好理解,因?yàn)槭蔷W(wǎng)站公共的圖標(biāo),被更改的頻率會(huì)很小,在這種背景下,可能在下一次用戶請求該網(wǎng)站時(shí),用戶瀏覽器已經(jīng)不存在此網(wǎng)站的緩存了,所以是可以更新到最新狀態(tài)的。

  • 對于知乎用戶頭像的緩存策略,初看起來似乎很矛盾,用戶更改頭像是隨時(shí)可能會(huì)發(fā)生的事情,如何在用戶頭像更改之后網(wǎng)站內(nèi)容可以及時(shí)更新呢?仔細(xì)想想,其實(shí)我們的擔(dān)心是多余的,用戶上傳新的頭像后,系統(tǒng)會(huì)給新頭像分配新名稱,這樣在用戶重新請求主頁面時(shí),動(dòng)態(tài)填充的內(nèi)容已經(jīng)發(fā)生了變化,服務(wù)器會(huì)返回新的主頁面給瀏覽器,瀏覽器解析到了新的用戶頭像連接,由于在瀏覽器緩存中并沒有找到對應(yīng)的緩存文件,所以瀏覽器會(huì)針對新的用戶頭像發(fā)起Http請求,進(jìn)而得到最新的用戶頭像

圖片和樣式文件的更改一般不會(huì)給網(wǎng)站帶來災(zāi)難性的影響,但如果是js文件被修改但是用戶瀏覽器依舊使用的是過期的緩存文件,這種情況相比較而言對網(wǎng)站的影響就要大得多。

如何避免此類問題呢?結(jié)合知乎個(gè)人頭像的例子,不難想到的一種方案就是對修改的腳本文件添加一個(gè)修改的標(biāo)志,類似下面這個(gè)樣子

<script src="dir/test.js?modify=true"></script>

如果頻繁修改呢,下面這種方式似乎給好一點(diǎn)
<script src="dir/test.js?version=2.0"></script>

上面的方案都是基于script標(biāo)簽的,在模塊化大行其道的今天,腳本加載器應(yīng)該是會(huì)考慮諸如此類實(shí)際問題的,例如在seajs中有下面的配置功能

seajs.config({ vars: { 'version': '2' } });
define(function(require, exports, module) {
var lang = require('./dir/test.js?version={version}');
});

考慮一下現(xiàn)實(shí)吧,假設(shè)文件A在系統(tǒng)中很重要,因此存在大量文件引用,如果還采用上述的方案,這無疑是煩人的體力勞動(dòng),如何解脫呢?

總體的方案是:

在動(dòng)態(tài)請求的文件中給靜態(tài)文件動(dòng)態(tài)添加類似于版本號的標(biāo)志,然后對服務(wù)器配置url重寫功能(例如apache服務(wù)器),在java中可以配置過濾器,對特定的文件進(jìn)行url重寫。

下面給出stackoverflow上一個(gè)基于php的實(shí)現(xiàn)方案,原文在這里

+ 首先,在apache的配置文件.htaccess中開啟重寫功能,并且添加規(guī)則
RewriteEngine on RewriteRule ^(.*)\.[\d]{10}\.(css|js)$ $1.$2 [L]

  • 給文件追加mtime標(biāo)志

function auto_version($file){ if(strpos($file, '/') !== 0 || !file_exists($_SERVER['DOCUMENT_ROOT'] . $file)) return $file; $mtime = filemtime($_SERVER['DOCUMENT_ROOT'] . $file); return preg_replace('{\\.([ ^./]+)$}', ".$mtime.\$1", $file); }

  • 實(shí)際使用

<script href="<?php echo auto_version('/js/base.js'); ?> />

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容