缓存设计
缓存策略
Cache Aside(旁路缓存)
最常用的模式,应用程序同时维护缓存和数据库。
go
// 读操作
func Get(key string) string {
// 1. 查缓存
val := rdb.Get(ctx, key).Val()
if val != "" {
return val
}
// 2. 查数据库
val = db.Query(key)
// 3. 写缓存
rdb.Set(ctx, key, val, 1*time.Hour)
return val
}
// 写操作
func Set(key, value string) {
// 1. 更新数据库
db.Update(key, value)
// 2. 删除缓存
rdb.Del(ctx, key)
}Write Through(写穿透)
应用程序只写缓存,由缓存负责写数据库。
Write Behind(写回)
应用程序只写缓存,异步批量写入数据库。
缓存问题
缓存穿透
查询不存在的数据,缓存和数据库都无数据。
解决方案:
- 缓存空值
go
val := rdb.Get(ctx, key).Val()
if val == "NULL" {
return "" // 缓存的空值
}- 布隆过滤器
go
// 使用布隆过滤器预先判断
if !bloomFilter.Test(key) {
return "" // key 不存在
}缓存击穿
热点 key 过期,大量请求直达数据库。
解决方案:
- 热点数据永不过期
- 互斥锁
go
if rdb.SetNX(ctx, "lock:"+key, 1, 10*time.Second).Val() {
defer rdb.Del(ctx, "lock:"+key)
// 查数据库并更新缓存
}缓存雪崩
大量 key 同时过期。
解决方案:
- 随机过期时间
go
expire := 3600 + rand.Intn(600) // 1小时 ± 10分钟
rdb.Set(ctx, key, value, time.Duration(expire)*time.Second)- 高可用架构:哨兵/集群
最佳实践
- 键命名:
业务:模块:id - 过期时间:随机化避免雪崩
- 序列化:JSON/Protobuf
- 监控:命中率、延迟、内存
在下一章中,我们将学习分布式锁。