go-cache初始化易panic,因defaultexpiration不能为0(须用cache.noexpiration)、cleanupinterval必须大于0;它无容量限制与淘汰策略,高写入易oom,不适合替代sync.map。

go-cache 不是开箱即用的缓存,初始化错一个参数就可能让内存持续上涨直到 OOM;它适合中小规模、带 TTL 的本地缓存,但绝不该被当成 sync.Map 的替代品来用。
cache.New() 为什么一运行就 panic
常见错误是传了 0 或负数给 cache.New() 的两个参数。第一个是默认过期时间(defaultExpiration),第二个是清理间隔(cleanupInterval)。传 0 会触发内部 time.Duration 转换失败,导致 nil 指针解引用 panic。
-
defaultExpiration设为 0 ≠ 永不过期,必须用cache.NoExpiration -
cleanupInterval必须 > 0;设为 0 不仅不清理,还会 panic - 别用
cache.New(0, 0)试图“禁用过期”——这是关掉清理器 + 所有条目永不过期 - 正确写法:
cache.New(5*time.Minute, 1*time.Minute)或cache.New(cache.NoExpiration, 30*time.Second)
Get() 返回 (nil, false) 却刚 Set 过,怎么回事
这不是并发竞争问题,而是语义误读或数据类型陷阱。go-cache 的 Get() 返回 (interface{}, bool),第二个布尔值才表示“是否命中有效条目”,nil 值本身不能用来判断存在性。
- 存了
nil *User,Get()会返回(nil, true),不是未命中 - 存了
map/slice/chan等引用类型,后续修改原变量会“意外”改掉缓存内容 - 并发写入时没注意:
Set传的是指针,多个 goroutine 共享底层数据,Get出来看似一致,实际是同一块内存 - 正确写法永远是:
if val, found := cache.Get("key"); found { /* 处理 val */ } else { /* 真未命中,该查 DB */ }
Set() 返回 false 就代表失败?
完全不是。cache.Set() 返回 bool 表示“是否首次插入该 key”,和操作成败无关。它几乎不会失败(内部 map 扩容失败极罕见,且不暴露 error)。
- key 已存在 → 更新 value 并返回
false - key 不存在 → 插入并返回
true - 误用
if !cache.Set(k, v, exp) { log.Println("set failed") }会导致日志刷屏 - 需要“首次设置”语义,请用
cache.Add(key, value, exp),它只在 key 不存在时写入,失败才返回false - 想实现“有则跳过、无则设且带过期”,只能自己先
Get()判断,go-cache 不提供 CAS 类型操作
为什么缓存用了还是扛不住突发流量
因为 go-cache 没有容量限制、没有淘汰策略(如 LRU/LFU)、不控制总 key 数量。它只靠 TTL + 定时扫描清理,内存只增不减是常态。
- 高写入场景下,内存持续上涨,GC 压力大,甚至 OOM
- 没有最大容量限制,
Set永远成功,不会像bigcache那样丢老数据腾空间 - 如果需要容量控制,要么自己封装一层计数 + 删除逻辑,要么换
lru或fastcache库 - 真正难处理的从来不是“怎么存”,而是“什么时候删、删哪些、删完怎么降级”。go-cache 把这部分交给你手动兜底
最常被忽略的其实是清理间隔(cleanupInterval)和引用类型存取——前者设错直接让过期项堆积,后者让缓存变成“共享内存黑盒”,debug 时连地址都对不上。











