詳解緩存:緩存中存在的挑戰(zhàn)及策略
在計算機世界中,緩存(caching)就是將數(shù)據(jù)子集儲存到一個具備高度可訪問性的高速運行層的過程,這一層被稱作高速緩沖存儲器(cache)。
此過程旨在快速讀取使用率較高的數(shù)據(jù),避免在存取之前的數(shù)據(jù)時產(chǎn)生額外的計算負(fù)擔(dān)。緩存只能儲存數(shù)據(jù)一小段時間,這是權(quán)衡容量后的選擇,以此換取更高的運行速度。
像隨機存取存儲器(RAM)和內(nèi)存存儲引擎這類在緩存層下的硬件能夠?qū)崿F(xiàn)快速存取,它們通常和軟件層一起被用于訪問數(shù)據(jù)。緩存基本上分為兩種:本地緩存和遠程緩存。本地緩存依靠JVW(Java虛擬機)堆進行儲存,而遠程(或集群)緩存使用內(nèi)存存儲器,如Redis和Memcached。
什么是堆內(nèi)本地緩存?
堆內(nèi)緩存指把數(shù)據(jù)存儲于Java堆中,在這里數(shù)據(jù)由垃圾收集器(GC)自動管理。
堆內(nèi)緩存的優(yōu)點:
- GC會自動分配和釋放對象
- 訪問數(shù)據(jù)的速度更快
堆內(nèi)緩存的不足:
- GC停頓多發(fā)
- 因為數(shù)據(jù)被存儲于JVM存儲器之中,如果JVM崩潰了,數(shù)據(jù)就會丟失。因此無法長期緩存。
什么是堆外本地緩存?
堆外緩存指把數(shù)據(jù)存儲在堆外。垃圾收集器不會自動處理這些數(shù)據(jù),因為數(shù)據(jù)被存儲在Java堆外,所以它們以字節(jié)數(shù)組存儲,因此也存在把數(shù)據(jù)序列化和反序列化的額外運行負(fù)擔(dān)。
堆外緩存的優(yōu)點:
- 允許大量數(shù)據(jù)的緩存且無須擔(dān)心GC停頓
- JVM崩潰后支持在存儲器中添加持久層以恢復(fù)數(shù)據(jù)
- JVM之間可共享緩存數(shù)據(jù)
堆外緩存的不足:
- 數(shù)據(jù)的序列化和反序列化是使用堆外緩存時最大的不便之處。這會為下層程序帶來計算負(fù)擔(dān)。因為沒有共同的數(shù)據(jù)結(jié)構(gòu),把序列化的數(shù)據(jù)轉(zhuǎn)換成單獨對象將付出額外的成本。
- 短期數(shù)據(jù)更適合堆內(nèi)緩存,因為它允許GC自動運行。因而,識別哪些數(shù)據(jù)可以被歸于堆內(nèi)緩存會帶來額外的計算。
- 手動存儲器管理(像存儲器分段之類的問題!)
總之,因為堆外緩存能夠長期存儲大量數(shù)據(jù),所以它是存儲數(shù)據(jù)的一種更好方式。再加上大磁盤子系統(tǒng),就能提高每秒讀寫次數(shù)(IOPS)。
什么是遠程緩存?
遠程緩存是將數(shù)據(jù)存儲在云端的緩沖區(qū)。因為可以在云端檢索數(shù)據(jù),所以這有助于構(gòu)建一個更堅固且性能更強的持久層。Redis和Memcached是當(dāng)下兩款大受歡迎的內(nèi)存緩存產(chǎn)品。
圖源:unsplash
遠程緩存的優(yōu)勢:
- 遠程緩存集群可以根據(jù)需求進行擴展
- 遠程緩存不僅僅局限于單一數(shù)據(jù)結(jié)構(gòu),并且它支持多語言編程,因而操作簡單。
- 與磁盤的低速存取相比,性能有所增強(因為數(shù)據(jù)存儲在存儲器中,存取數(shù)據(jù)速度更快)
如何確定系統(tǒng)/服務(wù)需要緩存
- 緩存命中率:如果服務(wù)所提供的數(shù)據(jù)不需要經(jīng)常刷新且屬于經(jīng)常檢索型數(shù)據(jù),應(yīng)當(dāng)考慮緩存它們。
- 最終一致性的容限:仔細(xì)考慮源數(shù)據(jù)的變化率,以及緩存的刷新頻率。還應(yīng)該考慮服務(wù)對象是否重視最近數(shù)據(jù)的讀取。
緩存策略類型和相應(yīng)挑戰(zhàn)
圖源:unsplash
(1) 本地緩存:
通過在服務(wù)(比如說哈希表)內(nèi)使用某種存儲方式將使本地緩存的執(zhí)行更加容易,但也會導(dǎo)致緩存一致性問題。意思就是,不同服務(wù)器中的本地緩存不同,這將導(dǎo)致數(shù)據(jù)不一致。
例如,服務(wù)器S1用數(shù)據(jù)D1響應(yīng)了請求R1,并把它存儲到本地緩存中。如果在數(shù)據(jù)庫中的數(shù)據(jù)被更新成D2版本,之后R1再次發(fā)出相同的請求,則有可能返回數(shù)據(jù)D1或D2,這取決于請求抵達了哪個服務(wù)器。
冷啟動也是內(nèi)存緩存的一個重要問題。因為每一個服務(wù)器都是在沒有緩存的情況下啟動的,隨著新服務(wù)器的增加,甚至在部署期間,對下游依賴發(fā)出的請求數(shù)量也會隨之變多。這個問題可以通過請求合并解決。
(2) 外部緩存:
- 能夠解決以上問題,因為外部緩存是分開存儲的,例如Redis和Memcached。
- 提供更多存儲空間并減少因容量而導(dǎo)致的緩存淘汰。
- 挑戰(zhàn)包括提高整體系統(tǒng)的復(fù)雜性和增加更多負(fù)載以維護額外的緩存服務(wù)器。
- 始終在服務(wù)中添加代碼來解決緩存可能不可用的情況。
- 可以在此期間呼叫下游服務(wù)。但如果緩存中斷時間過長,可能導(dǎo)致下游服務(wù)的負(fù)載增加。
- 或者,外部存儲器和內(nèi)部存儲器一起使用可以避免完全回落到下游依賴。
- 也可以考慮采取減載技術(shù),通過限制服務(wù)的請求數(shù)量來避免下游服務(wù)負(fù)載過重。
(3) 應(yīng)對緩存擴展和彈性問題:
如果緩存達到最大容量,那么需要通過向其添加更多節(jié)點來擴展。深入了解系統(tǒng)及其在達到最大容量時的反應(yīng)(例如,緩存達到最大容量時每個容器的內(nèi)存利用率上升)有助于設(shè)置準(zhǔn)確的警報。
這些警報可用于擴展緩存服務(wù)。在擴展服務(wù)時,需要記住兩件事:緩存集群是否支持在沒有宕機的情況下添加節(jié)點,或者是否支持一致性哈希算法來平衡流量分配。始終確保通過模擬故障來測試擴展策略。
(4) 控制數(shù)據(jù)的魯棒性
緩存數(shù)據(jù)應(yīng)能夠讀取更新后的代碼格式,而更新后的代碼應(yīng)能夠處理緩存所提供的舊版本數(shù)據(jù)。
緩存實現(xiàn)要點
圖源:unsplash
(1) 緩存大?。焊鶕?jù)服務(wù)中通過的數(shù)據(jù)類型可以確定緩存大小,以便提高緩存命中率。
(2) 緩存淘汰:這指的是當(dāng)緩存達到最大容量時,把數(shù)據(jù)從緩存中移除。緩存淘汰最常見的模式是LRU(最近最少使用)。
(3) 緩存逾期:這是一種確定數(shù)據(jù)在緩存中的保留時間的策略,具體取決于數(shù)據(jù)刷新頻率或客戶處理過時數(shù)據(jù)的能力。
(4) 下游服務(wù)不可用
如果下游服務(wù)因為某些原因不可用,緩存服務(wù)不應(yīng)該用更新數(shù)據(jù)的請求攻擊下游,而是能夠長期保護緩存,并等待它恢復(fù)。根據(jù)與客戶之間的權(quán)衡協(xié)調(diào),要么報告緩存中的過時數(shù)據(jù),避免請求令下游服務(wù)掉線,要么使用一種機制來存儲下游服務(wù)的錯誤響應(yīng),并對其進行相應(yīng)的解釋說明。
(5) 安全性
正在緩存的敏感數(shù)據(jù)或客戶數(shù)據(jù)的安全性是人們對緩存集群的擔(dān)憂之一。敏感數(shù)據(jù)應(yīng)在存儲前加密,并在輸入和輸出緩存數(shù)據(jù)的過程中確保其安全性。此外,由于緩存數(shù)據(jù)的返回速度比從數(shù)據(jù)庫獲取的調(diào)用速度快,攻擊者可以根據(jù)響應(yīng)時間識別服務(wù)發(fā)出的請求類型,這被稱為側(cè)信道時序攻擊。
(6) “驚群效應(yīng)”問題
下游服務(wù)不可用時,如果為了從下游服務(wù)獲取未緩存的數(shù)據(jù)而發(fā)出多個請求,則可能會引發(fā)多次重試,進而導(dǎo)致服務(wù)掉線??梢越Y(jié)合多個策略來緩解這個情況,例如按客戶或請求來進行節(jié)流、請求合并(即對相同的未緩存數(shù)據(jù)只發(fā)送一個請求)等。
緩存可以提供更快的數(shù)據(jù)訪問速度和提高下游服務(wù)可用性,但它的代價是緩存節(jié)點的處理會更加復(fù)雜。通過巧妙地理解下游服務(wù)的需求,可以得出一個緩存解決方案。在不同的情景中(如流量峰值、緩存不可用、下游服務(wù)掉線等)都應(yīng)當(dāng)對此解決方案的表現(xiàn)進行仔細(xì)監(jiān)控以調(diào)整參數(shù)。
希望本文能夠幫你理解那些在為服務(wù)部署緩存解決方案時要牢記于心的要點。