缓存击穿那一刻,singleflight 比互斥锁优雅在哪

举报
yd_275379215 发表于 2026/10/18 10:10:05 2026/10/18
【摘要】 热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)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。