背景
在2024年國(guó)慶前后,大A行情出現(xiàn)了一波高潮,國(guó)慶期間利好消息影響韭菜情緒造成擁堵開(kāi)戶的場(chǎng)景。對(duì)網(wǎng)絡(luò)和后端造成巨大的壓力,同時(shí)也影響用戶體驗(yàn),這種突發(fā)情況一般是網(wǎng)絡(luò)組會(huì)采用擴(kuò)容的方式進(jìn)行應(yīng)對(duì),但由于內(nèi)部硬件資源不充裕、資源調(diào)配不均勻,所以需要前端同事一起進(jìn)行優(yōu)化。
我這邊最終組織團(tuán)隊(duì)決定使用Service Worker對(duì)產(chǎn)品部分高頻調(diào)用的接口(如行情、指標(biāo)、基礎(chǔ)信息等)進(jìn)行緩存。理由是此種方式對(duì)于產(chǎn)品的源碼改動(dòng)最小化,通過(guò)前端緩存策略,可以有效減少重復(fù)請(qǐng)求、提升響應(yīng)速度。
本文結(jié)合實(shí)際 Service Worker 源碼,介紹如何對(duì)高頻接口進(jìn)行緩存,并兼容 GET/POST 場(chǎng)景。
方案概述
- GET 請(qǐng)求:采用 Cache Storage,結(jié)合緩存有效期與最大緩存條數(shù)控制。
- POST 請(qǐng)求:采用 IndexedDB,解決 Cache Storage 僅支持 GET 的限制,支持請(qǐng)求體作為緩存 key。
關(guān)鍵實(shí)現(xiàn)
1. Service Worker 攔截與分流
self.addEventListener('fetch', function (event) {
const resUrl = event.request.url;
if (resUrl.indexOf('finance/stock/maintable/index_merge') > -1) {
const expires = 1 * 60 * 1000;
handleServerCahce(event, 'INDXEX_MERGE', expires);
}
if (event.request.method === 'POST' && resUrl.indexOf('statistic/component/ptext') > -1) {
handlePostWithIDBCache(event, 'P_TEXT_POST');
}
});
- 通過(guò) URL 關(guān)鍵字精準(zhǔn)匹配高頻接口。
- GET/POST 分別調(diào)用不同緩存處理函數(shù)。
2. GET 接口緩存(Cache Storage)
function handleServerCahce(event, cache_name, expires, cache_length = 10) {
event.respondWith(
caches.match(event.request).then(function (response) {
if (response) {
const responseDate = response.headers.get('Date');
const FiveMinutesLater = new Date(responseDate).getTime() + expires;
if (Date.now() <= FiveMinutesLater) {
return response;
}
}
var fetchRequest = event.request.clone();
return fetch(fetchRequest).then(function (response) {
if (!response || response.status !== 200) return response;
var responseToCache = response.clone();
caches.open(cache_name).then(function (cache) {
checkCacheSize(cache, cache_length);
cache.put(event.request, responseToCache);
});
return response;
});
})
);
}
- 命中緩存且未過(guò)期直接返回,未命中或過(guò)期則請(qǐng)求網(wǎng)絡(luò)并更新緩存。
- 支持自定義緩存有效期和最大緩存條數(shù)。
3. POST 接口緩存(IndexedDB)
function handlePostWithIDBCache(event, storeName) {
event.respondWith((async () => {
const cacheKey = await event.request.clone().text();
const cached = await idbGet(storeName, cacheKey);
if (cached) {
return new Response(cached.body, cached.options);
}
const response = await fetch(event.request.clone());
if (response && response.status === 200) {
const body = await response.clone().text();
const options = {
status: response.status,
statusText: response.statusText,
headers: Array.from(response.headers.entries())
};
await idbSet(storeName, cacheKey, { body, options });
}
return response;
})());
}
- 以請(qǐng)求體為 key,解決 POST 請(qǐng)求冪等性和參數(shù)多樣性問(wèn)題。
- 通過(guò) IndexedDB 存儲(chǔ)和讀取緩存,突破 Cache Storage 限制。
4. IndexedDB 簡(jiǎn)單封裝
function idbGet(store, key) {
return new Promise((resolve, reject) => {
const request = indexedDB.open('sw-db', 1);
request.onupgradeneeded = function (event) {
const db = event.target.result;
if (!db.objectStoreNames.contains(store)) {
db.createObjectStore(store);
}
};
request.onsuccess = function (event) {
const db = event.target.result;
const tx = db.transaction(store, 'readonly');
const storeObj = tx.objectStore(store);
const getReq = storeObj.get(key);
getReq.onsuccess = function () {
resolve(getReq.result);
};
getReq.onerror = function () {
resolve(undefined);
};
};
request.onerror = function () {
resolve(undefined);
};
});
}
function idbSet(store, key, value) {
return new Promise((resolve, reject) => {
const request = indexedDB.open('sw-db', 1);
request.onupgradeneeded = function (event) {
const db = event.target.result;
if (!db.objectStoreNames.contains(store)) {
db.createObjectStore(store);
}
};
request.onsuccess = function (event) {
const db = event.target.result;
const tx = db.transaction(store, 'readwrite');
const storeObj = tx.objectStore(store);
storeObj.put(value, key);
tx.oncomplete = function () {
resolve();
};
tx.onerror = function () {
resolve();
};
};
request.onerror = function () {
resolve();
};
});
}
- 支持動(dòng)態(tài)建表,異步讀寫(xiě),便于擴(kuò)展和維護(hù)。
前端頁(yè)面注冊(cè) Service Worker
在你的 HTML 文件中添加如下腳本,確保 Service Worker 能被正確注冊(cè)和激活:
<script>
if ("serviceWorker" in navigator) {
window.addEventListener("load", function () {
navigator.serviceWorker.register("./sw.js").then(
function (registration) {
console.log("ServiceWorker registration successful");
},
function (err) {
console.error("ServiceWorker registration failed: ", err);
}
);
});
}
</script>
實(shí)踐建議
- 緩存粒度:僅對(duì)高頻、數(shù)據(jù)量適中的接口做緩存,避免緩存過(guò)大或數(shù)據(jù)一致性風(fēng)險(xiǎn)。
- 緩存上限:根據(jù)應(yīng)用內(nèi)存具體情況(瀏覽器、客戶端webview)設(shè)置數(shù)據(jù)大小上限,防止無(wú)限膨脹。
- 緩存失效:合理設(shè)置 expires,防止數(shù)據(jù)陳舊。
- 調(diào)試與監(jiān)控:可通過(guò)瀏覽器 DevTools Application 面板監(jiān)控緩存命中與存儲(chǔ)情況。
本方案通過(guò) Service Worker 攔截高頻接口請(qǐng)求,結(jié)合 Cache Storage 和 IndexedDB,實(shí)現(xiàn)了 GET/POST 場(chǎng)景下的高效緩存。既提升了用戶體驗(yàn),也減輕了后端壓力,適用于大部分前端高頻數(shù)據(jù)場(chǎng)景,實(shí)際項(xiàng)目可根據(jù)接口特性靈活調(diào)整緩存策略。
?轉(zhuǎn)自https://juejin.cn/post/7519688537466880010
該文章在 2025/9/6 17:02:27 編輯過(guò)