func GetProduct(ctx context.Context, id int64) (*Product, error) {
key := fmt.Sprintf("cache:product:%d", id)
if p, ok := readCache(ctx, key); ok { return p, nil }
// 同一进程可用 singleflight;跨实例可用短租约锁或逻辑过期。
value, err, _ := group.Do(key, func() (any, error) {
if p, ok := readCache(ctx, key); ok { return p, nil } // 二次检查
p, err := loadDatabase(ctx, id)
if errors.Is(err, sql.ErrNoRows) {
writeNotFound(ctx, key, 30*time.Second)
return nil, ErrNotFound
}
if err != nil { return nil, err }
ttl := 10*time.Minute + randomJitter()
writeCache(ctx, key, p, ttl)
return p, nil
})
if err != nil { return nil, err }
return value.(*Product), nil
}
代码边界:进程内 singleflight 只能合并同一实例的并发;多实例要结合 Redis 锁、数据库层控制或允许返回旧值。所有等待都必须服从请求 Context 的超时。