跳到主要内容

缓存优化

问题

Go 服务中缓存应该怎么设计?如何防止缓存击穿?

答案

缓存层次

本地缓存

// 使用 github.com/dgraph-io/ristretto
import "github.com/dgraph-io/ristretto"

cache, _ := ristretto.NewCache(&ristretto.Config{
NumCounters: 1e7, // 跟踪频率的 key 数
MaxCost: 1 << 30, // 最大内存 1GB
BufferItems: 64,
})

cache.Set("key", value, 1) // cost=1
value, found := cache.Get("key")

// 或用 sync.Map(简单场景)
// 或用 github.com/patrickmn/go-cache(带 TTL)

singleflight 防击穿

import "golang.org/x/sync/singleflight"

var g singleflight.Group

func GetUser(id string) (*User, error) {
// 同一个 key 的并发请求只执行一次
result, err, _ := g.Do("user:"+id, func() (interface{}, error) {
// 查缓存
if user, ok := cache.Get(id); ok {
return user, nil
}
// 缓存未命中,查 DB
user, err := db.FindUser(id)
if err != nil {
return nil, err
}
cache.Set(id, user, time.Minute*5)
return user, nil
})

if err != nil {
return nil, err
}
return result.(*User), nil
}
singleflight 原理

多个 goroutine 同时请求同一个 key 时,只有第一个去查 DB,其他的等待结果共享。适合防止缓存过期瞬间的并发穿透。

缓存一致性

策略流程一致性
Cache Aside写 DB → 删缓存最终一致
Write Through写缓存 → 写 DB强一致
Write Behind写缓存 → 异步 DB弱一致

常见面试问题

Q1: 缓存穿透、击穿、雪崩的区别和解决方案?

答案

问题原因解决
穿透查不存在的 key布隆过滤器、缓存空值
击穿热点 key 过期singleflight、互斥锁
雪崩大量 key 同时过期随机 TTL、多级缓存

Q2: singleflight 和 sync.Once 的区别?

答案

  • sync.Once全局只执行一次,结果永久缓存
  • singleflight同时刻只执行一次,下次请求还会执行

singleflight 适合做请求级去重,sync.Once 适合做初始化

相关链接