使用 Golang 的 samber/hot 進行記憶體快取 — 支援淘汰演算法 (LRU、LFU、TinyLFU、W-TinyLFU、S3FIFO、ARC、TwoQueue、SIEVE、FIFO)、TTL、快取載入器、分片、stale-while-revalidate、遺失鍵快取以及 Prometheus 指標。適用於正在使用或採用 samber/hot 的情況,當程式碼庫匯入 github.com/samber/hot,或專案頻繁載入相同的中低基數資源且需要降低延遲或後端壓力時。
角色定位: 你是一位將快取視為系統設計決策的 Go 工程師。你根據測量到的存取模式選擇淘汰演算法,根據工作集資料調整快取大小,並始終規劃過期、載入器失敗和監控事宜。
在 Go 中使用 samber/hot 進行記憶體快取
泛型、型別安全的 Go 記憶體快取函式庫,支援 Go 1.22+,提供 9 種淘汰演算法、TTL、具有 singleflight 去重功能的載入器鏈、分片、stale-while-revalidate 以及 Prometheus 指標。
官方資源:
此技能並非詳盡無遺。請參閱函式庫文件和程式碼範例以獲取更多資訊。Context7 可作為探索平台提供協助。如需 Go 套件文件、版本、符號和已知漏洞,→ 請參閱 samber/cc-skills-golang@golang-pkg-go-dev 技能。
go get -u github.com/samber/hot
演算法選擇
根據你的存取模式選擇 — 錯誤的演算法會浪費記憶體或降低命中率。
| 演算法 | 常數 | 最佳適用場景 | 應避免的情況 |
|---|---|---|---|
| W-TinyLFU | hot.WTinyLFU |
通用、混合工作負載(預設) | 你需要簡單性以便除錯 |
| LRU | hot.LRU |
近期主導(工作階段、近期查詢) | 頻率很重要(掃描污染會逐出熱門項目) |
| LFU | hot.LFU |
頻率主導(熱門商品、DNS) | 存取模式會變動(過時的熱門項目永遠不會被逐出) |
| TinyLFU | hot.TinyLFU |
讀取密集型且偏重頻率 | 寫入密集型(准入過濾器開銷) |
| S3FIFO | hot.S3FIFO |
高吞吐量、抗掃描 | 小型快取(<1000 個項目) |
| ARC | hot.ARC |
自我調整、未知模式 | 記憶體受限(2 倍追蹤開銷) |
| TwoQueue | hot.TwoQueue |
混合熱/冷分離 | 無法接受調整複雜度 |
| SIEVE | hot.SIEVE |
簡單的抗掃描 LRU 替代方案 | 高度傾斜的存取模式 |
| FIFO | hot.FIFO |
簡單、可預測的逐出順序 | 命中率很重要(無頻率/近期感知) |
決策捷徑: 從 hot.WTinyLFU 開始。僅在效能分析顯示未命中率過高而無法滿足 SLO 時才切換。
如需詳細的演算法比較、基準測試和決策樹,請參閱演算法指南。
核心用法
基本快取與 TTL
import "github.com/samber/hot"
cache := hot.NewHotCache[string, *User](hot.WTinyLFU, 10_000).
WithTTL(5 * time.Minute).
WithJanitor().
Build()
defer cache.StopJanitor()
cache.Set("user:123", user)
cache.SetWithTTL("session:abc", session, 30*time.Minute)
value, found, err := cache.Get("user:123")
載入器模式(Read-Through)
載入器會自動擷取遺失的鍵,並透過 singleflight 去重 — 對相同遺失鍵的並發 Get() 呼叫會共享一次載入器調用:
cache := hot.NewHotCache[int, *User](hot.WTinyLFU, 10_000).
WithTTL(5 * time.Minute).
WithLoaders(func(ids []int) (map[int]*User, error) {
return db.GetUsersByIDs(ctx, ids) // 批次查詢
}).
WithJanitor().
Build()
defer cache.StopJanitor()
user, found, err := cache.Get(123) // 未命中時觸發載入器
容量規劃
在設定快取容量之前,請估算記憶體預算中可容納多少項目:
- 估算單一項目大小 — 估算結構體的大小,加上堆積分配的欄位(切片、映射、字串)的大小。包含鍵的大小。每個條目約 100 位元組的粗略開銷涵蓋內部簿記(指標、過期時間戳、演算法元資料)。
- 詢問開發者在生產環境中為此快取分配了多少記憶體(例如 256 MB、1 GB)。這取決於服務的總記憶體以及與之共享程序的其他部分。
- 計算容量 —
capacity = memoryBudget / estimatedItemSize。向下取整以保留餘量。
範例:*User 結構體 ~500 位元組 + 字串鍵 ~50 位元組 + 開銷 ~100 位元組 = 約 650 位元組/條目
256 MB 預算 → 256_000_000 / 650 ≈ 393,000 個項目
如果項目大小未知,請要求開發者透過單元測試來測量,該測試分配 N 個項目並檢查 runtime.ReadMemStats。未經測量就猜測容量會導致 OOM 或記憶體浪費。
常見錯誤
- 忘記
WithJanitor()— 沒有它,過期的條目會留在記憶體中,直到演算法將其逐出。務必在 builder 中鏈式呼叫.WithJanitor()並使用defer cache.StopJanitor()。 - 在未設定遺失快取的情況下呼叫
SetMissing()— 執行時會 panic。請先在 builder 中啟用WithMissingCache(algorithm, capacity)或WithMissingSharedCache()。 WithoutLocking()+WithJanitor()— 互斥,會 panic。WithoutLocking()僅在沒有背景清理的單 goroutine 存取下才安全。- 快取過大 — 容納所有內容的快取就是一個帶有開銷的映射。根據你的工作集調整大小(通常是總資料的 10-20%)。監控命中率以驗證。
- 忽略載入器錯誤 —
Get()在載入器失敗時回傳(zero, false, err)。務必檢查err,而不僅僅是found。
最佳實踐
- 始終設定 TTL — 無限制的快取會無限期提供過時資料,因為沒有重新整理的訊號
- 使用
WithJitter(lambda, upperBound)來分散過期時間 — 沒有抖動,同時建立的項目會同時過期,導致對載入器的驚群效應 - 使用
WithPrometheusMetrics(cacheName)進行監控 — 命中率低於 80% 通常表示快取大小不足或演算法不適合工作負載 - 對可變值使用
WithCopyOnRead(fn)/WithCopyOnWrite(fn)— 沒有複製,呼叫者會變異快取物件並破壞共享狀態
如需進階模式(重新驗證、分片、遺失快取、監控設定),請參閱生產模式。
如需完整的 API 表面,請參閱API 參考。
如果你在 samber/hot 中遇到錯誤或非預期行為,請在 https://github.com/samber/hot/issues 開啟問題。
交叉參考
- → 請參閱
samber/cc-skills-golang@golang-performance技能,了解一般快取策略以及何時使用記憶體快取 vs Redis vs CDN - → 請參閱
samber/cc-skills-golang@golang-observability技能,了解 Prometheus 指標整合和監控 - → 請參閱
samber/cc-skills-golang@golang-database技能,了解與快取載入器搭配的資料庫查詢模式 - → 請參閱
samber/cc-skills@promql-cli技能,了解透過 CLI 查詢 Prometheus 快取指標






