缓存击穿那一刻,singleflight 比互斥锁优雅在哪
【摘要】 热key过期的那一秒,几百个并发请求同时miss,全部杀向数据库——击穿的标准现场。互斥锁能解但粒度太粗:锁的是回源这个动作,所有key的回源排一条队,一个慢查询把其它key的恢复也堵住。singleflight的语义是按key合并并发调用:同一个key的一批请求只放一个进去执行,其余等待并共享结果,不同key之间完全并行。golang.org/x/sync/singleflight三十行内...
热 key 过期的那一秒,几百个并发请求同时 miss,全部杀向数据库——击穿的标准现场。互斥锁能解但粒度太粗:锁的是回源这个动作,所有 key 的回源排一条队,一个慢查询把其它 key 的恢复也堵住。
singleflight 的语义是按 key 合并并发调用:同一个 key 的一批请求只放一个进去执行,其余等待并共享结果,不同 key 之间完全并行。golang.org/x/sync/singleflight 三十行内就能接进缓存层:
var g singleflight.Group
func GetProduct(ctx context.Context, id int64) (*Product, error) {
key := fmt.Sprintf("product:%d", id)
if v, ok := localCache.Get(key); ok {
return v.(*Product), nil
}
// 同 key 并发请求在此合并,只放一个回源
v, err, _ := g.Do(key, func() (any, error) {
p, err := loadFromDB(ctx, id)
if err == nil {
localCache.Set(key, p, 10*time.Minute)
}
return p, err
})
if err != nil {
return nil, err
}
return v.(*Product), nil
}
两个进阶点:回源失败时调用 g.Forget(key),让后续请求立刻重试而不是继续等这一批的失败结果;怕回源卡死拖住整批等待者,用 DoChan 配合 select 监听 ctx。
再叠两个习惯就完整了:过期时间加随机抖动(10 分钟正负 1 分钟)防雪崩;接受单机合并的极限,多实例仍会有少量重复回源,要求更严的场景在前面加 Redis 互斥或逻辑过期。这套组合下来,缓存层的抗压逻辑基本闭环,代码量不到五十行。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)