bigcache并非通用缓存,仅在高并发、低延迟且value为小而稳定的[]byte或string时提效;参数错误、类型误用或清理遗漏会导致性能反低于sync.map。

直接结论:BigCache 不是“拿来即用”的通用缓存,它只在高并发、低延迟、value 为小而稳定的 []byte 或 string 场景下真正提效;用错参数、传错类型、漏掉清理,性能反而比 sync.Map 还差。
Shards 设置不合理会导致锁争抢,而不是提升并发
shards 不是越多越好,也不是默认 256 就安全。它本质是分片数,每个 shard 独立加锁,目标是让并发写尽量落在不同 shard 上。
- QPS 1k~5k:建议设为
128或256,但必须是 2 的幂(shardMask依赖位运算) - QPS > 5k:可试
512,但实测4096在小服务里多占 20MB+ 内存,且哈希分布不均时反而加剧局部锁争抢 - 不设或设为 1:所有写操作串行排队,吞吐骤降,
Set变成瓶颈 - 非 2 的幂:触发 panic 或行为异常(源码用
hashedKey & c.shardMask替代取模)
Set() 和 SetString() 混用会引发脏数据或 panic
区别不在参数类型,而在是否拷贝底层数组。BigCache 的 Set() 默认不拷贝,SetString() 内部自动拷贝——这决定你缓存的是“快照”还是“引用”。
- HTTP body、DB 字段、
json.Marshal()返回的临时[]byte→ 必须用SetString(key, string(value)),或显式拷贝:append([]byte(nil), value...) - 传入未拷贝的临时切片(如
buf[:n]),下一次 GC 或内存复用后,Get()可能读出乱码甚至 panic - 空
string或nil []byte传给Set()不 panic,但Get()返回ErrNotFound,容易掩盖逻辑错误
Get 返回 nil 不代表 key 不存在
Get() 返回 nil 有三种可能:key 不存在、entry 已过期、entry 被 LRU 替换覆盖。BigCache 不保证淘汰顺序,也不提供原子性“查-设”操作。
- 业务判断不能只靠
if entry == nil,必须检查 error:if err == bigcache.ErrKeyNotFound - 缓存失效瞬间大量请求穿透 DB?BigCache 不兜底,必须自己加
singleflight.Group包裹 DB 查询 -
LifeWindow设为 0 或未设置 → TTL 功能完全失效(后台 goroutine 不启动) - key 到期后不会立刻消失,最多再活 1 秒(clean tick 间隔),别依赖“立刻失效”做业务一致性判断
结构体缓存反而更慢,别硬塞
BigCache 要求 value 最终是 []byte,每次 Set() 都要序列化,每次 Get() 都要反序列化。这个开销在结构体字段多、嵌套深时,会明显盖过“零 GC”优势。
- 含 5 个 string 字段 + 1 个嵌套 map 的 struct,JSON 序列化存 BigCache,比
go-cache存指针慢约 30% - value 是 struct?封装一层:
func (c *Cache) SetUser(id int, u User) error { data, _ := json.Marshal(u); return c.cache.Set(strconv.Itoa(id), data) } - 70% 以上缓存 value 是结构体,且更新频率 > 10 次/秒 → 换
ristretto或go-cache,别为“无 GC”标签牺牲 CPU - 真正适合的场景:
token字符串、session ID、预序列化的 API 响应体(如从http.ResponseWriter直接 copy 的[]byte)
最难的不是初始化一个 bigcache.NewBigCache(),而是把 value 生命周期、热点 key 分布、TTL 精度、穿透防护这四件事对齐。漏掉任意一环,“高性能”就只剩幻觉。











