singleflight通过请求合并缓解缓存击穿:同一key的并发请求共享一次后端调用,其余等待结果;但它不替代缓存,需配合显式缓存写入与稳定key设计。

为什么 singleflight 能缓解缓存击穿
缓存击穿本质是:某个 key 过期瞬间,大量并发请求同时发现缓存 miss,全部穿透到后端(DB 或 RPC),造成瞬时压力激增。singleflight 不是缓存,而是“请求合并”机制——它让同一时刻对相同 key 的所有 goroutine 共享一次真实后端调用,其余等待其结果,天然避免重复查询。
注意:singleflight 本身不存储数据、不处理过期、不自动回填缓存。它必须和你自己的缓存逻辑(如 map、redis)配合使用,否则每次请求仍会触发一次后端调用。
典型误用:把 singleflight.Do 当成缓存用
常见错误是只调用 singleflight.Do("user:123", fetchFromDB),但没在 fetchFromDB 内部写入缓存。结果下次请求依然 cache miss,又进 Do,形成“单次防重但不防下次”的假象。
-
singleflight.Do只保证「本次 key 的并发请求只执行一次函数」,不保证「下次请求能命中缓存」 - 必须在
fetchFromDB成功后,显式写入本地缓存(如sync.Map)或远程缓存(如redis.Set) - 如果后端调用失败(如 DB timeout),
singleflight默认会把 error 也返回给所有等待者——这可能导致缓存雪崩,需加兜底逻辑(如返回旧值或空结构体)
正确组合:先查缓存 → miss 则 singleflight.Do → 成功后双写
标准流程是三层检查:本地内存缓存 → singleflight 合并 → 后端加载 + 回填两级缓存。关键点在于「写入时机」和「key 构造一致性」。
每小时自动在推特搜索中国、加密货币、国际热点、美国热点、特朗普等话题的热度信息,提取前十热点,依据 X 平台真实算法权重(开源代码数据)为每个热点生成高曝光推文或线程。用户提及“推特热点”“Twitter热点”“推特搜索”“热点推文”“生成推文”“社交媒体监控”“Twitter监控”等关键词时触发。
示例片段:
func GetUser(ctx context.Context, id string) (*User, error) {
// 1. 先查本地缓存(比如 sync.Map)
if u, ok := localCache.Load("user:" + id); ok {
return u.(*User), nil
}
// 2. 使用 singleflight,key 必须和缓存 key 一致(否则无法复用)
v, err, _ := sg.Do("user:"+id, func() (interface{}, error) {
// 3. 真实加载:这里应包含 redis.Get + fallback 到 DB
u, err := loadFromDB(id)
if err != nil {
return nil, err
}
// 4. 关键:成功后立即写入本地 & 远程缓存
localCache.Store("user:"+id, u)
redisClient.Set(ctx, "user:"+id, u, time.Minute*10)
return u, nil
})
if err != nil {
return nil, err
}
return v.(*User), nil
}
- 本地缓存(
sync.Map)用于快速响应,降低singleflight进入概率 -
sg.Do的 key 必须和缓存 key 完全一致,否则多次请求会生成多个 group,失去合并效果 - 不要在
Do外层再套一层缓存检查(如先 redis.Get 再 Do)——redis 网络延迟会导致多个请求几乎同时 miss,仍可能触发多组 Do
singleflight 的隐蔽陷阱:key 泄漏与 group 持久化
singleflight.Group 内部用 map 存储 active key → group 映射,但不会自动清理已完成的 key。如果 key 是动态生成(如带时间戳、随机数),会导致内存持续增长。
- 永远避免用不可控字符串做 key:比如
"user:"+id+":"+time.Now().String() - key 应该是业务语义唯一且稳定的标识,如
"user:"+id、"post:hot:list" - 如果必须支持带参数的热点查询(如分页),提取公共维度作为 key:
"post:hot:page_size=20",而非拼入 offset - 没有内置 TTL,长期运行服务需定期调用
Forget清理已知冷 key(但别滥用——频繁 Forget 会削弱合并效果)
真正难的不是调用 Do,而是设计出稳定、可复用、不泄漏的 key,并确保缓存写入时机严丝合缝。漏掉任意一环,singleflight 就只是个昂贵的 mutex。










