golang-samber-hot

golang-samber-hot

热门

使用 samber/hot 在 Golang 中进行内存缓存——支持淘汰算法(LRU、LFU、TinyLFU、W-TinyLFU、S3FIFO、ARC、TwoQueue、SIEVE、FIFO)、TTL、缓存加载器、分片、stale-while-revalidate、缺失键缓存和 Prometheus 指标。适用于正在使用或采用 samber/hot 的项目,代码库导入 github.com/samber/hot 时,或项目频繁加载相同的中低基数资源且需要降低延迟或后端压力时。

2261Star
150Fork
更新于 2026/6/6
SKILL.md
只读
名称
golang-samber-hot
描述

使用 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) // 未命中时触发加载器

容量估算

在设置缓存容量之前,估算内存预算内能容纳多少项:

  1. 估算单项目大小——估算结构体大小,加上堆分配字段(切片、映射、字符串)的大小。包括键大小。粗略的每项开销约 100 字节,用于内部记账(指针、过期时间戳、算法元数据)。
  2. 询问开发者生产环境中为此缓存分配了多少内存(例如 256 MB、1 GB)。这取决于服务的总内存以及共享进程的其他部分。
  3. 计算容量——capacity = memoryBudget / estimatedItemSize。向下取整以留出余量。
示例:*User 结构体 ~500 字节 + 字符串键 ~50 字节 + 开销 ~100 字节 = 约 650 字节/项
         256 MB 预算 → 256_000_000 / 650 ≈ 393,000 项

如果项目大小未知,请要求开发者通过单元测试测量,分配 N 个项目并检查 runtime.ReadMemStats。未经测量就猜测容量会导致 OOM 或内存浪费。

常见错误

  1. 忘记 WithJanitor()——没有它,过期条目会留在内存中直到被算法淘汰。始终在构建器中链式调用 .WithJanitor()defer cache.StopJanitor()
  2. 未配置缺失缓存就调用 SetMissing()——运行时 panic。先在构建器中启用 WithMissingCache(algorithm, capacity)WithMissingSharedCache()
  3. WithoutLocking() + WithJanitor()——互斥,会 panic。WithoutLocking() 仅在没有后台清理的单 goroutine 访问下安全。
  4. 缓存过大——缓存所有内容相当于带开销的映射。根据工作集调整大小(通常为总数据的 10-20%)。监控命中率以验证。
  5. 忽略加载器错误——加载器失败时 Get() 返回 (zero, false, err)。始终检查 err,而不仅仅是 found

最佳实践

  1. 始终设置 TTL——无界缓存会无限期提供过期数据,因为没有刷新信号
  2. 使用 WithJitter(lambda, upperBound) 分散过期时间——没有抖动,同时创建的项会同时过期,导致加载器上的惊群效应
  3. 使用 WithPrometheusMetrics(cacheName) 监控——命中率低于 80% 通常意味着缓存过小或算法不适合工作负载
  4. 对可变值使用 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 缓存指标