后臺開發(fā)必備:每個(gè)程序員都應(yīng)掌握的緩存技術(shù)
來源:湖北國菱計(jì)算機(jī)科技有限公司-湖北國聯(lián)計(jì)算機(jī)科技有限公司-荊州網(wǎng)站建設(shè)-荊州軟件開發(fā)-政府網(wǎng)站建設(shè)公司
時(shí)間:2025-03-25
緩存(Cache)是一種存儲技術(shù),可以存儲數(shù)據(jù),以便快速獲取數(shù)據(jù)。緩存最重要的是兩個(gè)特性:存儲、快速獲取。緩存的本質(zhì):「用空間換時(shí)間」,用快速存儲的介質(zhì)保存數(shù)據(jù),以提升數(shù)據(jù)訪問的速度。
在計(jì)算機(jī)的世界里,緩存被廣泛應(yīng)用于硬件(如CPU 高速緩存、寄存器)和軟件中(如瀏覽器緩存、CDN、應(yīng)用程序中的緩存等)。
后臺中的緩存
后臺開發(fā)中,緩存的使用場景一般有:
ü 提升接口響應(yīng)速度:緩存相比IO 請求、數(shù)據(jù)庫查詢速度要快得多,適當(dāng)使用緩存提升響應(yīng)速度
ü 降低數(shù)據(jù)源服務(wù)的負(fù)載壓力:將需要高并發(fā)讀取的數(shù)據(jù)緩存起來,當(dāng)相同請求進(jìn)來時(shí)返回緩存數(shù)據(jù),減輕數(shù)據(jù)源服務(wù)(如數(shù)據(jù)庫、上游系統(tǒng))的負(fù)載壓力
ü 減少計(jì)算資源的浪費(fèi):將需要復(fù)雜且耗時(shí)計(jì)算的結(jié)果緩存起來,減少相同請求導(dǎo)致的重復(fù)計(jì)算資源浪費(fèi)
01
緩存策略
緩存策略通常分為兩種,通讀緩存和旁路緩存。
1.1 通讀(read-through)緩存
在使用通讀緩存時(shí),應(yīng)用程序會嘗試從緩存中獲取數(shù)據(jù)。如果該數(shù)據(jù)已存在于緩存中,那么緩存會直接返回該數(shù)據(jù)。若緩存中并未包含所需數(shù)據(jù),那么緩存會自行訪問數(shù)據(jù)源來獲取數(shù)據(jù),并將獲取的數(shù)據(jù)返回給應(yīng)用程序,同時(shí)將這份數(shù)據(jù)保存在緩存中。如此一來,當(dāng)應(yīng)用程序下次需要同樣的數(shù)據(jù)時(shí),就能夠直接從read-through 緩存中獲取無需再次訪問數(shù)據(jù)源。
通讀緩存的應(yīng)用有很多,比如CDN:
CDN(Content Delivery Network)即內(nèi)容分發(fā)網(wǎng)絡(luò),是一種分布式的網(wǎng)絡(luò)架構(gòu)。依賴于服務(wù)商提供的廣泛分布于各地的 CDN 服務(wù)器,通過把數(shù)據(jù)分發(fā)并緩存到各地的 CDN 服務(wù)器上,當(dāng)用戶請求數(shù)據(jù)時(shí),CDN 會匹配離用戶距離最近的節(jié)點(diǎn)并返回緩存數(shù)據(jù),以此來提高用戶訪問資源的速度。CDN 一般用來加速靜態(tài)資源的傳輸,比如圖片、CSS、HTML 等內(nèi)容。而動態(tài)的內(nèi)容,如搜索結(jié)果、單據(jù)信息等則不合適,
這類數(shù)據(jù)需要應(yīng)用服務(wù)器實(shí)時(shí)計(jì)算后才能返回。

1.2 旁路(cache-aside)緩存
與通讀緩存不同,旁路緩存不直接與數(shù)據(jù)源打交道。應(yīng)用程序嘗試從旁路緩存獲取數(shù)據(jù),如果數(shù)據(jù)不存在,則返回空。應(yīng)用程序自行請求數(shù)據(jù)源獲取數(shù)據(jù),并寫入旁路緩存。這樣下次相同的請求到達(dá)時(shí),應(yīng)用程序從旁路緩存中則會獲取到數(shù)據(jù)。
通讀緩存和旁路緩存這兩種策略,一般來說都會靈活應(yīng)用、互相包含。比如通讀緩存中,對數(shù)據(jù)的查詢和寫緩存,使用的就是旁路緩存策略。而該實(shí)現(xiàn)被整體封裝起來,對外表現(xiàn)的則是通讀緩存策略。
02
緩存類型
在后臺服務(wù)中,緩存的類型可以分為本地緩存和分布式緩存。
2.1 本地緩存(local cache)
本地緩存,與應(yīng)用程序的進(jìn)程有相同的生命周期,存放于應(yīng)用程序的堆空間(heap)中。
ü 優(yōu)勢:使用簡單;無外部依賴;讀取速度快(無網(wǎng)絡(luò)IO 請求);
ü 缺點(diǎn):空間?。簯?yīng)用程序的服務(wù)器資源有限,所以本地緩存的空間?。环植际揭恢滦詥栴}:如果后臺服務(wù)是分布式架構(gòu)的,那么不同的服務(wù)實(shí)例之間的本地緩存可能會有差異;無法持久化:本地緩存會隨著進(jìn)程結(jié)束而被銷毀,無法持久化。
2.2 分布式緩存(remote cache)
分布式緩存,也可理解為遠(yuǎn)端緩存。使用外部的緩存服務(wù),獨(dú)立部署,與應(yīng)用程序解藕。
ü 優(yōu)勢:空間充足:外部存儲一般空間都很充足;無分布式一致性問題:不同的服務(wù)實(shí)例連接同一個(gè)緩存服務(wù),不存在一致性問題;主流的分布式緩存,如redis,支持?jǐn)?shù)據(jù)持久化和恢復(fù),當(dāng)緩存服務(wù)掛了可以恢復(fù)數(shù)據(jù)。
ü 缺點(diǎn):引入外部依賴、需要部署和運(yùn)維單獨(dú)的緩存服務(wù)。
2.2.1 常見的緩存服務(wù)
Redis
ü 支持多種數(shù)據(jù)結(jié)構(gòu),如:list,set,zset,hash 等數(shù)據(jù)結(jié)構(gòu)。
ü 支持?jǐn)?shù)據(jù)的持久化和恢復(fù)。
ü 支持多種集群模式:主從復(fù)制、哨兵模式和Cluster 模式,保證高可用、容災(zāi)恢復(fù)、易于擴(kuò)展。
ü 能提供毫秒級別的響應(yīng)速度。
ü Redis 6.0 引入了多線程 IO,之前是單線程模型。
Memcache
ü 主要支持簡單的鍵值對存儲。
ü 不支持持久化和恢復(fù)。
ü 集群:可以通過客戶端分片實(shí)現(xiàn)分布式存儲。
ü 多線程,非阻塞IO 模式。
ü 能提供毫秒級別的響應(yīng)速度。
選擇Redis 還是 Memcached 取決于具體的應(yīng)用需求。如果需要一個(gè)輕量級、專注于緩存的解決方案,且不需要復(fù)雜的數(shù)據(jù)類型和持久化,Memcached 可能是更合適的選擇。而如果應(yīng)用需要利用豐富的數(shù)據(jù)類型、持久化以及構(gòu)建高可用的分布式系統(tǒng),Redis 將是更強(qiáng)大、更靈活的選擇。
03
緩存淘汰策略
由于緩存的空間是有限的,如果緩慢的空間被使用完了,則需要淘汰舊的數(shù)據(jù),騰出空間給新的數(shù)據(jù)使用。緩存淘汰常用的幾種策略有如下幾種。
3.1 FIFO(First In First Out)算法
FIFO 算法是最簡單最好理解的,其策略是:先進(jìn)先出,如果一個(gè)數(shù)據(jù)的寫入時(shí)間越早,說明將來被訪問的幾率越低。因此 FIFO 算法優(yōu)先淘汰最早寫入的數(shù)據(jù)。
3.2 LRU(Least Recently Used)算法
LRU 算法,即最近最少使用算法。如果一個(gè)數(shù)據(jù)最近被訪問了,那么將來被訪問的幾率越高。反之,如果一個(gè)數(shù)據(jù)很久都沒有訪問,那么將來被訪問的幾率越低。其淘汰策略就是:優(yōu)先淘汰最久沒有被使用到的數(shù)據(jù)。LRU 通常使用雙向鏈表+哈希表來實(shí)現(xiàn)。
3.3 LFU(Least Frequently Used)算法
LFU 算法,即最少使用算法。如果一個(gè)數(shù)據(jù)被訪問的次數(shù)越多,那么將來被訪問的幾率越高。反之,如果一個(gè)數(shù)據(jù)被訪問的次數(shù)越少,那么將來被訪問的幾率越小。其淘汰策略就是:優(yōu)先淘汰最少被使用的數(shù)據(jù)。LFU 算法可以使用小頂堆+哈希表來實(shí)現(xiàn)。
大部分本地緩存的三方庫或緩存服務(wù),都支持設(shè)置淘汰算法,無需自行實(shí)現(xiàn)。
04
緩存常見問題
4.1 緩存與數(shù)據(jù)源的一致性
緩存的數(shù)據(jù)是來自數(shù)據(jù)源的,當(dāng)數(shù)據(jù)源被更新了,而緩存沒有被更新,后臺服務(wù)則會從緩存中取到臟數(shù)據(jù),這就有數(shù)據(jù)臟讀的問題。對于這個(gè)問題,主要的策略有兩種。
4.1.1 過期失效
每個(gè)寫入緩存中的數(shù)據(jù),都設(shè)置一個(gè)合適的過期時(shí)間,在有效期內(nèi)都返回緩存的數(shù)據(jù)。當(dāng)緩存數(shù)據(jù)過期失效,則會回源到數(shù)據(jù)源,重新緩存最新的數(shù)據(jù)。這種情況會有數(shù)據(jù)臟讀的問題,不過采用過期失效的數(shù)據(jù),一般是更新不頻繁的數(shù)據(jù),比如用戶信息、熱門信息等,存在一定的數(shù)據(jù)延遲是可以接受的。
4.1.2 主動更新
當(dāng)數(shù)據(jù)源的數(shù)據(jù)更新了,程序主動去更新緩存數(shù)據(jù),保證緩存中的數(shù)據(jù)始終是最新的。這種策略,適合對數(shù)據(jù)的時(shí)效性要求很高的數(shù)據(jù),比如庫存、余額等等。主動更新的代價(jià)則是代碼的復(fù)雜度增加,所有涉及數(shù)據(jù)更新的操作,都需要更新其緩存數(shù)據(jù)。
無論是過期失效還是主動更新,首先應(yīng)該分析當(dāng)前場景對數(shù)據(jù)的時(shí)效性要求是否很高?如不是,過期失效足矣。如果是,需要衡量主動更新的代價(jià)能否接受,比如代碼復(fù)雜度增加。否則是否可以通過縮短失效時(shí)間而采取過期失效策略來折中處理。
4.2 緩存雪崩
緩存雪崩是指在同一時(shí)間點(diǎn)后臺服務(wù)中的緩存大量過期失效,當(dāng)服務(wù)的并發(fā)量很高時(shí),大量的數(shù)據(jù)請求同時(shí)到達(dá)數(shù)據(jù)源服務(wù)(比如數(shù)據(jù)庫或其他服務(wù)),引起數(shù)據(jù)源服務(wù)的瞬時(shí)負(fù)載增大,甚至崩潰。
引起緩存雪崩的原因一般有兩種:
ü 一是大量的緩存key 在同一時(shí)間失效
ü 二是緩存服務(wù)不可用,請求都被透傳到數(shù)據(jù)庫。
解決辦法:
ü 設(shè)置合理的緩存失效時(shí)間:合理設(shè)置過期時(shí)間,分散緩存的失效時(shí)間,避免緩存在同一時(shí)間失效。
ü 保證緩存服務(wù)的高可用:使用獨(dú)立的緩存服務(wù),盡量使用緩存集群,保證緩存服務(wù)的高可用和容災(zāi)恢復(fù)。
4.3 緩存穿透
緩存穿透是指同一時(shí)間大量的空值請求到達(dá)后臺服務(wù),空值請求是指查詢數(shù)據(jù)源后無數(shù)據(jù)的請求,由于數(shù)據(jù)源返回空數(shù)據(jù),所以緩存層沒有將空值保存,導(dǎo)致空值請求必然會穿透緩存層,透傳到數(shù)據(jù)源。
引起緩存穿透的原因,一般有:
ü 未對請求參數(shù)進(jìn)行合理的校驗(yàn)和攔截。
ü 被精心構(gòu)造的惡意請求攻擊。
解決辦法:
ü 設(shè)置合理的參數(shù)校驗(yàn):通過設(shè)置合理的校驗(yàn)規(guī)則,比如id 范圍、名稱正則規(guī)則等,對不合理或空值請求的參數(shù)的進(jìn)行過濾和攔截。
ü 緩存空值處理:即使數(shù)據(jù)源返回空值,也在緩存層將其保存起來。可能對其數(shù)值設(shè)置一些標(biāo)記,比如value=-1 等等,方便服務(wù)識別為空值。同時(shí),也可以為空值的緩存設(shè)置一個(gè)較短的失效時(shí)間。
4.4 緩存擊穿
緩存擊穿是指某個(gè)熱點(diǎn)數(shù)據(jù)的緩存失效了,然后同一時(shí)間有大量的請求訪問該熱點(diǎn)數(shù)據(jù),由于緩存失效,這些請求同時(shí)被透傳到數(shù)據(jù)源服務(wù),導(dǎo)致數(shù)據(jù)源的負(fù)載增加,甚至崩潰。
引起緩存擊穿的原因,一般是:熱點(diǎn)數(shù)據(jù)的緩存失效時(shí),高并發(fā)請求同時(shí)訪問該數(shù)據(jù)。
解決辦法:
ü 使用互斥鎖或者分布式鎖,對數(shù)據(jù)的回源操作進(jìn)行上鎖,保證同一時(shí)刻只有一個(gè)請求被透傳到數(shù)據(jù)源服務(wù)。
ü 熱點(diǎn)數(shù)據(jù)的緩存永不過期,由數(shù)據(jù)源主動更新緩存。
ü 熱點(diǎn)數(shù)據(jù)預(yù)加載:在緩存數(shù)據(jù)將要過期時(shí),由后臺服務(wù)主動更新熱點(diǎn)數(shù)據(jù)緩存,防止緩存失效。
05
總結(jié)
本文詳細(xì)地介紹了后臺開發(fā)中的緩存技術(shù),希望能對你的日常工作有所幫助和啟發(fā)。熟練掌握各種緩存策略和機(jī)制,解決緩存問題,對于程序員而言至關(guān)重要,可以在很大程度上提升后臺開發(fā)的效率、穩(wěn)定性和用戶體驗(yàn)。