go-cache优于map+sync.rwmutex,因其采用32段分片锁降低争抢,内置lru+ttl自动淘汰,避免手动锁管理风险及内存泄漏。

为什么不用 map + sync.RWMutex 就直接上 go-cache
因为 map 本身不是并发安全的,手动加 sync.RWMutex 容易漏锁、死锁或读写冲突,尤其在高频写+低频读场景下,锁竞争会明显拖慢吞吐。而 go-cache 内部用分段锁(sharded mutex)把 key 映射到 32 个独立锁上,大幅降低争抢概率;它还自带过期淘汰(LRU + TTL),map 得自己轮询清理,否则内存只增不减。
实操建议:
- 适合中小规模缓存(
- 别存指针或未导出字段结构体——
go-cache不深拷贝,多 goroutine 修改同一 value 会踩内存 - 初始化时设好
defaultExpiration和cleanupInterval,否则过期 key 永远不删,cleanupInterval: time.Minute是较稳的选择 - 避免用
Set(key, nil, ...),它不会清除 key,只是存个 nil;要用Delete(key)
bigcache 的 “零 GC” 是怎么做到的,什么情况下反而更慢
bigcache 把所有 value 序列化后塞进一个大 []byte,key 则存在哈希表里指向 offset,避免为每个 value 分配堆内存,从而减少 GC 压力。但它要求 value 必须是可序列化的(通常用 []byte 或 string),且 key 必须是 string。
实操建议:
- 适合高吞吐、value 较小(
- 如果 value 频繁变更,每次
Set都要重新序列化+拷贝,比go-cache直接存指针慢;此时宁愿用go-cache+ 自定义sync.Pool复用 buffer - 初始化必须传
shards(默认 256),太小会锁争抢,太大浪费内存;QPS > 10k 时建议 ≥ 512 - 没有内置 TTL,得靠
Get时检查时间戳字段,或外层包一层带过期逻辑的 wrapper
cache.Set() 返回 false 却没报错?这是正常行为
go-cache 的 Set 方法返回 bool 表示“是否成功插入新 key”,而不是“是否执行成功”。如果 key 已存在,它会更新 value 但返回 false;只有在内部 map 扩容失败(极罕见)时才可能出错,但目前代码里根本没暴露 error。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
常见误判现象:
- 用
if !cache.Set(k, v, exp) { log.Println("set failed") }来判断失败 → 实际只是 key 已存在,log 会刷屏 - 想靠返回值判断“是否首次写入”,结果发现并发 Set 时多个 goroutine 都返回
false→ 因为谁都可能不是第一个
正确做法:需要“首次设置”语义,改用 cache.Add(key, value, exp),它只在 key 不存在时写入并返回 true;失败就真失败,不覆盖。
本地缓存该不该加 metrics,加在哪一层最有效
加,但别埋在每行 Get/Set 里。高频调用下打点本身就有开销,而且指标粒度太细反而难定位问题。应该在 cache 上层封装一层 interface,统一拦截调用,并按 key pattern 聚合(比如只统计 "user:*" 和 "config:*" 两类)。
实操建议:
- 用
prometheus.CounterVec记cache_hits_total{type="go_cache",key_pattern="user:*"},比单个 key 更有分析价值 - 命中率低(
- 别对
bigcache的Get做延迟统计——它内部是纯内存寻址,耗时稳定在纳秒级,抖动来自 GC 或 CPU 抢占,不是 cache 本身
真正容易被忽略的是:所有本地缓存都假设“进程内视图一致”,但如果你用了 fork 或 hot reload(比如 air),旧进程的 cache 不会自动失效,可能拿着过期数据跑几秒。这种时候,缓存不是性能瓶颈,而是数据一致性陷阱。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










