目錄
一.什么是緩存
二.使用Redis作為緩存
2.1.關系型數據庫的缺點
2.2.使用Redis作為MySQL的緩存
三. 緩存更新策略:識別熱點數據
3.1.定期更新
3.2.實時生成
四.緩存的使用注意事項
4.1.緩存預熱(Cache preheating)
4.2.關于緩存穿透 (Cache penetration)
4.3..關于緩存雪崩(Cache avalanche)
4.4.關于緩存擊穿(Cache breakdown)
Redis 最主要的用途, 三個方面:
- 1. 存儲數據(內存數據庫)
- 2. 緩存 [redis 最常用的場景]
- 3. 消息隊列
現在我們就來講解一下,什么是緩存?
一.什么是緩存
緩存(Cache)?是計算機科學中一個非常核心且應用廣泛的經典概念。它的核心思路在于:將那些訪問頻率較高或需要快速獲取的數據,存儲在一個訪問速度更快的臨時區域中,以便在需要時能夠更快地讀取,從而提升系統的整體效率和響應速度。簡單來說,緩存就是用更快的存儲空間來存放“熱點數據”,以空間換取時間。
🧠 理解緩存的本質:一個生活化的例子
想象一下你需要乘坐高鐵的經歷。我們知道,乘坐高鐵需要多次刷身份證(進入高鐵站、檢票、上車、乘車過程中可能查驗、出站等)。正常情況下,你的身份證可能存放在行李箱里(行李箱容量大,能裝很多東西)。但問題是,每次刷身份證都需要打開行李箱翻找,效率極低,非常不便。
為了優化這個過程,你提前把身份證從行李箱取出,放進了衣服口袋。口袋雖然空間有限(遠小于行李箱),但其訪問速度卻快得多——你只需伸手就能立刻拿到。這樣,每次刷身份證時,直接從口袋掏出即可,省去了反復開箱的麻煩。
在這個例子中:
-
行李箱?代表了?主存儲(如硬盤、數據庫或網絡資源)。特點是:容量大,但訪問速度相對較慢。
-
衣服口袋?就充當了?緩存。特點是:容量小,但訪問速度極快。
-
身份證?就是當前需要頻繁訪問的?熱點數據。
-
提前將身份證放入口袋?的過程,類似于?數據加載到緩存。
-
直接從口袋拿身份證?的過程,就是?緩存命中(Cache Hit),帶來了顯著的效率提升。
-
開行李箱找身份證?則相當于?緩存未命中(Cache Miss),需要去訪問速度更慢的主存儲,效率低下。
使用緩存的核心價值就在于它能大大減少訪問慢速存儲介質的次數,從而顯著提升訪問效率。
? “觸手可及”的相對性與硬件層級
緩存中提到的“觸手可及”或“訪問速度更快”是一個相對的概念。在計算機體系結構中,不同存儲介質的訪問速度存在明顯的層級關系,通常遵循:
CPU寄存器 > CPU緩存(L1, L2, L3) > 內存(RAM) > 固態硬盤(SSD)/機械硬盤(HDD) > 網絡存儲/遠程數據庫
基于這種速度差異,我們可以構建多級緩存體系:
-
硬盤/網絡作為最慢層:?硬盤(尤其是機械硬盤)或網絡訪問延遲很高。
-
內存作為硬盤/網絡的緩存:?內存(RAM)的速度遠快于硬盤和網絡。因此,操作系統和應用程序會將經常需要讀寫的數據(如運行中的程序、打開的文件)從硬盤加載到內存中。瀏覽器也會將訪問過的網頁資源(如圖片、腳本)緩存到內存或本地硬盤上,避免重復從網絡下載。
-
CPU緩存作為內存的緩存:?CPU內部的緩存(L1, L2, L3)速度又遠快于內存。CPU會將當前正在處理的內存中的數據塊預取到各級緩存中,供CPU核心高速訪問。
-
寄存器作為CPU緩存的延伸:?寄存器是CPU內部最快最小的存儲單元,用于存放當前指令直接操作的數據。
📌 緩存的關鍵特性與挑戰
緩存機制帶來了巨大的性能收益,但也伴隨著兩個關鍵特性及其帶來的挑戰:
-
速度與成本的權衡:?訪問速度越快的存儲設備,其單位容量的制造成本通常越高。
-
容量限制:?因此,緩存的空間總是有限的,遠遠小于它所緩存的主存儲空間(如內存容量遠小于硬盤,CPU緩存容量遠小于內存)。
正是由于緩存空間有限,它不可能存放所有數據。?這就需要精明的策略來決定緩存中存放什么。緩存的核心價值在于存放“熱點數據”(Hot Data),即那些被頻繁訪問的數據。通過將熱點數據保留在快速訪問的緩存層,系統就能在絕大多數請求中獲得顯著的性能提升。為了管理有限的緩存空間,需要緩存淘汰策略(如LRU - 最近最少使用、LFU - 最不經常使用等)?來決定當緩存滿時,哪些舊數據應該被移除,以便為新數據騰出空間。
二八定律
在計算機科學,特別是緩存優化領域,一個廣為人知且極具指導性的經驗法則就是“二八定律”(也稱為帕累托法則)。其核心思想可以精煉地表述為:
大約 20% 的數據(熱點數據),往往承載了 80% 甚至更高的訪問請求。
這個看似簡單的比例關系,深刻地揭示了數據訪問模式中普遍存在的不均衡性:并非所有數據被訪問的頻率都是均等的。系統中總存在一小部分數據,由于其重要性、時效性或關聯性,會被用戶或系統進程反復、高頻地訪問。
二.使用Redis作為緩存
2.1.關系型數據庫的缺點
雖然關系型數據庫(如 MySQL, PostgreSQL, SQL Server)因其強大的功能、數據一致性和成熟的生態系統而被廣泛應用,但在處理某些特定場景,尤其是高并發、低延遲或海量數據的讀寫請求時,其性能可能會成為瓶頸。這主要源于其架構設計和工作機制帶來的固有開銷:
-
磁盤 I/O 瓶頸:訪問速度的物理限制
-
核心問題:?關系型數據庫的核心數據通常持久化存儲在硬盤(HDD/SSD)?上,以確保數據的持久性(Durability)。然而,硬盤的 I/O 速度(尤其是隨機讀寫)?相比?內存(RAM)?存在數量級的差距(內存訪問速度通常是納秒級,而磁盤是毫秒級)。
-
性能影響:?任何需要訪問磁盤數據的操作(查詢、插入、更新、刪除)都可能受到磁盤 I/O 速度的限制。在高并發場景下,大量請求排隊等待磁盤 I/O,會迅速成為系統性能的主要瓶頸。
-
隨機訪問更甚:?順序讀寫磁盤尚可接受,但數據庫操作(特別是根據非連續索引或條件查詢)常常涉及隨機 I/O,這對傳統機械硬盤(HDD)尤其不友好,性能急劇下降。即使使用 SSD,其隨機 I/O 性能也遠低于內存。
-
-
全表掃描:索引失效的代價
-
核心機制:?索引是關系型數據庫提升查詢效率的關鍵。如果查詢條件能夠有效命中合適的索引,數據庫可以快速定位到目標數據行。
-
性能陷阱:?一旦查詢條件無法命中任何索引(例如對非索引列進行過濾、使用函數處理索引列、或使用了不合適的運算符),數據庫就不得不進行?全表掃描(Full Table Scan)。
-
嚴重后果:?全表掃描意味著數據庫需要逐行讀取整個表的所有數據塊。對于大表,這會產生海量的磁盤 I/O 操作(將表數據從磁盤讀入內存緩沖區),消耗大量 CPU 資源進行比較運算,導致查詢響應時間呈指數級增長。這是導致“慢查詢”的最常見原因之一。
-
-
SQL 執行引擎的開銷:解析、優化與執行的代價
-
處理流程:?一條 SQL 語句在數據庫內部執行并非簡單的“直達”,而是要經歷一個相對復雜的流程:
-
語法解析(Parsing):?檢查 SQL 語法是否正確。
-
語義分析(Semantic Analysis):?驗證表名、列名是否存在,用戶是否有權限等。
-
查詢優化(Optimization):?這是最核心也最耗資源的環節之一。優化器需要分析表結構、索引、數據分布統計信息,估算不同執行路徑(如選擇哪個索引、使用哪種連接算法)的成本,并生成一個理論上最優的執行計劃(Execution Plan)。這個過程本身就需要消耗可觀的 CPU 計算資源。
-
執行(Execution):?根據生成的執行計劃,調用存儲引擎讀取數據并進行計算(排序、分組、連接等)。
-
-
性能影響:?對于簡單查詢,這部分開銷相對可控。但對于復雜查詢或超高并發的短小查詢(如簡單的主鍵查詢),SQL 解析和優化的時間成本可能接近甚至超過實際數據檢索的時間,成為顯著的性能負擔。
-
-
復雜查詢的沉重代價:連接、聚合與排序
-
操作類型:?關系型數據庫的核心優勢在于處理復雜的關系操作,如多表連接(JOIN)、分組聚合(GROUP BY)、排序(ORDER BY)、子查詢、窗口函數等。
-
性能挑戰:
-
連接操作:?多表連接(尤其是大表連接)可能需要在內存或臨時磁盤空間中進行巨大的笛卡爾積計算(即使有索引,連接操作本身也可能很重),消耗大量 CPU 和內存資源。
-
聚合與排序:?對海量結果集進行分組、求和、求平均、排序等操作,同樣需要巨大的內存和 CPU 計算量。當內存不足時,數據庫會使用臨時磁盤空間(TempDB),這又會引入額外的磁盤 I/O 開銷。
-
資源消耗:?復雜查詢通常需要占用大量的內存來存放中間結果,并長時間占用 CPU?進行計算。在高并發環境下,幾個這樣的復雜查詢就可能耗盡數據庫資源,拖慢整個系統。
-
-
-
事務與鎖的并發控制開銷
-
ACID 保障:?關系型數據庫的核心特性之一是提供?ACID(原子性、一致性、隔離性、持久性)?事務保證。
-
性能權衡:?為了實現高隔離級別(如可重復讀、串行化),數據庫需要復雜的鎖機制(Locking)?或?多版本并發控制(MVCC)。
-
性能影響:
-
鎖競爭:?當多個事務試圖同時訪問或修改同一數據時,會發生鎖競爭。等待鎖釋放會阻塞后續操作,增加延遲,嚴重時甚至導致死鎖(需要數據庫檢測并解除)。
-
MVCC 開銷:?MVCC 通過維護數據版本來避免讀阻塞寫和寫阻塞讀,但這增加了存儲歷史版本數據的開銷(占用更多空間),以及垃圾回收(清理舊版本)?的 CPU 和 I/O 成本。在更新頻繁的場景下,MVCC 的開銷可能很大。
-
-
-
網絡與連接管理開銷(對于遠程訪問)
-
應用場景:?當應用程序與數據庫部署在不同的服務器上時,所有數據庫操作都需要通過網絡進行通信。
-
性能影響:?網絡延遲(Latency)?和帶寬限制(Bandwidth)?會成為額外的性能瓶頸。建立和銷毀數據庫連接(Connection)本身也有一定開銷。頻繁建立短連接或連接池配置不當都會顯著影響性能。
-
2.2.使用Redis作為MySQL的緩存
在現代網站架構中,關系型數據庫(如 MySQL、PostgreSQL)?通常是存儲核心、結構化數據的基石,提供強大的事務支持、復雜查詢能力和數據一致性保障。然而,其性能局限在高并發場景下尤為突出:執行一次查詢操作往往消耗較多的系統資源(CPU、內存、I/O),響應時間相對較長。
? 高并發下的數據庫壓力與風險
當網站訪問量激增,大量請求同時涌向數據庫時:
-
資源消耗劇增:?每個查詢都需要數據庫進行磁盤 I/O、SQL 解析優化、執行計算等操作,消耗 CPU、內存、磁盤和網絡資源。
-
瓶頸效應顯現:?數據庫服務器的硬件資源(CPU核心數、內存容量、磁盤I/O吞吐、網絡帶寬)是有限的。高并發下,這些資源迅速被消耗殆盡。
-
宕機風險陡升:?資源耗盡(尤其是 CPU 100%、內存 OOM、磁盤 I/O 飽和或連接數爆滿)將直接導致數據庫響應時間飆升、拒絕新連接,甚至進程崩潰(OOM Killer觸發或程序異常),引發服務不可用。
🛡 提升數據庫承載能力的核心策略
為了應對高并發挑戰,保護數據庫穩定運行,核心思路圍繞“開源”與“節流”展開,通常需要雙管齊下:
-
開源:橫向/縱向擴展數據庫能力
-
核心思想:?通過增加硬件資源或部署更多數據庫實例來分攤負載。
-
主要手段:
-
垂直擴展 (Scale Up):?升級單臺數據庫服務器的硬件(更強的 CPU、更大的內存、更快的 SSD)。優點:簡單直接。缺點:成本高昂,有物理上限,單點故障風險。
-
水平擴展 (Scale Out):?部署數據庫集群。
-
讀寫分離 (主從復制):?設置一個主庫(Master)負責寫操作,多個從庫(Slave)復制主庫數據并分擔讀操作。顯著提升讀吞吐量。
-
分庫分表:?將龐大的數據庫/表水平拆分成多個較小的、分布在不同的物理服務器上的庫/表。根據特定規則(如用戶ID范圍、地域)路由請求。大幅提升整體讀寫能力和存儲上限。技術復雜度高,需解決分布式事務、跨庫查詢等問題。
-
-
-
目標:?直接提升數據庫集群整體的處理容量。
-
-
節流:引入緩存,減少數據庫直接訪問
-
核心思想:?在數據庫前方設置一道高速緩沖層,攔截并處理大量重復的、對熱點數據的讀請求,避免它們“穿透”到后端的數據庫,從而顯著降低數據庫的訪問壓力和資源消耗。
-
關鍵利器:Redis
-
定位:?Redis 是實現數據庫緩存方案的首選工具。
-
性能優勢顯著:
-
內存速度:?Redis 核心數據存儲在內存(RAM)中,訪問速度(微秒級)比基于磁盤的關系型數據庫(毫秒級)快幾個數量級。
-
簡單高效:?Redis 主要提供簡單的?Key-Value?數據結構操作,避免了關系型數據庫復雜的 SQL 解析、優化器、執行引擎、連接計算(如 JOIN)等帶來的巨大開銷。處理一個簡單的鍵查詢請求,Redis 消耗的系統資源遠低于 MySQL。
-
高并發支撐:?得益于內存操作和輕量級架構,Redis 能夠輕松支撐極高的讀寫并發量。
-
-
作用比喻:?Redis 猶如數據庫前方的?“護盾”?或?“高速閘門”,將大量請求“拒之門外”(在緩存層解決),保護后端的 MySQL 免受洪峰沖擊。
-
-
🔄 Redis緩存工作機制:命中與回填
典型的使用Redis作為數據庫緩存的請求處理流程如下:
-
客戶端發起請求:?用戶通過客戶端(如瀏覽器、App)向業務服務器發起數據查詢請求。
-
業務服務器查詢Redis:?業務服務器首先嘗試從 Redis 緩存中根據請求的 Key 查找所需數據。
-
緩存命中 (Cache Hit):?如果數據在 Redis 中存在且有效(未過期),業務服務器直接從 Redis 獲取數據并返回給客戶端。此過程完全繞過數據庫,響應最快。
-
緩存未命中 (Cache Miss):?如果數據在 Redis 中不存在或已過期,業務服務器則轉向查詢后端數據庫 (如 MySQL)。
-
-
查詢數據庫并回填緩存:
-
業務服務器從 MySQL 中獲取所需數據。
-
業務服務器將查詢到的數據按約定格式(通常也是 Key-Value)寫入 Redis 緩存,并設置一個合理的過期時間 (TTL)。這個過程稱為緩存回填 (Cache Population)。
-
業務服務器將數據返回給客戶端。
-
-
后續請求受益:?對于相同 Key 的后續請求,只要緩存數據未過期,都將直接從 Redis 中快速獲取(命中),極大地減輕了數據庫壓力。
📊 “二八定律”與緩存的價值
緩存機制有效性的核心理論基礎正是“二八定律”(帕累托法則):在大多數系統中,大約?20% 的熱點數據承載了 80% 的訪問請求。
-
策略精髓:?Redis 緩存無需存儲所有數據。它聚焦于識別并存儲那關鍵的?20% 的熱點數據 (Hot Data)。
-
效果顯著:?通過成功緩存這部分少量但訪問極其頻繁的數據,就能有效攔截并處理掉?80% 甚至更高比例的查詢請求,使其無需穿透到數據庫層。
-
實踐考量:?實際業務中,“二八”比例可能因場景不同有所浮動(可能是“一九”、“三七”等),但“少數數據支撐多數訪問”的核心規律普遍成立。精心設計和維護的緩存系統,通常只需配置相對總數據量很小一部分的內存(如 1%-5%),就能將整體緩存命中率提升至 80%-95% 以上。
-
核心收益:
-
大幅提升用戶體驗:?響應速度更快(毫秒級 vs 幾十甚至幾百毫秒)。
-
顯著降低數據庫負載:?保護數據庫免受流量洪峰沖擊,提升其穩定性。
-
增強系統整體吞吐量:?用更少的數據庫資源處理更多用戶請求。
-
優化成本效益:?利用相對經濟的緩存資源(內存)有效緩解了擴展數據庫(尤其是分庫分表)的復雜度和成本。
-
三. 緩存更新策略:識別熱點數據
在實施緩存策略時,一個核心挑戰是:如何準確識別哪些數據屬于需要重點緩存的“熱點數據”?
3.1.定期更新
一種常見策略是定期生成熱點數據集。系統會設定一個固定的時間周期(例如一天、一周或一個月),對該周期內所有被訪問數據的頻次進行統計和分析。最終,篩選出訪問頻率最高的前 N% 的數據,認定它們為當前階段的熱點數據。
🥇 搜索引擎案例:
以搜索引擎為例,用戶每次輸入的搜索詞(查詢詞)都會被服務器詳細記錄在日志中,包含用戶信息、時間戳和查詢詞本身。這些日志數據量通常極其龐大。
-
高頻詞:?某些查詢詞被大量用戶頻繁搜索(例如,日常熱門商品、本地服務、流行話題等)。
-
低頻詞:?而另一些查詢詞則相對冷門,搜索次數很少。
-
統計過程:?搜索引擎會定期(如每天或每周)利用大數據處理框架(如 Hadoop 或 Spark)對這些海量日志進行離線批量分析,計算每個查詢詞出現的總次數或頻率。通過這種統計分析,就能生成一份反映該統計周期內用戶搜索熱度的“高頻詞表”。
優點:
-
實現相對簡單:?利用成熟的離線批處理技術即可完成。
-
結果穩定:?統計周期較長時,結果能反映穩定的、長期的熱點趨勢。
缺點與挑戰:
-
實時性差:?這是該方法最主要的短板。統計結果是基于過去一個周期的歷史數據,無法即時反映當前發生的變化。新產生的熱點或突發熱點無法被及時識別和緩存。
-
應對突發流量不足:?對于由突發事件、節日活動或營銷推廣等引起的瞬時流量高峰,這種延遲的統計方式往往反應滯后。
-
示例:?在春節臨近和期間,“春晚”、“春運”、“春節祝福語”等詞匯的搜索量會急劇飆升,成為絕對的熱點。然而,在平時,這些詞的搜索頻率則相對較低。基于上周或上月數據生成的“高頻詞表”很可能完全未包含這些詞,導致緩存系統在春節流量洪峰來臨時未能有效緩存相關結果,加重后端數據庫壓力,影響用戶體驗。
-
-
資源消耗:?處理海量日志進行全量統計本身需要消耗可觀的計算和存儲資源。
-
數據新鮮度:?統計周期結束時,數據已經“過時”了一段時間。
3.2.實時生成
與定期生成不同,實時生成策略的核心思想是:讓緩存系統在運行過程中,根據用戶的實際訪問行為,動態地識別和保留熱點數據。?這種策略通常結合緩存淘汰機制來實現。
實現原理:
-
設定緩存容量:?首先,為緩存設定一個明確的容量上限。例如,在 Redis 中,可以通過配置文件中的?
maxmemory
?參數來設置最大內存使用量。 -
處理用戶查詢:
-
當用戶發起查詢請求時,系統首先在 Redis 緩存中查找。
-
緩存命中:?如果在 Redis 中找到數據,則直接返回結果給用戶,效率最高。
-
緩存未命中:?如果在 Redis 中未找到數據,則系統需要訪問后端數據庫(如 MySQL)獲取結果。
-
將數據庫查詢結果返回給用戶。
-
同時,將這份查詢結果寫入 Redis 緩存,以便后續相同的請求能直接從緩存中獲取,加速響應。
-
-
-
動態淘汰:?當持續寫入數據導致緩存達到預設的容量上限時,Redis 會自動觸發其緩存淘汰策略。這時,系統會根據設定的策略算法,選擇淘汰掉一部分“相對不那么熱門”的數據,騰出空間來容納新寫入的數據。
-
熱點數據自然沉淀:?按照上述過程持續運行一段時間后,那些被頻繁訪問的數據因為不斷地被訪問(命中)而得以保留在緩存中。相反,那些訪問頻率低的數據,在新數據寫入或根據淘汰策略計算后被淘汰的概率更大。最終,緩存中留存下來的數據,自然就趨向于當前最熱門的“熱點數據”。這是一個動態、自適應的過程。
常見的通用緩存淘汰策略:
以下策略不僅適用于 Redis,也是許多緩存系統支持的核心淘汰機制:
-
FIFO (First In First Out - 先進先出):
-
原理:?淘汰最早進入緩存的數據,類似于隊列。它認為“先來的數據”被再次訪問的可能性較低。
-
優點:?實現簡單。
-
缺點:?可能淘汰掉仍然熱門但只是較早進入的舊數據,無法有效反映數據的實際熱度。對突發的新熱點不友好。
-
-
LRU (Least Recently Used - 最近最少使用):
-
原理:?系統記錄每個緩存項(Key)的最后一次被訪問的時間戳。當需要淘汰時,選擇最久未被訪問的那個數據項淘汰。它認為“最近沒被用過的數據”將來被用的可能性也低。
-
優點:?相比 FIFO,能更好地反映數據的近期訪問熱度,保護了最近被訪問過的數據。
-
缺點:?對突發性的、周期性的或“掃描式”訪問模式可能不夠友好(例如,一個很久沒訪問但突然被大量訪問的數據會被保留,而一個周期性訪問但恰好在淘汰檢查前很久沒訪問的數據會被淘汰)。需要維護訪問時間戳,有一定開銷。容易受到偶發大量訪問的影響(可能把真正的老熱點擠出)。
-
-
LFU (Least Frequently Used - 最不經常使用):
-
原理:?系統記錄每個緩存項(Key)在一段時間內的訪問頻率(次數)。當需要淘汰時,選擇訪問頻率最低的那個數據項淘汰。它認為“總訪問次數少的數據”是冷數據。
-
優點:?從長期累積的角度識別冷數據,能更穩定地保護真正的熱點(高頻訪問數據)。
-
缺點:?新加入的數據初始頻率為0或很低,容易被立即淘汰,即使它可能很快會變成熱點(“新生劣勢”)。需要維護和更新訪問計數器,開銷通常比 LRU 更大。對突發的新熱點響應較慢。舊熱點如果訪問頻率下降,可能長期占據緩存(需要結合老化機制)。Redis 實現的 LFU 是近似計數。
-
-
Random (隨機淘汰):
-
原理:?當緩存滿時,隨機選擇一個數據項進行淘汰。
-
優點:?實現極其簡單,開銷最小。
-
缺點:?完全無法保證淘汰的是冷數據,可能誤淘汰熱點數據,導致緩存命中率不穩定且通常較低。實際生產環境中較少單獨使用。
-
🏕 理解淘汰策略(比喻延續):
想象緩存如同皇帝有限的“精力”(緩存空間),需要專注于處理最重要的國事(熱點數據)。數據庫則如同龐大的“后宮”(全量數據),記錄了所有妃嬪(數據項)。
-
新數據入緩存:?如同皇帝看中(查詢)了一位新入宮的秀女(新數據),決定召見(寫入緩存)。
-
緩存已滿(精力有限):?要召見新人,就必須有舊人暫時移出視線(淘汰出緩存)。選擇移出誰(淘汰誰)就是淘汰策略解決的問題:
-
FIFO (先進先出):?最早入宮(最早寫入緩存)的皇后(數據A)被移出。不管她是否依然重要。
-
LRU (最近最少使用):?查看妃嬪們最近一次被召見(訪問)的時間。假設:
-
皇后:1周前
-
熹妃:昨天
-
安答應:2周前
-
華妃:1個月前
則?華妃(最久未被召見)?被移出。
-
-
LFU (最不經常使用):?統計妃嬪們最近一個月被召見(訪問)的次數。假設:
-
皇后:3次
-
熹妃:15次
-
安答應:1次
-
華妃:10次
則?安答應(訪問次數最少)?被移出。
-
-
Random (隨機淘汰):?閉著眼睛隨便指一位妃嬪(如純元皇后畫像...)移出。
-
Redis 的淘汰策略
除了理解通用的緩存淘汰原理,Redis 自身提供了豐富的、可直接配置使用的內存淘汰策略。這些策略在內存達到?maxmemory
?限制時自動觸發,用于為新數據騰出空間。
Redis 的淘汰策略名稱通常包含兩部分:
-
作用范圍 (
volatile
?vs?allkeys
):-
volatile-
:策略僅作用于設置了過期時間 (expire TTL) 的 key。 -
allkeys-
:策略作用于所有 key,無論是否設置了過期時間。
-
-
淘汰算法 (
lru
,?lfu
,?random
,?ttl
):?決定在指定范圍內依據什么規則選擇淘汰哪些 key。
以下是 Redis 支持的主要淘汰策略:
-
volatile-lru
?(Least Recently Used on volatile keys):-
當內存不足時,僅從設置了過期時間的 key 中,淘汰?最近最久未使用 (LRU)?的 key。
-
適用場景:?明確區分緩存數據(設TTL)和持久數據(不設TTL)的場景,只希望淘汰緩存數據。
-
-
allkeys-lru
?(Least Recently Used on all keys):-
當內存不足時,從所有 key 中(無論是否過期),淘汰?最近最久未使用 (LRU)?的 key。
-
適用場景:?最常見的選擇之一。適用于希望將所有數據都視為潛在緩存,并優先淘汰最不活躍數據的場景。對整體緩存命中率提升效果較好。
-
-
volatile-lfu
?(Least Frequently Used on volatile keys) [Redis 4.0+]:-
當內存不足時,僅從設置了過期時間的 key 中,淘汰?訪問頻率最低 (LFU)?的 key。
-
適用場景:?需要更精準識別長期冷門緩存數據(設TTL)的場景,LFU 比 LRU 更能保護長期熱點。
-
-
allkeys-lfu
?(Least Frequently Used on all keys) [Redis 4.0+]:-
當內存不足時,從所有 key 中(無論是否過期),淘汰?訪問頻率最低 (LFU)?的 key。
-
適用場景:?另一個強推薦策略。適用于希望基于長期訪問頻率來保留最熱數據,對所有數據一視同仁的場景。能更好地保護持久的熱點數據。
-
-
volatile-random
?(Random on volatile keys):-
當內存不足時,僅從設置了過期時間的 key 中,隨機淘汰一個 key。
-
適用場景:?對淘汰數據要求不高,且主要淘汰緩存數據的簡單場景。效率高但命中率不穩定。
-
-
allkeys-random
?(Random on all keys):-
當內存不足時,從所有 key 中(無論是否過期),隨機淘汰一個 key。
-
適用場景:?極少使用。對數據重要性無區分,或僅作為測試/基準比較。生產環境慎用,可能誤淘汰關鍵數據。
-
-
volatile-ttl
?(Time To Live on volatile keys):-
當內存不足時,僅從設置了過期時間的 key 中,淘汰?剩余生存時間 (TTL) 最短的 key(即最快過期的)。
-
適用場景:?可以理解為在設置了TTL的key中實現了?FIFO(先進先出)?的近似效果(早過期的先淘汰)。適用于希望優先淘汰即將自然過期的緩存數據,減少主動淘汰開銷的場景。有助于緩解緩存雪崩風險(避免大量key同時過期)。
-
-
noeviction
?(No Eviction - 默認策略):-
當內存不足時,新寫入的操作(會占用更多內存的命令,如 SET, LPUSH 等)將直接返回錯誤(通常是?
(error) OOM command not allowed when used memory > 'maxmemory'
)。讀取命令(如 GET)通常不受影響。 -
適用場景:?不推薦用于緩存場景!?僅適用于Redis存儲的數據絕對不允許丟失,且應用層能妥善處理寫入失敗的情況(例如作為唯一的主存儲)。在需要緩存的系統中使用此策略會導致寫入失敗,嚴重影響服務可用性。
-
四.緩存的使用注意事項
4.1.緩存預熱(Cache preheating)
什么是緩存預熱?
在使用 Redis 作為 MySQL 的前置緩存時,會遇到一個典型問題:緩存空窗期壓力。這通常發生在以下兩種場景:
-
Redis 服務剛啟動:此時 Redis 緩存是完全空的。
-
Redis 中大批量 Key 同時失效:例如,大量緩存設置了相同的過期時間,導致在某個時刻集中失效。
問題后果:
在這段空窗期內,用戶的查詢請求會直接穿透 Redis(因為找不到數據),全部落到后端的 MySQL 數據庫上。由于 MySQL 處理請求的能力通常遠低于緩存,瞬間激增的請求會對其造成巨大的訪問壓力,可能導致數據庫響應變慢甚至崩潰,嚴重影響服務的可用性和用戶體驗。
緩存預熱的定義與目的:
緩存預熱(Cache Warming)就是為了解決上述“空窗期”問題而采取的一種主動策略。?其核心思想是:在緩存服務(如 Redis)正式投入使用或預期可能面臨大批緩存失效之前,提前將預估的“熱點數據”加載到緩存中。
如何實現緩存預熱?
-
識別熱點數據:?利用之前介紹的方法(如定期統計分析歷史訪問日志)離線計算出一批最可能被頻繁訪問的數據。這份熱點數據列表不要求絕對精確,其目標是覆蓋大部分高概率請求即可。
-
主動加載數據:
-
在 Redis 啟動后、正式接入流量之前。
-
或者在預期大批緩存失效(如緩存刷新、大促前)之前。
-
通過腳本、后臺任務或專門的數據加載工具,將識別出的熱點數據及其查詢結果批量寫入 Redis。
-
緩存預熱的意義與效果:
-
立即提供保護:?預熱完成后,當用戶請求到達時,Redis 緩存中已經存在了大量熱點數據,能夠直接響應這些請求,顯著減少甚至避免了穿透到 MySQL 的請求數量,為數據庫撐起了“保護傘”。
-
平滑啟動與過渡:?避免了服務啟動或緩存刷新瞬間對數據庫造成的巨大沖擊,保障了系統啟動和運行的穩定性。
-
結合動態調整:?預熱加載的初始熱點數據是基于歷史統計的預測。隨著系統正式運行,用戶的實時訪問行為會驅動 Redis 的淘汰機制(如 LRU, LFU)持續工作。訪問真正頻繁的新熱點數據會逐漸被寫入緩存并保留下來,而訪問較少的預熱數據則會根據策略被逐步淘汰。這樣,緩存的內容會動態演化,越來越貼合當前實際的訪問模式。
4.2.關于緩存穿透 (Cache penetration)
什么是緩存穿透?
緩存穿透是指用戶查詢一個在緩存(如 Redis)和底層數據庫(如 MySQL)中都不存在的數據。由于緩存中找不到該數據(稱為緩存未命中),系統會轉而查詢數據庫。數據庫同樣返回“不存在”的結果。關鍵問題在于:這個無效的查詢結果通常不會被存儲到緩存中。
為什么緩存穿透是個問題?
-
數據庫壓力驟增:?由于這個無效的查詢結果沒有被緩存起來,后續對該同一個無效 Key 的重復查詢,依然會穿透緩存,直接打到數據庫上。
-
放大效應:?如果存在大量不同的無效 Key(例如,大量隨機生成的、不存在的 ID),并且這些 Key 被頻繁查詢(可能是惡意攻擊或業務邏輯缺陷導致),數據庫將承受巨大的、本可避免的查詢壓力,嚴重時可能導致數據庫性能下降甚至崩潰。
-
緩存失效:?緩存層在這種場景下完全失去了保護數據庫的作用。
緩存穿透是如何產生的?
主要原因包括:
-
業務設計不合理:
-
缺少必要的參數校驗環節。例如,查詢用戶信息時,未對輸入的 ID 進行格式校驗(如長度、字符類型),導致非法或無效的 ID 被直接提交到后端進行查詢。
-
-
開發/運維誤操作:
-
開發或運維人員不小心誤刪了數據庫中的部分有效數據,導致原本存在的 Key 變成了“不存在”。(這種情況雖非典型穿透的起因,但結果表現相同)。
-
-
惡意攻擊:
-
黑客惡意攻擊是緩存穿透最常見且危害最大的來源。攻擊者會故意構造并大量請求根本不存在的 Key(如隨機生成的用戶ID、商品ID等),旨在耗盡數據庫資源,使服務不可用。
-
補充說明:?面對網絡上海量的惡意掃描和攻擊嘗試,單純依賴預先設定的規則(如參數校驗規則)不一定能及時發現或完全攔截所有惡意請求。
-
如何解決緩存穿透?
解決思路主要圍繞:識別并攔截無效請求?和?減輕無效請求對數據庫的沖擊。
-
加強參數校驗(業務層防御):
-
最根本的解決方案。?在請求到達緩存和數據庫查詢邏輯之前,在業務層對查詢參數進行嚴格的合法性校驗。例如:
-
查詢用戶手機號:校驗是否符合手機號格式(長度、數字組成、有效號段)。
-
查詢商品ID:校驗是否為有效格式(如純數字、特定長度范圍)。
-
查詢訂單號:校驗結構是否符合規則。
-
-
盡早過濾掉明顯非法的請求,避免它們穿透到后端存儲。
-
-
緩存空對象(Null Object Caching / Cache Empty Results):
-
核心思想:?即使數據庫查詢結果為“不存在”,也將這個“空結果”寫入緩存。
-
實現:?當系統查詢某個 Key,在數據庫確認不存在后,在 Redis 中設置該 Key 對應的 Value 為一個特殊的、表示“空”或“不存在”的值(例如空字符串?
""
、特殊標識字符串?"NULL"
、或一個具有特定含義的對象)。 -
優點:?后續相同的無效 Key 查詢會直接從緩存中獲取到這個“空值”,避免了重復穿透到數據庫。
-
關鍵點:
-
需要為這些“空值”Key?設置一個合理的過期時間 (TTL)。防止存儲大量永不失效的無效 Key 占用過多緩存空間,也應對未來該 Key 可能變為有效的情況(比如新用戶注冊了這個手機號)。
-
選擇何種“空值”形式需結合業務邏輯,確保應用層能正確識別和處理。
-
-
-
使用布隆過濾器 (Bloom Filter):
-
核心思想:?在查詢緩存和數據庫之前,增加一個快速預檢層,用于高概率地判斷一個 Key?是否可能存在于數據庫中。如果布隆過濾器判斷 Key?肯定不存在,則直接返回空結果,無需查詢緩存或數據庫。
-
原理簡述:?布隆過濾器是一個基于哈希 (Hash)?和位數組 (Bitmap)?的概率型數據結構。
-
將所有可能存在于數據庫的有效 Key?預先加載(“添加”)到布隆過濾器中。
-
當需要查詢一個 Key 時,布隆過濾器通過多個哈希函數計算該 Key 在位數組中的對應位置。
-
如果這些位置有任何一位是 0,則肯定可以斷定該 Key?不存在于數據庫中。
-
如果這些位置全都是 1,則該 Key?可能存在(存在一定的誤判率,即布隆過濾器說“可能存在”時,實際可能不存在)。
-
-
優點:
-
空間效率極高:?相對于存儲所有 Key 本身,布隆過濾器僅使用一個位數組,占用內存極小。
-
查詢速度極快:?計算幾個哈希并檢查位數組,時間復雜度是常數級 O(k)。
-
-
缺點:
-
概率性:?存在誤判率 (False Positive)。即可能將數據庫中不存在的 Key 誤判為“可能存在”(但不會將存在的 Key 誤判為不存在)。這會導致少量無效請求仍然可能穿透到緩存/數據庫。
-
不支持刪除:?標準的布隆過濾器不支持直接刪除 Key(刪除會影響其他 Key 的判斷)。需要刪除的場景可考慮變種如計數布隆過濾器(Counting Bloom Filter),但空間開銷增大。
-
-
適用場景:?非常適合解決惡意攻擊產生的大量、隨機的無效 Key 查詢問題。它能攔截掉絕大部分明顯無效的請求。
-
補充說明:?布隆過濾器通常需要獨立維護(如使用 Redis Module 提供的布隆過濾器功能,或使用獨立的 Bloom Filter 庫/服務),并需要機制將數據庫的有效 Key 同步到過濾器中(如啟動時全量加載,增量更新)。
-
4.3..關于緩存雪崩(Cache avalanche)
緩存雪崩是指在極短的時間內,緩存系統(如 Redis)中有大量的緩存 Key 集中過期失效,或者緩存服務本身發生大規模故障(如整個 Redis 集群宕機)。
為什么緩存雪崩是個嚴重問題?
-
緩存保護失效:?緩存的核心作用是作為數據庫(如 MySQL)的“護盾”,吸收并處理大部分查詢請求,減輕數據庫壓力。當海量 Key 瞬間失效或緩存服務不可用時,這面“護盾”就瞬間崩塌了。
-
數據庫壓力海嘯:?所有原本可以由緩存響應的請求,此時都直接涌向底層數據庫。數據庫瞬時需要處理遠超其設計負載的并發查詢。
-
級聯崩潰風險:?數據庫很可能因無法承受這種瞬時洪峰壓力而導致響應變慢、連接耗盡,甚至完全崩潰宕機。
-
服務不可用:?數據庫的崩潰或過載會直接導致依賴它的應用服務對外不可用或響應極慢,形成大規模的服務故障。
-
恢復困難:?即使數據庫重啟,如果緩存仍未恢復或大量 Key 仍處于失效狀態,重啟后的數據庫可能立即再次被蜂擁而至的請求壓垮,形成惡性循環。
緩存雪崩是如何產生的?
主要原因可以分為兩大類:
-
緩存服務大規模故障:
-
Redis 單點/主節點宕機:?單節點部署下,該節點宕機即導致整個緩存服務不可用。
-
Redis 集群故障:?在集群模式下,如果發生大規模節點宕機(如網絡分區、硬件故障、軟件Bug導致批量崩潰),或者集群無法完成故障轉移(Failover),導致大部分或整個集群服務不可用。
-
網絡問題:?連接緩存服務的網絡出現嚴重中斷或擁塞。
-
-
大量 Key 集中同時過期:
-
批量初始化/預熱:?常見于系統啟動、數據遷移或緩存預熱時,一次性向緩存中加載了大量數據,并且為這些 Key?設置了相同或非常接近的過期時間 (TTL)。例如,在凌晨進行數據預熱,所有 Key 都設置了 24 小時過期,那么第二天凌晨這些 Key 就會同時失效。
-
定時任務刷新:?某些定時任務定期刷新緩存數據,如果刷新邏輯是刪除舊 Key 并重新加載且設置相同 TTL,也可能導致下一周期 Key 集中過期。
-
業務特性:?特定業務場景下自然產生了大量具有相同生命周期的數據(雖然相對少見)。
-
如何解決和預防緩存雪崩?
解決思路的核心是:避免大規模失效集中發生?和?增強系統對失效的韌性。
-
構建高可用的緩存架構:
-
集群化部署:?使用 Redis Cluster、Codis 或基于 Sentinel 的主從復制等高可用方案,確保單個或多個節點故障時,服務能自動切換并持續可用。
-
異地多活/容災:?對于關鍵業務,考慮部署跨機房的緩存集群,提升整體容災能力。
-
監控與報警:?至關重要!?建立完善的監控體系,實時監控 Redis 節點狀態、集群健康度、內存使用率、連接數、QPS、緩存命中率等關鍵指標。設置嚴格的報警閾值(如節點宕機、命中率驟降),確保問題能第一時間被發現和處理。
-
-
優化 Key 過期策略:
-
差異化過期時間:?避免為大量 Key 設置完全相同的過期時間。在設置 TTL 時,引入隨機因子。例如,基礎過期時間為 24 小時,實際設置的 TTL = 24小時 + 隨機(0 到 30分鐘)。這樣可以將 Key 的失效時間點打散,避免瞬間雪崩。
-
熱點數據永不過期 + 異步更新:?對于訪問極其頻繁的核心熱點數據,可以考慮不設置過期時間。通過獨立的異步邏輯(如后臺任務、監聽數據庫變更)來更新緩存,確保數據相對新鮮的同時避免被動失效。需要配合內存淘汰策略(如 LRU)管理內存。
-
-
采用多級緩存策略:
-
在應用層(如 JVM 內)使用本地緩存(如 Caffeine、Guava Cache),在分布式緩存(Redis)之上再增加一層保護。即使 Redis 崩潰或大量 Key 失效,部分請求仍可由本地緩存響應。需要注意本地緩存的一致性和容量管理,通常設置較短的 TTL。
-
-
服務降級與熔斷:
-
熔斷機制:?當檢測到數據庫壓力過大或錯誤率飆升時,自動觸發熔斷,短時間內直接拒絕部分或全部請求(返回預設的兜底值、錯誤提示或排隊中狀態),保護數據庫不被壓垮。
-
服務降級:?在極端情況下,暫時關閉非核心功能或返回簡化數據,優先保障核心流程和數據庫的可用性。
-
-
緩存預熱與延時加載:
-
預熱:?在預期的高峰期來臨前(或系統啟動后),提前加載熱點數據到緩存中,并設置合理的、帶有隨機因子的 TTL。
-
延時加載:?對于緩存失效后的重建,可以使用互斥鎖(如 Redis 的?
SETNX
)或分布式鎖,確保只有一個線程去數據庫查詢并重建緩存,其他線程等待或短暫返回舊值/默認值,避免大量線程同時擊穿緩存訪問數據庫。
-
4.4.關于緩存擊穿(Cache breakdown)
什么是緩存擊穿?
緩存擊穿(也稱為熱點 Key 失效)是緩存雪崩的一個特例,但其影響往往更為集中和劇烈。它特指在緩存系統中,一個或多個訪問極其頻繁的熱點 Key?在某個瞬間同時過期失效。
為什么緩存擊穿危害巨大?
-
單點失效,壓力集中:?與雪崩的大面積失效不同,擊穿通常只影響少數甚至單個 Key。但正因為這些 Key 是熱點(被海量并發請求訪問),其失效的影響被急劇放大。
-
瞬間洪峰沖擊:?當熱點 Key 失效的瞬間,所有原本依賴緩存響應的、針對該 Key 的海量并發請求,會像洪水一樣瞬間涌向底層數據庫(如 MySQL),試圖查詢并重建緩存。
-
數據庫不堪重負:?數據庫可能完全無法承受這種針對單點數據的超高并發查詢壓力,導致響應延遲飆升、連接資源耗盡,甚至引發數據庫崩潰宕機。
-
連鎖反應:?數據庫的崩潰或過載會進一步導致依賴該數據的應用服務大面積不可用或響應超時,影響范圍迅速擴大。
-
恢復困難:?即使數據庫重啟或緩存重建完成,如果熱點訪問模式未改變,重建過程本身或后續的首次訪問高峰仍可能再次壓垮數據庫。
緩存擊穿是如何產生的?
核心原因:
-
熱點 Key 集中過期:?一個或多個訪問量巨大的 Key 被設置了相同的過期時間,并且在同一時刻失效。例如:
-
一個首頁置頂的爆款商品信息 Key。
-
一個重要的系統配置項 Key。
-
一個頂流明星/主播的個人信息 Key。
-
-
缺乏失效保護機制:?在熱點 Key 失效時,沒有有效的機制來平滑處理海量并發重建請求。
如何解決和預防緩存擊穿?
解決思路的核心是:防止熱點 Key 被動失效引發瞬時洪峰?和?控制緩存重建時的并發訪問。
-
熱點 Key 永不過期 + 異步更新:
-
核心策略:?對于識別出的熱點 Key,在緩存中不設置過期時間 (TTL),使其理論上“永不過期”。
-
保證數據新鮮度:?配合使用異步更新機制:
-
后臺定時任務:?啟動一個獨立的任務,定期(如每分鐘)檢查數據庫中的數據是否有更新,如有則主動刷新緩存。
-
監聽數據庫變更:?利用數據庫的 Binlog 或變更數據捕獲 (CDC) 技術,當檢測到熱點 Key 對應的源數據發生變化時,立即觸發緩存更新。
-
-
優點:?完全避免了被動過期導致的瞬時擊穿風險。
-
關鍵點:?需要有效的熱點 Key 發現機制(如通過監控訪問頻率、QPS 統計、業務經驗識別)和可靠的后臺更新邏輯。同時需配合內存淘汰策略管理內存。
-
-
互斥鎖 (Mutex Lock) 控制并發重建:
-
核心思想:?當熱點 Key 失效時,只允許一個請求線程去數據庫查詢數據并重建緩存,其他并發請求等待或短暫返回舊值/默認值,重建完成后再恢復正常訪問。
-
實現方式:?使用分布式鎖(如基于 Redis 的?
SETNX
?+?EXPIRE
,或 RedLock 算法,或 ZooKeeper)來實現互斥訪問。 -
流程簡述:
-
請求 A 發現 Key 在緩存中失效。
-
請求 A 嘗試獲取該 Key 對應的分布式鎖。
-
如果獲取鎖成功,請求 A 負責查詢數據庫、重建緩存、設置新值(可能帶新的TTL)、然后釋放鎖。
-
在請求 A 持有鎖并重建緩存期間:
-
其他并發請求 (B, C, D...) 發現 Key 失效且獲取鎖失敗。
-
這些請求可以選擇:
-
等待重試:?短暫休眠后重試獲取緩存(可能已由 A 重建好)。
-
返回兜底值:?直接返回一個業務上可接受的默認值或空值(服務降級的一種形式)。
-
-
-
請求 A 釋放鎖后,后續請求可以正常訪問新緩存。
-
-
優點:?有效防止了海量線程同時沖擊數據庫。
-
關鍵點:
-
鎖操作本身需要高效、可靠,避免成為瓶頸或單點。
-
鎖需要設置合理的超時時間,防止持有鎖的線程意外掛死導致死鎖。
-
等待的線程需要合理的等待/重試策略,避免無限等待或過度消耗資源。
-
兜底值的設計需要符合業務邏輯。
-
-
-
邏輯過期時間 (Logical Expiration):
-
核心思想:?在緩存的 Value 中,額外存儲一個邏輯過期時間戳,而 Key 本身的物理 TTL 設置得足夠長(甚至永不過期)。
-
流程簡述:
-
應用從緩存中讀取 Value。
-
檢查 Value 中的邏輯過期時間戳:
-
如果邏輯上未過期,直接使用緩存數據。
-
如果邏輯上已過期:
-
嘗試獲取分布式鎖(類似方案2)。
-
獲取鎖成功的線程負責異步更新數據(查詢數據庫、更新緩存 Value 和邏輯過期時間戳)。
-
其他線程在鎖被占用期間,繼續返回“過期”但可用的舊數據(犧牲一定的實時性,保證可用性),直到新數據更新完成。
-
-
-
-
優點:?結合了永不過期(無被動失效洪峰)和可更新(保證數據相對新鮮)的優點。在邏輯過期期間,用戶看到的是稍舊數據,但服務是正常的。
-
關鍵點:?增加了 Value 結構的復雜性,需要應用層處理邏輯過期判斷。
-
-
服務降級與熔斷:
-
作為最后防線或配合上述策略使用:
-
熔斷:?當檢測到針對特定 Key 或數據庫的訪問異常激增或錯誤率飆升時,觸發熔斷機制,暫時拒絕部分針對該 Key 的請求,保護數據庫。
-
服務降級:?在極端高壓或緩存/數據庫故障時,可以暫時關閉非核心功能,或者對于緩存失效的請求,直接返回簡化數據、默認值或排隊提示(“省電模式”),優先保障核心服務的可用性和數據庫的生存能力。
-
-