Golang 从入门到放弃 -0x17
Redis 与缓存基础:缓存解决什么问题、常见策略和几个高频坑。
后端服务只要一开始上量,很快就会碰到一个灵魂问题:为什么明明数据库没那么多数据,接口还是越来越慢?这时候缓存就会跑出来。很多团队第一反应是“上 Redis”,这方向没错,但如果只记住“上 Redis 就会快”,后面大概率还会再踩一轮坑。
缓存到底解决什么问题
缓存的本质,不是让代码看起来更高级,而是减少对慢资源的重复访问。数据库、远程接口、复杂计算,这些都可能是慢点。
一个很朴素的例子:
- 热门文章详情被频繁读取
- 每次都查数据库,数据库压力越来越大
- 把结果放进缓存,重复请求直接取缓存
Redis 为什么常见
因为它快、简单、生态成熟,而且除了当缓存,还能做计数器、分布式锁、排行榜、消息队列之类的事。不过我们这一章先克制一点,只聊它最朴素也最常见的用法:缓存。
最简单的缓存读写
ctx := context.Background()
err := rdb.Set(ctx, "user:1", `{"id":1,"name":"Raymond"}`, 5*time.Minute).Err()
if err != nil {
return err
}
value, err := rdb.Get(ctx, "user:1").Result()
if err != nil {
return err
}
fmt.Println(value)这里最关键的不是 API,而是那个过期时间。很多缓存事故的源头,不是没缓存,而是缓存设了和没设一样。
缓存穿透、击穿、雪崩
这几个词听起来像面试黑话,但其实对应的都是真问题。
- 穿透:查一个根本不存在的数据,每次都打到数据库
- 击穿:某个超热点 key 正好过期,大量请求瞬间打爆后端
- 雪崩:一批 key 同时过期,后端集体遭殃
别急着背名词,先知道大意就够了。缓存不是加上就万事大吉,它也有自己的脾气。
最常用的缓存策略
业务里常见的是 Cache Aside,也就是“读时回填,写时更新或删缓存”。
func GetUser(ctx context.Context, id int) (*User, error) {
key := fmt.Sprintf("user:%d", id)
if value, err := rdb.Get(ctx, key).Result(); err == nil {
var user User
if json.Unmarshal([]byte(value), &user) == nil {
return &user, nil
}
}
user, err := repo.FindByID(ctx, id)
if err != nil {
return nil, err
}
data, _ := json.Marshal(user)
_ = rdb.Set(ctx, key, data, 5*time.Minute).Err()
return user, nil
}更新数据时,很多场景下最简单靠谱的做法其实是“更新数据库后删缓存”,而不是一上来就追求双写一致性的完美幻觉。
缓存不是越多越好
有些数据天生就不适合缓存,比如变化频率极高、强一致性要求特别高、访问量其实并不高的数据。别把缓存当银弹,缓存是拿来省成本和提性能的,不是拿来制造复杂度的。
小结
这一章先把缓存的基本感觉建立起来:
- 缓存是为了解决慢资源的重复访问。
- Redis 常见,但别只会喊口号。
- 过期时间、热点 key、一致性都是实际问题。
- 很多场景下,“更新库后删缓存”已经够实用。
下一章我们说登录鉴权和 JWT。前面接口能跑了,接下来要开始决定“谁能调,谁不能调”。