最常遇到的坑是传 nil 或空字符串作 key 导致 panic,因 freecache 不校验 key 长度;bigcache 的 setstring() 会拷贝 value 确保生命周期独立,set() 则不拷贝易致脏数据;高并发读下 bigcache 延迟更稳,freecache 支持精确 ttl 和 lru;避免缓存击穿需用 singleflight.group 或两级 ttl。

freecache 写入时 panic: runtime error: index out of range
这是最常遇到的坑:直接把 nil 或空字符串当 key 传给 freecache.Set(),底层用 unsafe 操作字节数组时越界。它不校验 key 长度,也不拒绝空 key。
实操建议:
- 写入前强制检查
key != nil && len(key) > 0,尤其从 HTTP query、JSON 字段或数据库查出的字段可能为空 - 别用
string(nil)或string([]byte(nil))构造 key —— 这会得到空字符串,但看着像“有值” - 如果 key 来自用户输入,统一加前缀(如
"user:" + userID),既防空也防冲突
bigcache 的 SetString() 和 Set() 有什么区别
表面看只是参数类型不同,实际影响缓存行为和 GC 压力。
SetString(key, value string) 内部会拷贝字符串底层数组,确保 value 生命周期独立于调用栈;而 Set(key, value []byte) 默认不拷贝,如果传入的是局部 []byte(比如从 json.Marshal() 返回的临时切片),后续被复用或回收后,缓存里就变成脏数据或 panic。
实操建议:
- 优先用
SetString(),除非你明确控制 value 的生命周期且确认不会被复用 - 若必须用
Set(),对来源不确定的[]byte显式拷贝:append([]byte(nil), src...) - 注意
bigcache.NewBigCache()的Shards参数默认是 256,小服务设太高反而增加锁竞争,16~64 更稳
freecache 和 bigcache 在高并发读下的表现差异
两者都靠分片(shard)减少锁争用,但 freecache 每个 shard 是带 LRU 链表的 map,bigcache 是纯数组 + 环形缓冲区。这意味着:freecache 查 key 要哈希再遍历链表(O(1) 平均但有抖动),bigcache 是纯计算偏移(O(1) 稳定),但淘汰策略更粗粒度。
实操建议:
- 热点 key 集中(比如
"config:global"被每秒几千次读)—— bigcache 更稳,freecache 可能因单 shard 锁导致延迟毛刺 - 需要精确 TTL 或按访问频次淘汰 —— freecache 支持
SetWithTTL()和 LRU,bigcache 的 TTL 是全局 tick 触发,精度默认 1 秒 - 内存敏感场景(比如容器内存限制紧)—— bigcache 实际占用略低,freecache 的指针和链表节点有额外开销
本地缓存失效后怎么避免缓存击穿
freecache/bigcache 都不提供原子性“查缓存 → 查 DB → 写缓存”封装,漏掉这步就会在热点 key 过期瞬间涌进大量 DB 请求。
实操建议:
- 不要裸用
Get()+if nil { load from DB; Set() },必须加单 key 互斥锁(比如用singleflight.Group) - 对一定会查 DB 的 key,设两级 TTL:主缓存 TTL 短(如 30s),后台 goroutine 定期刷新并设长 TTL(如 5m),让过期时间错开
- freecache 的
GetCb()回调只在 miss 时触发,但不解决并发问题;bigcache 没类似机制,得自己包一层
unsafe 或共享底层数组的地方,日志里看不到错误,只看到延迟飙升或偶发 panic。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











