本文深入探討了分散式應用快取的概念、實現方式、策略以及最佳實踐,詳細介紹了主要的快取模式,並討論了快取驅逐策略及今後的發展。原文: Mastering Caching in Distributed Applications
快取似乎是一種你覺得可以做對,但卻永遠做不對的東西。這是有原因的,畢竟快取(或快取失效)是電腦科學中最難解決的兩個基礎問題之一(另一個問題是變數的命名)。
不管是不是開玩笑,快取確實很難做好,尤其是在大型分散式應用中。因此,團隊通常會透過迭代和實驗來調整快取策略和實施,直到有希望在某個時候將其調整到某種合理的半最佳化狀態。
本文將揭開快取的神秘面紗,澄清其中一些經常被忽略或誤解的問題。
希望讀完這篇文章後,你能更清楚的瞭解什麼是快取、快取的主要方法、需要注意的事項以及各種快取技術在實際案例中的應用。
因此,不再贅述....
什麼是快取?
簡而言之,快取就是將資料儲存在臨時介質中,在這種介質中檢索資料比從原始儲存(記錄系統)中檢索資料更便宜、更快捷或更最佳化。
換句話說,想象一下下面的用例。
訂單管理系統需要從庫存系統中檢索產品資訊。假設庫存系統性能不佳,每次收到請求時,都必須去中央資料庫獲取產品資訊。該資料庫速度很慢,無法支援過多的並行請求。
爲了提高效能並減輕庫存資料庫的壓力,我們引入快取層,在其中儲存相同的產品資訊。現在,我們不再透過笨重的資料庫來訪問庫存系統,而是先訪問快取,如果資料在快取中,就從那裏獲取資料。
這裏所做的是引入一種臨時儲存介質(快取),以提高效能並最佳化原始資料庫的資源使用。
什麼是"快取"?
人們開始感到困惑的一點是快取的技術性質。
當我們聽到"快取"一詞時,軟件開發領域的大多數人都會產生非常特殊的聯想。我們通常會把這個詞與分散式快取產品聯絡起來,比如 Redis、Memcached 或 EHCache。在其他時候,我們會想到瀏覽器快取、資料庫快取、作業系統快取,甚至硬體快取。
這正是問題的關鍵所在。快取的概念並不侷限於電腦科學領域的某一特定產品或領域。從最廣泛的意義上講,"快取"實際上是我們從某個記錄系統中複製資料到任何型別的臨時介質。之所以這樣做,是因為將資料儲存在臨時介質中在某種程度上是有利的。
造成這種情況的典型原因是快取記憶體比原始儲存成本更低、效能更好或可擴充套件性更強。
看一下前面的訂單管理和庫存系統的例子,快取層理論上可以是很多東西:
分散式快取產品(例如 Redis)
另一個自帶資料庫的微服務
實際庫存管理系統中的記憶體儲存
儘管每個方案的實現方式各不相同,但上述所有方案都符合快取記憶體的標準。
簡單來說,上述所有東西都可以成為快取。快取作為一個概念,可以在(事實上也正在)計算機系統的各個層級實現,並橫跨許多數字領域。
一些術語
在繼續討論之前,有必要了解一下圍繞快取這一主題的不同術語。
記錄系統(System of Record):儲存資料的永久儲存器,最有可能是資料庫,也稱為真實源(source-of-truth)系統。
快取缺失(Cache Miss):當應用程式在快取中查詢特定記錄,但快取中並不存在。
快取命中(Cache Hit):記錄確實存在於快取中,並以此返回。
快取汙染(Cache Pollution):當快取中充滿未使用或未查詢的值時。
快取驅逐(Cache Eviction):從快取中刪除條目以釋放記憶體的過程。
資料新鮮度(Data Freshness):快取中的記錄與底層記錄系統的同步程度。
快取過期(Cache Expiration):根據時間刪除快取記錄,這是驅逐過程的一部分,也是快取失效的一部分,將在下文討論。
既然我們已經完全掌握了快取術語,那麼就讓我們深入瞭解一下快取的一些實施位置和層級。
快取在哪裏實施?
正如前面已經提到的,快取的應用遍及整個技術領域--在各個層面和許多不同的技術棧中。
在硬體層面,快取是 CPU 架構的一部分,例如,以 1-3 級(L1/L2/L3)快取的形式出現。
在作業系統核心層面,有一種磁碟快取形式被稱為頁面快取。當然,還有其他形式。
對於基於網路的系統,當然有瀏覽器快取和 CDN(內容分發網路)。它們分別在客戶端或 CDN 端快取常用的靜態資源(圖片、樣式表等)。這樣做的目的是減少頻寬,並快速、高效、低成本的向用戶提供這些資源。
不同型別的應用程式和中介軟體也有自己的快取。例如,資料庫使用快取來儲存經常使用的查詢和經常返回的結果集。
當然,還有許多強大的軟體快取產品,如 Redis、EHCache、Memcached、Hazelcast、Infinispan 等,可以在分散式應用中實現可擴充套件的分散式快取。
需要強調的一點是,"分散式"快取記憶體的概念也可以與"本地"或"本地化"快取記憶體相比較。分散式快取是一種分佈在網路上多個裝置上的快取形式,本地快取只存在於一臺裝置上。
要理解這兩個概念之間的區別,最好的辦法是想象一個應用程式部署在一個伺服器叢集中。換句話說,應用程式有多個例項同時執行,這對於任何從事大型應用程式工作的人來說都不陌生。
如果在這樣的系統中引入分散式快取記憶體,那麼任何一個應用程式例項都可以訪問該快取記憶體,並修改其中的記錄。
另一方面,如果使用本地快取,每個例項都將擁有自己的快取--很可能就在該例項的記憶體中。不同例項無法訪問另一個例項的快取,而只能訪問自己的快取。
兩種方法各有利弊。
一方面,如果有多個例項訪問快取,可能需要解決同步問題、競爭條件、資料損壞以及分散式應用帶來的其他挑戰。另一方面,共享快取是一個強大的概念,可以幫助應用程式處理本地快取(儘管更簡單)無法處理的用例。
例如,你可能在多個可用區內的雲環境中部署應用,每個可用區都可能有一個執行應用程式的虛擬機器例項群集,每個叢集很可能都有自己的分散式快取。原因是分散式快取的前提之一是能夠快速高效的訪問,這意味著快取記憶體與所服務的例項之間的網路距離(不一定是物理距離,也可以是虛擬距離)要夠近。
同時,分散式快取和本地快取都面臨著一些共同的挑戰。
主要挑戰在於如何在保持資料新鮮度、在最佳化快取失效和驅逐之間保持平衡,以及如何使快取管理方式與特定用例相匹配。
接下來,我們將討論管理快取--快取模式這一非常重要的概念。
與軟體工程中的大多數決策一樣,每種方法都有其利弊,下面我們將討論每種方法的利弊。
本地和分散式快取系統模式
主要有五種快取模式,都與快取的讀取、寫入以及與底層記錄系統同步的方式有關。
旁路快取(Cache-Aside)
旁路快取策略可能是最流行的策略,也是大多數軟體工程師所熟悉的策略,這種快取方法完全由應用程式來控制快取的寫入和讀取。應用既要控制何時從資料庫或快取讀取資料,也要控制何時向資料庫或快取寫入資料。
下面以一個例子來說明如何操作。
想象一下,應用接收到使用者登入請求,並隨之獲取使用者的郵寄地址。
應用首先會檢查快取中是否存在使用者地址。
如果沒有該使用者的地址條目,應用將從資料庫中獲取資料。
但是,如果資訊存在於快取中,就會立即檢索到該資料,從而節省了訪問資料庫的時間。
獲取新資訊後,應用也會將資料寫入快取。
在第 2 步中,如果快取中沒有該特定條目,通常被稱為"快取缺失"。
優點
實施簡單
完全由應用控制
因為只有在需要時纔會獲取快取項(懶載入),因此記憶體使用最小(至少理論上如此)
缺點
由於必須從速度較慢的儲存區獲取資料,快取缺失的延遲會更高。快取缺失次數過多,效能可能會受到影響
應用邏輯變得更加複雜(儘管總體構思很容易實現)
何時使用
當你想完全控制快取的填充方式時
當你沒有能夠管理資料庫讀/寫的快取產品時
當快取記憶體的訪問模式不規則時
直寫快取(Write-Through Caching)
透過直寫快取可確保快取與底層持久化資料儲存之間的一致性。換句話說,當寫入發生時,它會在同一個事務中同時傳播到快取和資料庫中。
下面舉例說明:
財務應用收到用新餘額更新使用者賬戶的請求。
資料庫和快取中都存在使用者賬戶餘額。
資料庫和快取都會在同一事務中更新新值。
又有一個請求出現了,這次是要讀取使用者餘額。我們首先在快取中查詢並使用該值。由於快取中有最新的值,因此不必擔心該值與底層資料庫不同步。
請注意,第 3 步可以透過應用邏輯完成。但通常情況下,實際的快取產品會承擔這一責任。例如,如果使用的是 EHCache 或 Infinispan,那麼應用將更新 Redis 快取,而 Redis 快取又可配置為更新資料庫。
優點
確保快取與底層資料儲存之間的一致性
缺點
事務複雜性,需要某種兩階段提交邏輯,以確保快取記憶體和資料庫的更新(如果不受快取記憶體控制的話)
操作複雜性,如果其中一項出現故障,需要從容應對使用者體驗
寫入速度會變慢,因為需要更新兩個地方(快取和資料儲存),而不是一個地方(資料儲存)。
何時使用
直寫快取非常適合那些要求資料一致性強、不能提供陳舊資料的應用程式。通常用於資料寫入後必須立即準確更新的環境。
繞寫快取
這種策略會填充底層儲存,但不會填充快取本身。換句話說,寫操作繞過快取,只寫入底層儲存。這種技術與Cache-Aside有一些重疊。
不同之處在於,使用 Cache-Aside 時,重點是讀取和懶載入--只有在首次從資料儲存中讀取資料時纔將其填充到快取中。而使用 Write-Around 快取時,重點則是寫入效能。當資料被頻繁寫入但不被頻繁讀取時,這種技術通常用於避免快取汙染。
優點
減少快取汙染,因為每次寫入都不會填充快取
缺點
如果某些記錄經常被讀取,效能就會受到影響,因此,如果能主動載入到快取中,就能避免在首次讀取時訪問資料庫。
何時使用
當寫入量較大但讀取量明顯較小時,通常會使用這種方法。
回寫(背寫)快取(Write-Back (Write-Behind) Caching)
寫操作首先填充快取,然後寫入資料儲存。這裏的關鍵是,寫入資料儲存是非同步進行的,因此不需要兩階段的事務提交。
Write-Behind 快取策略通常由快取產品處理。如果快取產品有這種機制,應用將向快取寫入內容,然後快取產品將負責向資料庫傳送更改內容。如果快取產品不支援這種機制,應用程式本身將觸發對資料庫的非同步更新。
優點
寫入速度更快,因為系統只需在初始事務中寫入快取。資料庫將在稍後時間更新。
如果流量由快取產品處理,那麼應用邏輯的複雜性就會降低。
缺點
在資料庫接收到新更改之前,資料庫和快取記憶體可能會不同步,因此可能會出現不一致。
當快取最終嘗試更新資料庫時,可能會出現錯誤。如果出現這種情況,就需要有更復雜的機制來確保資料庫接收到最新的資料。
何時使用
當寫入效能非常重要,而且資料庫中的資料與快取中的資料暫時稍有不同步是可以接受的時候,就可以使用回寫快取。適用於寫入量大但一致性要求不那麼嚴格的應用。例如,CDN(內容分發網路)可用於快速更新快取內容,然後將其同步到記錄系統。
直讀(Read-Through)快取
直讀快取在某種意義上類似於旁路快取模式,因為在這兩種模式中,快取都是我們首先查詢記錄的地方。如果快取未命中,就會在資料庫中查詢。不過,在旁路快取模式中,查詢快取和資料庫的責任都落在了應用程式身上,而在直讀快取中,這一責任則落在了快取產品身上(如果它有這種機制的話)。
優點
簡單--所有邏輯都封裝在快取應用程式中
缺點
快取缺失時從資料庫讀取資料的潛在延遲。需要複雜的資料更新失效機制。
何時使用
當你想簡化訪問資料的程式碼時,就會使用直讀快取。此外,當你想確保快取始終包含來自資料儲存的最新資料時,也可以使用直讀快取。這對於讀取資料比寫入資料更頻繁的應用程式非常有用。但這裏的關鍵點是,快取產品應該能夠透過配置或本地方式從底層記錄系統中執行讀取操作。
快取策略總結
以下是我們就五種快取模式所做的總結。
旁路快取
只有當應用程式提出請求,但在快取中找不到資料時,纔會按需將資料載入到快取中。
真實場景:按需快取產品詳細資訊的電子商務網站。
負責 DB 操作:應用程式
直寫快取
每次寫操作都會同時寫入快取和底層資料儲存,以保持一致性。
真實場景:可使交易賬戶餘額保持一致的銀行系統。
負責 DB 操作:快取產品或應用程式
回寫快取
寫入首先記錄在快取記憶體中,然後非同步寫入資料儲存。
真實案例:CDN 先更新快取中的內容,然後同步到儲存系統。
負責 DB 操作:快取產品或應用程式
繞寫快取
寫操作會繞過快取,直接更新資料儲存,從而避免快取不急需的資料。
真實案例:日誌操作,日誌直接寫入儲存,無需快取。
負責 DB 操作:應用程式
直讀快取
快取是讀取資料的主要介面。如果快取中缺少資料,則會從記錄系統中獲取並快取。
真實案例:使用者配置檔案服務在快取缺失時獲取和快取用戶數據。
負責 DB 操作:快取產品或應用程式
快取失效
我們已經瞭解了填充快取的不同方法,接下來還需要了解如何使快取與底層記錄系統保持同步。
說到快取失效,主要有基於時間和基於事件的兩種方法。基於時間的失效方法可以透過大多數快取產品中的"存活時間"(TTL)設定來控制。基於事件的方法要求應用程式或其他裝置將新記錄傳送到快取。
快取的問題在於,資料幾乎總是至少與底層資料儲存(記錄系統)稍有不同步。換句話說,資料會變得陳舊。爲了儘可能保持快取與記錄系統同步,需要實施某種快取失效策略。
換句話說,我們需要確保快取中資料的"新鮮度"。
快取失效會導致新記錄從記錄系統中被檢索到快取中。因此,瞭解快取失效與上面討論的快取策略之間的關係非常重要。
快取策略與如何從快取中載入和檢索資料有關,而快取失效則更多的與記錄系統和快取之間的資料一致性和新鮮度有關。
因此,這兩個概念之間存在一些重疊,對於某些快取策略來說,失效比其他策略更簡單。例如,使用"透過快取寫入"方法時,快取會在每次寫入時更新,因此無需額外實現。但是,刪除可能不會被反映,因此可能需要應用邏輯來顯式處理。
有兩種方法可以使快取條目失效:
事件驅動
使用事件驅動方法,應用程式將在底層記錄儲存發生變化時通知快取。無論同步還是非同步,每次記錄發生變化時,都會向快取發出通知。
這可以透過應用程式完成,程式碼負責保持快取的更新。或者,某些快取產品可能具有釋出/訂閱功能,快取產品可以訂閱這些型別的通知。在這種情況下,應用程式的工作量可能會減少。不過,仍然需要一些東西來產生通知事件。
基於時間
在基於時間的方法中,所有快取記錄都有一個與之相關的 TTL(生存時間)。記錄的 TTL 過期後,該快取記錄將被刪除。這通常由快取產品控制。
快取驅逐策略
快取驅逐與快取失效類似,都是刪除舊的快取記錄。但兩者區別在於,當快取已滿,無法再容納任何記錄時,就需要進行快取驅逐。
請記住,快取的目的是儲存最常訪問記錄的子集。而不是複製整個真實源系統。因此,快取的大小通常要比資料庫/真實源/記錄系統中儲存的資料小很多。
因此,需要一種可以"驅逐"或換句話說刪除記錄的機制。
與此同時,還需要確保從那些應用最不需要的記錄開始,否則快取的全部意義都將化為烏有。
為確保以最佳方式驅逐記錄,可以利用多種驅逐策略:
最近最少使用 (LRU,Least Recently Used)
透過這種方法,可以刪除很久沒有使用的記錄。
何時使用:適用於資料即將被訪問的可能性隨上次訪問記錄後時間的推移而降低的情況。這非常適合通用快取,因為訪問的週期是未來訪問的重要指標。
何時不宜使用:不適合資料訪問模式與週期無關的工作負載。
先進先出(FIFO,First In First Out)
將先於其他記錄儲存在快取中的記錄驅逐出去。
何時使用:適用於資料年齡比訪問頻率更重要的快取。適用於快取可預測使用時間的資料。
何時不宜使用:對於可能仍需頻繁訪問舊資料的工作負載而言,這不是最佳選擇。
最不常用 (LFU,Least Frequently Used)
刪除不經常使用/訪問的記錄。
何時使用:最適合長期頻繁訪問資料的情況。適用於具有穩定訪問模式的應用程式。
何時不宜使用:在訪問模式會發生顯著變化的環境中,效果較差。不常訪問的專案會汙染快取。
生存時間 (TTL)
根據預先確定的離開時間進行驅逐。
何時使用:適用於在一段時間後變得過時或陳舊的資料。
何時不宜使用:不適用於有效性不會隨時間自然失效的資料,以及基於其他因素需要無限期保留在快取中的資料。
隨機替換
隨機驅逐記錄。
何時使用:用於複雜追蹤機制得不償失的情況,或者訪問模式不可預測,因此其他驅逐策略不適合的情況。
何時不宜使用:在大多數實際情況下,當訪問模式或多或少可預測時,其效率一般低於其他策略。
總結
我們已經討論過快取在分散式應用中的重要性以及選擇正確快取策略的關鍵性。目前有許多流行的策略:
旁路快取(Cache-aside)
直寫快取(Write-through cache)
直讀快取(Read through cache)
回寫快取(Write behind cache)
繞寫快取(Write Around)
還討論了使用基於時間或事件驅動的方法進行快取失效的問題。
我們指出了快取驅逐的重要性以及為此可採取的策略。這些策略是:
LRU
FIFO
LFU
TTL
隨機
最後,快取可以是本地快取,也可以是分散式快取。前者僅限於單臺機器/應用例項,後者跨越多臺機器,通常(但不一定)侷限於一個例項叢集。
希望這篇文章能讓你清楚瞭解什麼是快取、為什麼快取很重要,以及在使用快取技術時必須瞭解的所有不同術語和細微差別。
快取:未來
與其他技術一樣,市場上的快取產品也出現了巨大創新。一些值得關注的亮點包括:
與邊緣計算整合
隨著邊緣計算的不斷髮展,快取策略變得更加分散,將資料轉移到網路邊緣需要的地方。接近使用者降低了資料服務的延遲、頻寬和成本,這對於物聯網和移動應用等實時應用至關重要。
案例:自動駕駛汽車使用邊緣計算在本地處理實時資料。在邊緣節點快取地圖和交通狀況等關鍵資料,有助於快速做出決策,而不會出現查詢中央伺服器的延遲。
人工智慧和機器學習驅動的快取
人工智慧和機器學習可以預測資料使用模式,並根據預期需求預先快取資料,從而增強快取機制。這種積極主動的方法可以顯著提高效率,尤其是在資料訪問模式經常變化的動態環境中。
案例:亞馬遜利用機器學習預測使用者行為,並預先快取使用者在黑色星期五等高峰期可能購買的產品。這樣可以縮短載入時間,從而提升使用者體驗。
記憶體資料網格(IMDG,In-Memory Data Grids)
IMDG 正在迅速發展成為一種強大的快取解決方案,可提供跨分散式系統的低延遲複雜資料訪問。IMDG 不僅能快取資料,還能直接在快取層中提供一系列數據處理、實時分析和決策功能。
案例:高頻交易平臺利用 IMDG 在記憶體中快取市場資料和交易訂單。這樣可以快速訪問和處理,這對於做出亞秒級的交易決策至關重要。