前端:你應該知道的緩存策略
緩存是所有工程師都必須知道的非常有用的軟件組件。它是一個橫向組件,適用于所有技術領域和架構層,如操作系統(tǒng)、數(shù)據(jù)平臺、后端、前端和其他組件。在本文中,我們將描述什么是緩存,并針對前端和客戶端解釋具體用例。
什么是緩存?
緩存可以以基本方式定義為數(shù)據(jù)消費者和數(shù)據(jù)生產(chǎn)者之間的中間存儲器,用于存儲和提供將被相同/不同消費者多次訪問的數(shù)據(jù)。除了提高性能外,它在用戶可用性方面對數(shù)據(jù)消費者來說是一個透明層。 通常,數(shù)據(jù)生產(chǎn)者提供的數(shù)據(jù)的可重用性是利用緩存優(yōu)勢的關鍵。性能是使用內(nèi)存數(shù)據(jù)庫等緩存系統(tǒng)來提供具有低延遲、高吞吐量和并發(fā)性的高性能解決方案的另一個原因。
例如,每天有多少人查詢天氣,他們會重復查詢多少次?假設紐約有 1,000 人查詢天氣,其中 50% 的人每天重復相同的查詢兩次。在這種情況下,如果我們可以將第一個查詢存儲在盡可能靠近用戶設備的位置,我們將獲得兩個好處:增加用戶體驗,因為數(shù)據(jù)提供得更快,并減少對數(shù)據(jù)生產(chǎn)者/服務器端的查詢次數(shù)。輸出是更好的用戶體驗和支持更多并發(fā)用戶使用該平臺的解決方案。
天氣查詢場景
在高層次上,我們可以以互補的方式應用兩種緩存策略:
Client/Consumer Side:緩存的數(shù)據(jù)存儲在消費者或用戶端,當我們談論 Web 解決方案時,通常在瀏覽器的內(nèi)存中(也稱為私有緩存)。
服務器/生產(chǎn)者端:緩存的數(shù)據(jù)存儲在數(shù)據(jù)生產(chǎn)者架構的組件中。
客戶端和服務器端
與任何其他解決方案一樣,緩存具有一系列優(yōu)勢,我們將對其進行總結:
- 應用程序性能:提供更快的響應時間,因為可以更快地提供數(shù)據(jù)。
- 減少服務器端的負載:當我們將緩存應用到以前的系統(tǒng)并重用一段數(shù)據(jù)時,我們正在避免查詢/請求到下一層。
- 可擴展性和成本改進:隨著數(shù)據(jù)緩存越來越接近消費者,我們以更低的成本提高了解決方案的可擴展性和性能。
靠近客戶端的組件更具可擴展性和更便宜,因為三個主要原因:
- 這些組件側重于性能和可用性,但一致性較差。
- 他們只有部分信息:用戶使用更多的數(shù)據(jù)。
- 在瀏覽器的本地緩存的情況下,數(shù)據(jù)生產(chǎn)者沒有成本。
成本、性能和一致性圖
緩存的最大挑戰(zhàn)是數(shù)據(jù)一致性和數(shù)據(jù)新鮮度, 這意味著數(shù)據(jù)如何在整個組織內(nèi)同步和更新。根據(jù)用例,我們會有或多或少的要求限制,因為它與緩存圖像相比與庫存或銷售行為有很大不同。
客戶端緩存
談到客戶端緩存,我們可以有不同類型的緩存,我們將在本文中稍微分析一下:
- HTTP 緩存:這種緩存類型是一種中間緩存系統(tǒng),因為它部分取決于服務器。
- 緩存 API:這是一個瀏覽器 API,允許我們在瀏覽器中緩存請求。
- 自定義本地緩存: 前端應用控制緩存存儲、過期、失效和更新。
緩存
它在瀏覽器中緩存對任何資源(CSS、HTML、圖像、視頻等)的 HTTP 請求,并從前端管理所有與存儲、過期、驗證、獲取等相關的內(nèi)容。應用程序的觀點幾乎是透明的,因為它以常規(guī)方式發(fā)出請求并且瀏覽 器執(zhí)行所有“魔術”。
緩存
控制緩存的方法是使用 HTTP Headers,在服務器端,它向 HTTP 響應添加特定于緩存的標頭,例如:“Expires: Tue, 30 Jul 2023 05:30:22 GMT”,然后是瀏覽器知道這個資源可以被緩存,下次客戶端(應用程序)請求同一個資源時,如果請求時間在過期日期之前,請求將不會完成,瀏覽器將返回該資源的本地副本。
它允許您設置響應的偽裝方式,因為相同的 URL 可以生成不同的響應(并且它們的緩存應該以不同的方式處理)。例如,在返回一些數(shù)據(jù)的 API 端點中,我們可以使用請求標頭來Content-type指定我們是否需要 JSON 或 CSV 等格式的響應。因此,緩存應根據(jù)請求標頭與響應一起存儲。為此,服務器應該設置響應標頭Vary: Accept-Language,讓瀏覽器知道緩存取決于該值。有很多不同的標頭來控制緩存流和行為,但深入研究它不是本文的目標。它可能會在另一篇文章中解決。
正如我們之前提到的,這種緩存類型需要服務器設置資源過期、驗證等。所以這不是一種純粹的前端緩存方法或類型,但它是緩存前端應用程序使用的資源的最簡單方法之一,它是我們將在下面提到的另一種方式的補充。
與這種緩存類型相關,由于它是中間緩存,我們甚至可以將其委托給客戶端和服務器之間的“一塊”;例如,CDN、反向代理(例如 Varnish)等。
HTTP 緩存的優(yōu)點和缺點
緩存接口
它與 HTTP 緩存方法非常相似,但在這種情況下,我們控制哪些請求被存儲或從緩存中提取。我們必須管理緩存過期(這并不容易,因為這些緩存被認為“永遠存在”)。即使這些 API 在窗口上下文中可用,也非常適合它們在 worker 上下文中的使用。
該緩存非常適合用于離線應用程序。在第一次請求時,我們可以獲取并緩存它需要的所有資源(圖像、CSS、JS 等),從而允許應用程序離線工作。它在移動應用程序中非常有用,例如,除了天氣數(shù)據(jù)之外,我們的 GPS 系統(tǒng)還可以使用地圖。這使我們即使沒有連接到服務器也能獲得遠足路線的所有信息。
它如何在窗口上下文中工作的一個示例:
const url = ‘
https://catfact.ninja/breeds’caches.open('v1').then((cache) => {
cache.match((url).then((res) => {
if (res) {
console.log('it is in cache')
console.log(res.json())
} else {
console.log('it is NOT in cache')
fetch(url) .then(res => {
cache.put('test', res.clone())
})
}
})
})
緩存 API 優(yōu)缺點
自定義本地緩存
在某些情況下,我們 需要更多地控制緩存數(shù)據(jù)和失效(不僅僅是過期)。緩存失效不僅僅是檢查max-age緩存條目。
想象一下我們上面提到的天氣應用程序。該應用程序允許用戶更新天氣以反映某個地方的真實天氣。該應用程序需要針對每個城市執(zhí)行請求并將溫度值從華氏度轉換為攝氏度(這是一個簡單的示例:在其他用例中計算成本可能更高)。
自定義本地緩存
為了避免向服務器做請求(即使它被緩存),我們可以一次做所有的請求,把所有的數(shù)據(jù)放在一個我們方便的數(shù)據(jù)結構中,并存儲在,例如在瀏覽器的IndexedDB中,在LocalStorage、SessionStorage 甚至在內(nèi)存中(不推薦)。下次我們要顯示數(shù)據(jù)時,我們可以從緩存中獲取它,而不僅僅是資源數(shù)據(jù)(甚至是我們所做的計算),節(jié)省網(wǎng)絡和計算時間。
我們可以通過在API后面加上發(fā)布時間來控制緩存的過期,也可以控制緩存的失效。現(xiàn)在想象一下,用戶在其瀏覽器中添加了一只新貓。我們可以讓緩存失效,下次再做請求和計算,或者更進一步,用新數(shù)據(jù)更新我們的本地緩存。或者,另一個用戶可以更改該值,服務器將發(fā)送一個事件以將更改通知給所有客戶端。例如,使用WebSockets,我們的前端應用程序可以聽到這些事件并使緩存無效或只更新緩存。
客戶到供應商系統(tǒng)
這種緩存需要我們這邊的工作來檢查緩存并處理可能使其失效或更新的事件等,但非常適合六邊形架構,其中使用端口適配器(存儲庫)從 API 使用數(shù)據(jù)可以聽到域事件以對更改做出反應并使某些緩存無效或更新。
自定義本地緩存優(yōu)缺點
這不是緩存通用解決方案。我們需要考慮它是否適合我們的用例,因為它需要在前端應用程序端工作以使緩存無效或發(fā)出和處理數(shù)據(jù)更改事件。在大多數(shù)情況下,HTTP 緩存就足夠了。
結論
擁有緩存解決方案和良好的策略應該是任何軟件架構中必須的, 但我們的解決方案將是不完整的,并且可能沒有優(yōu)化。 緩存是我們最好的朋友,主要是在高性能場景中。看起來技術失效緩存過程是挑戰(zhàn),但 最大的挑戰(zhàn)是了解業(yè)務場景和用例,以確定在數(shù)據(jù)新鮮度和一致性方面的要求,使我們能夠設計和選擇最佳策略。