Redis学习

Redis 最常见的模式 Cache Aside:

目前写go应用的用法如下:
在 service/product.go 实现了 Cache Aside 模式:
GET /api/products/:id
├─ Redis GET product:xxx → 命中 → 直接返回
└─ 未命中 → MySQL 查询 → Redis SET(30分钟过期) → 返回

PUT /api/products/:id → 更新 DB → Redis DEL product:xxx
DELETE /api/products/:id → 删除 DB → Redis DEL product:xxx

核心代码就几行
// 查缓存
val, err := common.RedisClient.Get(common.Ctx, cacheKey).Result()
if err == nil {
// 命中,反序列化返回
json.Unmarshal([]byte(val), &product)
}

(此处省略查DB的过程,就是查出product对象)

// 未命中,查 DB 后写入缓存
data, _ := json.Marshal(product)
common.RedisClient.Set(common.Ctx, cacheKey, data, 30*time.Minute)
知识点

  • String 结构:用 JSON 序列化存复杂对象
  • 过期时间:30 分钟自动淘汰,防止脏数据
  • 缓存失效:写操作后主动删缓存,下次读时重新加载

123

(图片来源于互联网 侵删)

2.Redis命名约定 object-type🆔field

Redis 官方文档推荐的惯例,来源在这:
https://redis.io/docs/manual/keyspace/ (https://redis.io/docs/manual/keyspace/)
里面有句话:
Try to stick with a schema. For instance, "object-type🆔field" is a good idea.
官方推荐的格式就是 object-type🆔field,也就是 实体类型:ID:字段。

例如:
user:123:visits 用户 123 的访问次数
product:456:views 商品 456 的浏览量
product:456 商品 456 的详情(我们项目里)

3.缓存策略深入

先讲问题,再改代码。

1: 缓存穿透问题 + 空值缓存方案
2: 缓存击穿问题 + 本地锁保护
3: 缓存雪崩问题 + 随机 TTL

Q:我不知道为什么会导致这三个问题 你做了什么改动 为什么这么改 我看不懂呀

A:对不起,我跳太快了。我们把代码展开一行行对比讲。
改动前 GetProduct(Service层的一个函数)
func GetProduct(id string) (*model.Product, error) {
cacheKey := "product:" + id

// ① 查 Redis
val, err := common.RedisClient.Get(common.Ctx, cacheKey).Result()
if err == nil {
var product model.Product
if json.Unmarshal([]byte(val), &product) == nil {
return &product, nil // 有缓存 → 直接返回
}
}

// ② 没缓存 → 查 MySQL
product, err := dao.GetProduct(id)
if err != nil {
return nil, err // MySQL 也没有 → 返回错误
}

// ③ 查到后写入 Redis
data, _ := json.Marshal(product)
common.RedisClient.Set(common.Ctx, cacheKey, data, 30time.Minute)
return product, nil
}
这个代码有 3 个问题,逐个说:
问题 1:缓存穿透(查的商品根本不存在)
场景: 请求 GET /api/products/nonexist-1,Redis 没有 → 查 MySQL → 没有 → 返回错误
下次再来查 nonexist-1,Redis 仍然没有 → 又查 MySQL。恶意攻击的话,每秒请求 1000 个不存在的商品 ID,MySQL 就崩了。
我的改动:
product, err := dao.GetProduct(id)
if err != nil {
// ★ 不存在也缓存,存个 "null",60 秒过期
common.RedisClient.Set(common.Ctx, cacheKey, "null", 1
time.Minute)
return nil, errors.New("商品不存在")
}
效果: 第一次查完发现不存在 → Redis 记下"这个商品不存在,别查 MySQL 了" → 后续请求看到 "null" 直接返回,不打 MySQL。
问题 2:缓存击穿(热点商品缓存刚好过期)
场景: 一个爆款商品,同时 1000 个人访问。Redis 刚好过期 → 1000 个请求同时打到 MySQL。
我的改动——加了双检锁:
lock := getProductLock(id) // ① 每个商品一个锁
lock.Lock()
defer lock.Unlock()

// ② 拿到锁后再查一次 Redis(别人可能已经写进去了)
val, err = common.RedisClient.Get(common.Ctx, cacheKey).Result()
if err == nil {
// 有缓存了 → 直接返回,不用查 MySQL
...
}
效果: 1000 个请求同时来,只有 1 个 能拿到锁去查 MySQL,其他 999 个等锁释放后,从 Redis 拿数据。
问题 3:缓存雪崩(大量 key 同时过期)
场景: 你存了 1 万个商品,全是 30time.Minute 到期。30 分钟后全过期 → 1 万个请求同时打 MySQL。
我的改动:
// 原来是固定 30 分钟
common.RedisClient.Set(common.Ctx, cacheKey, data, 30
time.Minute)

// 改成 20~40 分钟随机
ttl := 20time.Minute + time.Duration(rand.Intn(20))time.Minute
效果: A 商品 22 分钟过期,B 商品 35 分钟过期,C 商品 27 分钟过期……不会扎堆。

===================================================
一行一行看最终代码
func GetProduct(id string) (*model.Product, error) {
cacheKey := "product:" + id

// ——— 第 1 步:查 Redis ———
val, err := common.RedisClient.Get(common.Ctx, cacheKey).Result()
if err == nil {
// ★ 缓存的是 "null" → 直接返回"商品不存在"
if val == "null" {
return nil, errors.New("商品不存在")
}
var product model.Product
if json.Unmarshal([]byte(val), &product) == nil {
return &product, nil
}
}

// ——— 第 2 步:拿本地锁(防击穿) ———
lock := getProductLock(id)
lock.Lock()
defer lock.Unlock()

// ★ 双检:拿到锁后再查一次 Redis,避免重复查 DB
val, err = common.RedisClient.Get(common.Ctx, cacheKey).Result()
if err == nil {
if val == "null" {
return nil, errors.New("商品不存在")
}
var product model.Product
if json.Unmarshal([]byte(val), &product) == nil {
return &product, nil
}
}

// ——— 第 3 步:查 MySQL ———
product, err := dao.GetProduct(id)
if err != nil {
// ★ 防穿透:不存在也缓存空值
common.RedisClient.Set(common.Ctx, cacheKey, "null", 1*time.Minute)
return nil, errors.New("商品不存在")
}

// ——— 第 4 步:写入 Redis(随机 TTL 防雪崩) ———
ttl := 20time.Minute + time.Duration(rand.Intn(20))time.Minute
data, _ := json.Marshal(product)
common.RedisClient.Set(common.Ctx, cacheKey, data, ttl)

return product, nil
}

posted @ 2026-07-05 21:34  lostkk  阅读(2)  评论(0)    收藏  举报