go-cache可直接用于Gin但初始化易panic:defaultExpiration不能为0(须用cache.NoExpiration),cleanupInterval必须大于0;Get/Set非原子操作,高频场景需双检加锁或singleflight.Group。

go-cache 能直接用在 Gin 里,但初始化错一个参数就会 panic,高频场景下还会击穿缓存——不是不能用,而是得绕开几个硬坑。
go-cache.New() 初始化必须避开两个零值参数
常见 panic 错误是 invalid memory address 或 cleanupInterval must be greater than 0,本质都是传了非法 duration。go-cache 内部会把 0 当作未初始化信号,触发 nil 解引用或除零。
-
defaultExpiration不能传0:要用cache.NoExpiration或具体时长(如5 * time.Minute) -
cleanupInterval必须 > 0:推荐设为30 * time.Second到2 * time.Minute之间,太小会频繁扫描,太大导致过期项堆积 - 错误写法:
cache.New(0, 0)、cache.New(time.Second, 0);正确写法:cache.New(10*time.Minute, 30*time.Second)
Get/GetOrSet 不是线程安全的原子操作
go-cache 的 Get 和 Set 是分开调用的,中间没有锁保护。如果多个 goroutine 同时发现缓存 miss,都会去查 DB,造成“缓存击穿”。
- 不要这么写:
if v, ok := c.Get(key); !ok { v = loadFromDB(); c.Set(key, v, ttl) } - 高频读写场景必须加双检 + 锁,例如用
sync.RWMutex包一层,或者换singleflight.Group - 低频调用(比如配置项、静态字典)可忽略,但临床系统里“研究中心列表”这类接口,QPS 超 50 就得警惕
Gin 中挂载缓存实例的推荐方式
别在 handler 里每次 new 一个 cache,也不要把 cache 塞进 c.Keys 这种请求级上下文——它该是全局单例,且生命周期和应用一致。
- 在
main.go初始化后,存到全局变量或注入到 Gin 的Engine的Keys(注意是 engine.Keys,不是 context.Keys) - 示例:
gin.SetMode(gin.ReleaseMode); r := gin.New(); r.Set("cache", myCache),然后在 handler 里用c.MustGet("cache").(*cache.Cache) - 避免用
sync.Map替代:它不支持 TTL,删不干净,写多时内存暴涨,真不如直接上 go-cache
缓存 key 设计要防冲突,尤其带参数的接口
直接拼接字符串当 key 很容易撞车,比如 "sites_" + trialID 看似简单,但若 trialID 为空或含特殊字符,Get 会静默失败。
- key 应做标准化处理:trim 空格、转小写、替换非法字符(如用
strings.ReplaceAll(trialID, " ", "_")) - 建议加前缀隔离域,比如
"trial_sites:" + normalizedID,避免和用户、药品等其他缓存 key 冲突 - 别把整个 struct 当 key:go-cache 的 key 是
string,强行用fmt.Sprintf("%v", obj)既慢又不可靠
go-cache 的核心价值是“够用且可控”,但它不会自动帮你解决并发竞争、key 冲突、过期策略这些事——这些都得你写代码兜住。真正上线前,至少压测一次缓存 miss 高峰,看 DB 是否被打穿。


















