在後端與系統架構面試中,Redis 是出現頻率極高的必考題。然而,面試官早已不再滿足於「Redis 有哪些資料結構」或「記憶體資料庫為什麼快」這種基礎問答,他們真正關注的是:你是否在高併發環境下踩過坑?你知道 Redis 的機制邊界在哪裡嗎?
Redis 本身效能極高且使用簡單,但「用錯」的代價極其昂貴。本文將從快取崩潰、分散式鎖死穴、併發 race condition、生產效能黑洞與高可用一致性,全面系統化拆解 15 個最常踩爆的 Redis 實戰陷阱。
一、 快取三大經典災難:不只是背概念,更要懂邊界
1. 快取穿透(Cache Penetration)
- 現象:請求查詢根本不存在的資料,每次都繞過 Redis 直接衝擊 DB。
- 面試陷阱:只回答「加布隆過濾器(Bloom Filter)」,卻說不出誤判率(False Positive)與動態維護/刪除成本。
- 實務最佳解:布隆過濾器 + 空值快取(Set Null with Short TTL)+ 嚴格的前端/API 參數校驗。對不存在的 Key 寫入
null並設定 1~5 分鐘的短 TTL,即可阻擋絕大多數惡意掃描。
2. 快取擊穿(Cache Breakdown)
- 現象:某個超熱點 Key(Hot Key) 在過期的瞬間,恰好有成千上萬的併發請求湧入,直接把 DB 壓垮。
- 面試陷阱:以為「直接加鎖」就萬事大吉,卻忽略了鎖粒度過大導致的請求排隊與 Timeout。
- 實務最佳解:
- 互斥鎖重建(Mutex Lock):僅允許一個請求拿到鎖去 DB 查資料並回寫快取,其他請求等待重試。
- 邏輯過期(Logical Expiration):熱點 Key 不設定物理 TTL,而是在 Value 中儲存過期時間欄位。當程式發現邏輯過期時,非同步派發 Background Worker 去更新快取,主流程直接回傳舊值。
3. 快取雪崩(Cache Avalanche)
- 現象:大量 Key 在同一個時間點集中過期,或是 Redis 節點直接宕機,流量瞬間全數傾倒至 DB。
- 面試陷阱:只記得把 TTL 加上隨機值,卻忽視了伺服器層級的單點故障(SPOF)。
- 實務最佳解:
- TTL 隨機化:基礎 TTL 加上 1~5 分鐘的微小隨機抖動值(Jitter),分散過期時間。
- 架構高可用:部署 Redis Sentinel 或 Cluster 部署,並搭配服務層的限流降級(Rate Limiting & Circuit Breaker)。
二、 分散式鎖:面試官最愛追問的「連環死穴」
分散式鎖是 Redis 陷阱最多的區域,市面上許多教科書範例都是嚴重有 Bug 的寫法:
Plaintext
❌ 錯誤 1:SETNX + EXPIRE 分成兩步執行 (非原子性,Crash 即死鎖)
❌ 錯誤 2:解鎖時直接用 DEL 命令 (可能把別人剛取得的鎖誤刪)
❌ 錯誤 3:鎖的 Value 用固定值如 "1" (無法識別持有者)
❌ 錯誤 4:TTL 設太短且無自動續期 (業務沒執行完鎖就過期,併發直接失控)
正確的分散式鎖設計防線:
- 原子性加鎖:必須使用
SET key uuid NX PX 30000(單一指令完成加鎖與過期時間設定)。 - 安全釋放鎖:解鎖時必須使用 Lua 腳本 檢查 Value 是否為當前客戶端的 UUID,確認無誤後才執行
DEL。 - 續命機制(Watchdog):業務執行時間不可控時,必須有背景線程定期續期(如 Redisson 預設的 Watchdog 機制)。
資深追問:Redlock 真的安全嗎?主從切換會丟鎖嗎?
主從異步複製風險:Master 拿到鎖後尚未同步到 Slave 就宕機,Slave 升級為 Master 後,另一個 Request 能再次拿到同一把鎖。 Redlock 爭議:Martin Kleppmann 曾提出著名的質疑,Redlock 極度依賴系統時鐘(System Clock),若發生時鐘漂移或 GC Pause(垃圾回收停頓),鎖依然會失效。超高一致性要求的場景應考慮採用 Fencing Token 或 ZooKeeper/etcd。
三、 併發修改同一個 Key:別一聽到併發就上鎖!
經典超賣範例:
Plaintext
// ❌ 典型的 Check-then-Act 併發漏洞
stock = GET product_stock
if (stock > 0) {
SET product_stock = stock - 1
}
當 100 個 Request 同時
GET 到 stock = 1,全部人都會通過檢查並寫回 0,最終導致賣出 100 件商品(超賣)。架構師原則:能用原子指令,就不要上鎖!
- 簡單數值操作:直接使用 Redis 原生的原子指令
DECR/DECRBY/INCR。 - 複雜業務邏輯:將「查詢、判斷、扣減」寫入 Lua 腳本 執行。Redis 執行 Lua 腳本具備單線程原子性,能避免鎖帶來的額外通訊開銷與複雜度。
四、 生產環境的效能黑洞(Performance Hazards)
| 問題 | 為什麼是災難? | 實務正確解法 |
KEYS * | Redis 是單線程模型!對數百萬個 Key 執行 KEYS * 會直接卡死主線程,導致全站逾時。 | 生產環境嚴禁使用,改用 SCAN 分頁漸進式遍歷。 |
| Big Key | 大型的 Hash / List / Set 讀寫時會阻塞網路與主線程,且過期刪除時會造成 Redis 瞬間停頓。 | 拆分小 Key、壓縮資料、刪除時採用非同步刪除 UNLINK。 |
| Hot Key | 單一 Key 存取量過大,導致單一 Redis 節點 CPU/網卡流量暴增崩潰。 | 導入本地快取(Caffeine/Memory)、將 Key 加上隨機 Hash 後綴打散至不同 Shard。 |
| Pipeline 誤當 Transaction | Pipeline 只是將多條指令打包一次傳送以減少 RTT,不保證原子性。 | 需要原子性與事務隔離時,必須使用 Lua 腳本 或 MULTI/EXEC。 |
五、 高可用、持久化與資料一致性
許多工程師習慣把 Redis 當成「不會掛的強一致性資料庫」,這在架構設計上是極其危險的。
- RDB vs AOF 的權衡:
- RDB(快照):恢復速度極快,但備份間隔期間宕機會遺失資料。
- AOF(日誌):資料安全性高,但日誌檔龐大;在 AOF Rewrite(重寫)期間,
fork()子進程可能引發記憶體暴漲與 Page Cache 鎖定。
- Sentinel vs Cluster 腦裂(Split-Brain):
- 當網路發生分區,舊 Master 與 Slave 失去聯繫,Sentinel 會推舉新 Master;但舊 Master 若仍能接收客戶端寫入,當網路恢復時,舊 Master 降級為 Slave 並同步新 Master 的資料,期間寫入舊 Master 的資料將永久遺失。
- 快取與資料庫的一致性(Cache Aside Pattern):
- 標準做法:先更新 DB,再刪除快取(Delete Cache),而不是更新快取。
- 極限併發極短延遲:可搭配「延遲雙刪(Delayed Double Delete)」或監控 DB Binlog(如 Canal)非同步刷新快取。
六、 總結:Redis 實戰思維地圖
要區分「只會背指令」與「具備架構思維」的工程師,看他處理 Redis 的層級即可劃分:
| 層級 | 考點範疇 | 資深工程師的思考角度 |
| L1. 基礎應用 | 資料結構、過期策略、基本命令 | 「這個場景用 Hash 還是 String 記憶體開銷比較低?」 |
| L2. 機制與防禦 | 快取三大問題、分散式鎖基本寫法 | 「空值快取的記憶體代價與布隆過濾器的維護成本如何權衡?」 |
| L3. 併發與陷阱 | 原子性、Lua 腳本、鎖失效邊界 | 「為什麼能用 DECR 或 Lua 就絕對不要隨便上分散式鎖?」 |
| L4. 生產與架構 | Big/Hot Key、高可用、主從一致性 | 「Redis 絕非強一致性 DB,如果發生主從斷連,我們的降級與備援機制是什麼?」 |
核心記憶口訣:
快取穿透靠空值與布隆;擊穿靠邏輯過期與互斥鎖;併發優先用原子指令與 Lua;分散式鎖注意過期與解鎖身份;Redis 是極致的快取,永遠為它準備好 DB 降級與回源退路。
沒有留言:
張貼留言