Redis 快取與協調模式

最後更新: 2026-09-03 15:56:43 +0800 +0800
原文以英文撰寫。您正在閱讀AI翻譯版本。

Redis 是記憶體內資料結構伺服器,可以加速讀取並協調有界作業。正式環境設計必須定義真實來源、一致性視窗、容量原則、失敗行為以及每個金鑰的擁有者。持久化與複寫改善復原選項;它們不會賦予快取來源資料庫的交易不變量。

Cache-Aside 與一致性視窗 #

Cache-aside 將讀寫原則留在應用程式中:

ClientmissresponAspeplicatSiEoTnvraelaudeEXDRatettdalibsase

應用程式讀取 Redis,從資料庫載入遺漏值,以 TTL 儲存結果,並回傳結果。來源寫入必須使受影響的金鑰失效或重新整理。由於資料庫提交與快取變更不是單一原子作業,讀取者可能在兩個事件之間觀察到過期資料。請定義可接受視窗;快取失效必須在程序失敗後仍存活時,使用交易型 outbox 或變更資料串流。

當不存在識別碼的重複請求一律抵達資料庫時,就會發生快取穿透。確認權威來源不存在後,儲存短期負面哨兵,但絕不要讓該哨兵超過建立必須可見的時間間隔。Bloom filter 可以在查找前拒絕確定不存在的值;誤判陽性仍會抵達後端系統。

雪崩與重新整理 #

熱門金鑰到期時,並行遺漏可能重複昂貴的載入。緩解方式包括:

  • 加入隨機 TTL 抖動,使相關金鑰不會同時到期;
  • 允許一個有界重新整理器重建金鑰,其他請求使用過期資料;
  • 合併同一金鑰的程序內請求;
  • 在熱門金鑰硬性到期前重新整理。

重新整理鎖需要租約與失敗路徑。它不能將快取遺漏變成無限等待,而 Redis 可能不可用,因此後端服務仍需要超載控制。

到期、逐出與記憶體壓力 #

以原子方式設定值與到期時間:

redis
SET session:42 '{"user":42}' EX 1800

Redis 在存取時被動移除到期金鑰,並主動取樣具有 TTL 的金鑰。因此到期定義邏輯可見性,而不是回收記憶體的精確時刻。選擇 TTL 應考量新鮮度、重新計算成本、物件大小與同步到期風險。

maxmemory 限制資料可用的記憶體,而 maxmemory-policy 選擇對壓力的回應。原則包括 noeviction、全金鑰或易失金鑰的 LRU/LFU 近似、隨機逐出,以及以易失 TTL 為基礎的逐出。易失原則只考慮具有到期時間的金鑰,當沒有合資格的金鑰時可能表現得像 noeviction。請監控已用記憶體、碎片、逐出、到期、遭拒寫入、延遲與快取命中率,而不要假設設定的 TTL 保證駐留。

分散式鎖與圍欄 #

使用唯一擁有權權杖與有界租約取得單一實例 Redis 鎖:

redis
SET lock:invoice:42 4d1c9f... NX PX 30000

只有權杖仍相符時才釋放,並使用一個原子腳本:

lua
if redis.call("GET", KEYS[1]) == ARGV[1] then
  return redis.call("DEL", KEYS[1])
end
return 0

租約限制遺棄,但不證明獨佔存取。程序可能暫停超過 TTL、失去鎖、恢復,並與新持有者並行寫入。下游資源可能損毀時,請發出單調遞增的圍欄權杖,並要求該資源拒絕比已觀察到最大值更舊的權杖。

複寫是非同步的,時鐘與暫停會影響租約推理,而網路分割可能隱藏擁有權變更。已有資料庫限制、條件寫入或共識協調器保護不變量時,請優先使用它。Redis 鎖應保護失敗模型已文件化的有界、可重試工作。

固定視窗速率限制 #

固定視窗為每個主體與時間區段儲存一個計數器。原子地遞增、設定初始到期時間並作出決定:

lua
local count = redis.call("INCR", KEYS[1])
if count == 1 then
  redis.call("EXPIRE", KEYS[1], tonumber(ARGV[1]))
end
if count > tonumber(ARGV[2]) then
  return {0, count}
end
return {1, count}

固定視窗需要固定狀態,但一個區段結束與下一個開始時的流量,可能產生接近名義限制兩倍的突發。請一致推導區段識別;用戶端時鐘可能不一致,因此邊界精度重要時,請在腳本中使用伺服器時間,或讓一個主體通過一致的時鐘網域路由。

滑動視窗與桶 #

排序集合滑動記錄會以時間戳作為分數,為每個請求儲存唯一成員。一個腳本會移除視窗之前的項目、計算剩餘項目、條件式加入請求並設定 TTL。演算法強制執行滾動計數,但記憶體消耗與已接受事件成正比。即使多個請求共用時間戳,成員也必須保持唯一。

令牌桶儲存令牌餘額與最後補充時間。每個請求會依經過時間補充至容量上限,然後在可用時消耗一個令牌。它在允許設定的突發下強制長期速率。洩漏桶則以固定速率排出佇列工作,使輸出平滑,但需要有界佇列與明確的拒絕原則。

Lua 腳本與 Redis Functions 讓每項決策在一個 Redis 節點上保持原子性。請保持腳本具確定性並有界;長腳本會阻塞其他命令執行。只有在數值確實反映所選演算法時,才回傳限制、剩餘容量與重設或重試時間。

叢集放置、重試與可觀測性 #

Redis Cluster 要求單一交易、腳本或函式觸及的每個金鑰都屬於相同雜湊槽。使用 rate:{account-42}:write 等雜湊標籤共置相關金鑰,而不強迫不相關帳戶進入同一槽。一個熱門主體仍可能使其槽超載。

逾時不證明變更命令失敗。盲目重試 INCR、XADD 或速率限制腳本可能重複效果,因此需要精確一次記帳時,請附加作業 ID,或讓腳本偵測重複請求。限制重試並暴露 Redis 錯誤,不要靜默地以無限制存取取代嚴格限制;安全敏感系統必須刻意選擇失效開放或失效關閉。

記錄腳本延遲、錯誤、遭拒與允許計數、熱門金鑰、槽分布、複本延遲、故障轉移、記憶體壓力與時鐘偏移。這些訊號可區分原則行為與 Redis 飽和或拓撲變更。

參考資料 #

評論

在 GitHub 上閱讀或評論(發表評論需要登入)