go-cache 不适合有容量限制或强淘汰需求的本地缓存,因无 LRU/LFU、不控内存总量,仅依赖过期+定时清理,高写入易 OOM;cache.New(0, 0) 会 panic 因参数需合法 time.Duration,cleanupInterval 必须 > 0。

go-cache 不适合做有容量限制或强淘汰需求的本地缓存 —— 它没有 LRU/LFU,不控内存总量,只靠过期+定时清理,高写入下极易 OOM。
为什么 cache.New(0, 0) 会 panic
因为 cache.New 的两个参数都必须是合法的 time.Duration:第一个参数 defaultExpiration 若为 0,会被转成 -1(即 cache.NoExpiration),但传负数会直接触发内部 time.Duration 转换失败;第二个参数 cleanupInterval 必须 > 0,否则后台 janitor goroutine 不启动,且传 ≤ 0 会 panic。
- 错例:
cache.New(0, 0)→ 触发 nil 指针解引用 - 正确写法:
cache.New(cache.NoExpiration, 30*time.Second)或cache.New(10*time.Minute, 1*time.Minute) - 注意:
cache.DefaultExpiration是常量 0,仅用于Set第三个参数,表示“用实例默认值”,不能传给New
Set 和 SetDefault 的并发行为差异
Set 总是覆盖,SetDefault 只在 key 完全不存在(包括已过期被删)时才写入。但它不检查 value 是否为空,也不提供 CAS 语义。
- 想实现“存在则跳过、不存在则设且带过期”,必须手动
Get+ 判断 +Set,go-cache不内置该逻辑 - 并发场景下,多个 goroutine 同时调用
SetDefault可能因竞态导致多次写入(虽概率低,但非原子) - 若存的是指针(如
&user)或 map/slice,后续修改原变量会“透传”影响缓存内容 —— 缓存是值拷贝,但引用类型底层共享
过期不是淘汰:DeleteExpired 不解决内存膨胀
DeleteExpired 只删已过期项,但不会限制总 key 数量或内存占用。即使每秒 Set 1000 个新 key(哪怕都设 1s 过期),只要清理间隔不够密,或 GC 来不及回收,内存就持续上涨。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 后台 janitor 默认每
cleanupInterval扫描一次全部 items,时间复杂度 O(n),大缓存下开销明显 - 没有容量上限机制:Set 永远成功,不像
ristretto或lru.Cache可设MaxCost或Size - 所谓“惰性删除”仅发生在
Get时检查过期并删 —— 但若 key 再也没被 Get,就永远留在内存里
真正可控的替代方案选型建议
别在生产环境硬改 go-cache 加淘汰逻辑。需要 TTL + 容量控制 + 并发安全,直接换库更省心:
- 首选
ristretto:支持TTL、基于 cost 的驱逐(不是简单计数)、无锁读、异步清理;初始化用MaxCost: 100 * 1024 * 1024,别设NumCounters - 次选
lru.Cache+ 懒检查:自己封装Set存value和expireAt time.Time,Get时先判expireAt.Before(time.Now())再删,不自动返回(nil, false) - 绝对避开
sync.Map做带过期/淘汰的缓存:它不支持遍历、无访问序、Range 无序,根本没法做 LRU
最易被忽略的点:每个缓存项多存一个 time.Time 字段,意味着一次堆分配;高频短生命周期缓存(如 request-scoped)宁愿用毫秒级 int64 时间戳,也别盲目追求纳秒精度。

















