1.web緩存
????緩存的作用就是提升網頁加載速度。瀏覽器加載一個完整的網頁勢必會引用外部資源(圖片,js,css)。若每次加載網頁都要去加載這些外部資源則會引起不必要的時間和資源浪費,且會影響用戶體驗。而解決上述問題需要一個優秀的緩存策略。除此之外,web緩存的優點還有很多,例如:減輕服務器壓力 ,加快了客戶端加載速度,節省網絡帶寬等。
web緩存按緩存位置,緩存機制可大致分為三類。
- 1 數據庫緩存。
- 2 服務器緩存。
- 3 客戶端(瀏覽器)緩存/HTTP緩存。
下面著重介紹HTTP緩存。
2.HTTP緩存
????HTTP緩存就是將靜態資源存儲在客戶端本地,下次請求該資源時直接使用本地資源,而不必從服務端加載。而當服務端的靜態資源更新時,本地緩存資源也要更新。當然這需要一系列策略進行約定。
2.1 強緩存策略
所謂強緩存策略即在靜態資源有效期內,使用該資源時直接使用本地資源,不請求服務器。
2.1.1 HTTP1.0 expires
????在HTTP1.0中,服務器使用expires字段規定過期時間,可以在客戶端資源請求的Response Headers 中增加 expires 字段表示資源的過期時間。它是一個時間戳,當客戶端再次請求該資源的時候,會把客戶端時間與該時間戳進行對比,如果大于該時間戳則已過期,否則直接使用該緩存資源。下面用一個實例驗證,使用node.js搭建一個web服務,使用定時器創建一個隨時間變化的內容。
const http = require("http");
let time = null;
function updateTime() {
setInterval(() => (time = new Date().toUTCString()), 1000);
}
updateTime();
// time每5秒更新一次
http
.createServer((req, res) => {
const { url } = req;
if ("/" === url) {
res.end(`
<html>
<!-- <meta charset="UTF-8"> -->
Html Update Time: ${time}
<script src='main.js'></script>
</html>
`);
} else if (url === "/main.js") {
const content = `document.writeln('<br>JS Update Time:${time}')`;
// HTTP1.0 強緩存
res.setHeader('Expires', new Date(Date.now() + 5 * 1000).toUTCString())
res.statusCode = 200;
res.end(content);
} else if (url === "/favicon.ico") {
res.end("");
}
})
.listen(3000, () => {
console.log("服務已啟動" + 3000);
});
????上面代碼實現的效果是,每次刷新頁面,HTML時間一秒鐘變化一次,而JS時間由于設置了強緩存因此每5秒變化一次。可以訪問http://localhost:3000進行驗證。
????HTML時間和JS時間不一致,說明強緩存生效。但這種強緩存方式存在一些問題,由于發送請求時是使用客戶端時間進行對比,因此一方面是客戶端和服務端時間可能不一致,另一方面是客戶端的時間(系統時間)是可以自行修改的,因此可能出現服務器資源與本地緩存資源不一致的問題。
2.1.2 HTTP1.1 cache-control
????鑒于使用expires字段可能出現的問題,HTTP1.1 新增了 cache-control 字段來解決該問題,所以當 cache-control 和 expires 都存在時,cache-control 優先級更高。cache-control 主要有 max-age 和 s-maxage、public 和 private、no-cache 和 no-store 等值。max-age字段是一個時間長度,單位秒,表示該資源過了多少秒后失效。當客戶端請求資源的時候,發現該資源還在有效時間內則使用該緩存,它不依賴客戶端時間。
下面使用該字段設置強緩存
// HTTP1.0 強緩存
res.setHeader('Expires', new Date(Date.now() + 5 * 1000).toUTCString())
// HTTP1.1 強緩存 cache-control字段 cache-control 優先級更高
res.setHeader('Cache-Control', 'max-age=10')
可以看到強緩存生效且cache-control字段優先級比expires高。
鑒于上述強緩存的特點,強緩存的應用場景一般是用于需要定期更新的內容。
2.2 協商緩存
????上面所述的強緩存會直接訪問本地緩存,沒過期的話不會請求服務器,直接使用本地緩存。而協商緩存,顧名思義,需要和服務器協商一下,看資源是否需要更新。若協商結果是需要更新則會返回更新的內容。若結果是不需要則只返回304狀態碼,這樣可以有效減輕服務器壓力。協商緩存的方式主要有以下兩種。
2.2.1 last-modified & if-Modified-Since
該方式的協商過程為:
1 服務器進行靜態資源應答時會通過last-modified字段來標示修改時間。
2 瀏覽器下次請求相同資源會將last-modified時間作為if-modified-since字段的值放在請求報文中用以詢問服務器是否該資源過期。
3 服務器需要通過規則判斷是否過期。
4 過期時直接返回200并在body中放入更新內容。
5 如果未過期則直接返回304狀態碼。
下面進行實例驗證
// 協商緩存
// 方式一 last-modified & if-Modified-Since 通過協商修改時間為基礎的策略
// 首先需要禁用強緩存
res.setHeader('Cache-Control', 'no-cache')
res.setHeader('last-modified', new Date().toUTCString())
// 設置過期時間為5秒
if (new Date(req.headers['if-modified-since']).getTime() + 5 * 1000 > Date.now()) {
console.log('協商緩存命中....')
res.statusCode = 304
res.end()
return
}
2.2.2 etag & if-None-Match
????第二種方式是通過內容判斷,一般的做法是將返回內容使用Hash函數進行消息摘要,然后通過對比摘要來判斷內容是否需要更新。其協商過程為:
1 服務器進行靜態資源應答時通過etag來標示內容摘要。
2 瀏覽器下次請求相同資源會將etag作為if-none-match字段放在請求報文中用以詢問服務器是否該資源過期。
3 服務器需要通過和服務器內容的摘要進行比對確定是否過期。
4 過期時直接返回200并在body中放入更新內容。
5 如果未過期則直接返回304狀態碼。
下面進行實例驗證
res.setHeader("Cache-Control", "no-cache");
const crypto = require("crypto"); // nodejs的一個加密模塊
// createHash 創建并返回一個 Hash 對象,該對象可用于生成哈希摘要 digest 字符編碼
const hash = crypto.createHash("sha1").update(content).digest("hex");
res.setHeader("Etag", hash);
if (req.headers["if-none-match"] === hash) {
console.log("Etag協商緩存命中.....");
res.statusCode = 304;
res.end();
return;
}
????可以看到由于該策略是通過判斷內容來決定是否需要更新,因此HTML時間和JS時間會保持一致。因為時間每秒更新一次因此在一秒內刷新頁面時會命中協商緩存。因此鑒于協商緩存的特點,其一般用于非定期更新的內容,需要客戶端發送請求詢問服務器是否需要更新。