go-cache 的 add 和 set 语义不同:set 覆盖写入并返回 false 表示 key 已存在,add 仅首次写入成功;singleflight.group 需配合缓存预检防击穿;bigcache 分片数需按 qps 和 value 大小调优;本地缓存必须暴露 hits/misses/size 指标。

go-cache 的 Add 和 Set 别混用,热点 key 会因返回值误判失效
Set 返回 false 不代表写入失败,只表示 key 已存在;而 Add 才是真正“首次写入才成功”的语义。线上曾有服务用 if !cache.Set(k, v, exp) { log.Fatal("cache write failed") },结果日志每秒刷屏——其实只是多个 goroutine 同时抢写同一个热点 key,谁都不是第一个。
- 需要幂等更新(覆盖旧值):用
Set,忽略返回值 - 需要原子性首次设置(防重复初始化):必须用
Add,它在 key 不存在时才写入并返回true - 并发场景下,
Add内部用 CAS + 分段锁,比自己加sync.Once更轻量、更可靠
singleflight.Group 不是万能胶,得配 Get 前的缓存预检
只套一层 singleflight.Group.Do 拦不住缓存击穿:如果本地缓存里 key 已过期或根本不存在,每次 Get 都会触发 Do,等于把“打爆 DB”换成“打爆 Do 的内部 channel”。真实压测中,QPS 上 5k 后,Do 自身的锁争抢和 channel 阻塞反而成了瓶颈。
- 正确姿势:先调
cache.Get(key),命中就直接返回;未命中且确认需加载,再进singleflight.Group.Do - 别让
Do包裹整个业务逻辑,只包最重的那一步(比如 DB 查询或 HTTP 调用) - 给
Do的 fn 加超时控制,避免一个慢请求拖住整组等待协程
bigcache 分片数 shards 设错,比不用缓存还慢
bigcache 默认 shards=256,但这是针对 value 小、key 多、QPS 稳定的场景。我们线上有个服务缓存用户 session(value ~2KB),初始按默认值跑,GC 周期从 30s 缩短到 8s,P99 延迟翻倍——查 pprof 发现 70% 时间花在 shard.lock 上。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- QPS shards=64 足够,内存开销小
- QPS 1k–5k:建议
shards=128–256,平衡锁粒度与内存占用 - QPS > 5k 或 value > 1KB:必须 ≥
512,否则单 shard 锁竞争会吃掉所有吞吐 - 别信“越多越好”——shards 超过 CPU 核数太多,调度开销反升
本地缓存没监控,等于埋了个定时炸弹
缓存没指标,出问题时你连“是没命中的问题,还是根本没生效”的判断依据都没有。某次发布后接口 P95 突增 300ms,排查两小时才发现是新版本漏掉了 cache.New 初始化,所有请求全走回源——而监控面板上缓存命中率字段一直显示 “N/A”,因为没人给它埋点。
- 至少暴露三个基础指标:
cache_hits、cache_misses、cache_size(当前 key 数) - 不要依赖库自带 metrics:go-cache 的 stats 是非线程安全的,bigcache 根本没暴露 size 接口,得自己 wrap
- 用
expvar或prometheus.CounterVec注册,确保每次Get/Set都原子计数
缓存逻辑越“透明”,越容易被当成可删代码;越“黑盒”,越可能在半夜三点把你叫醒。别省那几行埋点代码。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










