使用 samber/hot 在 Golang 中进行内存缓存——支持淘汰算法(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 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()——没有它,过期条目会留在内存中直到被算法淘汰。始终在构建器中链式调用.WithJanitor()并defer cache.StopJanitor()。 - 未配置缺失缓存就调用
SetMissing()——运行时 panic。先在构建器中启用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 缓存指标






