singleflight.group 是击穿防护首选,因它让并发请求共享首次执行结果,避免重复查询db、无锁竞争、低延迟且对调用透明;需配合空值缓存与参数校验防穿透。

为什么 singleflight.Group 是击穿防护的首选方案
缓存击穿的本质是:多个 goroutine 同时发现某个 key 缓存失效,又都去查数据库,结果 DB 瞬间被压垮。singleflight.Group 不是“加锁等结果”,而是“只让第一个执行,其余直接共享返回值”——它天然规避了锁竞争、死锁、重试逻辑和重复 DB 查询。
- 它不依赖 Redis 或任何外部组件,纯内存协作,延迟极低
- 对调用方完全透明:你传一个
func(),它保证这个函数最多执行一次,其他并发请求自动等待并拿到相同结果 - 适用于任意数据源:DB 查询、RPC 调用、HTTP 请求、本地计算……只要封装成函数就行
- 注意:
Group是无状态的,不同实例之间不共享 call 状态,所以全局复用一个singleflight.Group实例(比如定义为包变量)才有效
怎么写一个防击穿的缓存读取函数
核心不是“先查缓存再 fallback”,而是“查缓存 → 未命中 → 用 singleflight.Do 加载 → 写缓存 → 返回”,整个链路要串在一次原子操作里。
-
singleflight.Do的key必须与缓存 key 语义一致(比如"user:123"),否则无法聚合请求 - 不要在
fn里直接写缓存——应由外层统一写入,避免多个 goroutine 重复写(哪怕fn只执行一次,写缓存也该是确定性动作) - 如果
fn报错,singleflight会把 error 一并返回给所有等待者,这点必须处理,不能静默忽略 - 示例片段:
var sg singleflight.Group
func GetUser(ctx context.Context, userID string) (*User, error) {
cacheKey := "user:" + userID
// 1. 先查缓存
if data, err := redis.Get(cacheKey); err == nil && data != nil {
return decodeUser(data), nil
}
// 2. 缓存未命中,用 singleflight 加载
v, err := sg.Do(cacheKey, func() (interface{}, error) {
u, err := db.QueryUser(userID) // 真实 DB 查询
if err != nil {
return nil, err
}
// 3. 成功后写缓存(注意:只在这里写一次)
_ = redis.Set(cacheKey, encodeUser(u), time.Hour)
return u, nil
})
if err != nil {
return nil, err
}
return v.(*User), nil
}
别踩坑:空值缓存 + singleflight 必须配合使用
击穿和穿透经常一起发生——比如黑客用大量非法 userID(如负数、超长字符串)刷接口,既没缓存也没 DB 记录,singleflight 会为每个非法 key 都起一个“加载任务”,照样压 DB。
- 必须前置校验或布隆过滤(如
userID格式校验、长度限制),拦截明显非法请求 - 对确认 DB 中不存在的数据,也要走缓存空值流程:
redis.Set("user:-999", "nil", time.Minute) -
singleflight.Do的key应该是原始请求 key(如"user:-999"),这样后续同 key 请求才能被聚合,而不是放行到 DB 层再报错 - 空值缓存的过期时间要短(几十秒到几分钟),避免长期占满缓存空间,也防止合法数据上线后被空值挡住
什么时候不该用 singleflight?
它不是银弹。在以下场景,强行套用反而引入风险或冗余:
立即学习“go语言免费学习笔记(深入)”;
- 请求 key 粒度太细且不可预测(比如带毫秒级时间戳、随机 UUID 的 key),会导致
Group.mmap 持续膨胀,GC 压力大,且几乎无法复用 - 业务要求严格区分“查不到”和“查失败”——因为
singleflight会把 error 广播给所有人,而你可能想让部分请求重试、部分降级 - DB 查询本身极快(singleflight 反而增加调度开销,得不偿失
- 你已经在用分布式锁(如 Redis SETNX)做击穿防护,且锁粒度合理、超时设置得当,那
singleflight就成了多余的一层本地协调
真正关键的点是:击穿防护从来不是单点技术问题,而是缓存策略、key 设计、空值治理、监控告警几件事咬合在一起的结果。漏掉任何一个环节,singleflight 再好使,也扛不住那一波“刚好全过期”的请求洪峰。










