redis缓存 -店铺查询

逐行超详细通俗解释

我把代码拆成 5 个核心模块 来讲,你一看就懂。

模块 1:先去 Redis 缓存里查店铺

java

运行

// 通过店铺ID获取店铺信息
String shop_key = CACHE_SHOP_KEY + id;
String shopJson = stringRedisTemplate.opsForValue().get(shop_key);

  • 意思:先查缓存,不直接查数据库(减轻数据库压力)
  • shop_key 是缓存的 key,比如 shop:1001

模块 2:缓存里有数据 → 直接返回

java

运行

// 如果店铺信息不为空则返回店铺信息
if (StrUtil.isNotBlank(shopJson)) {
    return JSONUtil.toBean(shopJson, Shop.class);
}

  • 缓存里有数据 → 直接转成 Shop 对象返回
  • 最快、最爽的情况

模块 3:缓存里是空字符串 → 直接返回 null(防缓存穿透)

java

运行

// 判断是否是空值,如果是空值则返回店铺信息为空,防止缓存穿透
if (shopJson != null) {
    return null;
}

这里非常关键!

  • 如果缓存里存的是 "" 空字符串
  • 说明之前查过数据库,数据库里也没有这个店铺
  • 所以直接返回 null,不让请求去查数据库
  • 作用:防止缓存穿透(黑客用不存在 id 狂刷请求打崩数据库)

模块 4:缓存没有 → 准备去数据库查,但要加锁(防缓存击穿)

java

运行

// 声明锁和店铺
String lock_key = LOCK_SHOP_KEY + id;
Shop shop = null;

try {
    // 判断获取锁是否成功
    boolean flag = tryLock(lock_key);

    // 不成功则重新休眠在获取
    if (!flag) {
        Thread.sleep(50);
        queryWithMuteX(id);
    }

这里是核心:互斥锁(MuteX)

高并发下(例如 1000 个人同时查同一个店铺):

  • 缓存失效了
  • 1000 个请求同时去查数据库 → 数据库直接崩掉 → 这叫缓存击穿

所以用锁:

  • 只有一个线程能拿到锁
  • 其他线程拿不到 → 休息 50ms → 再重试
  • 最终只有一个请求去查数据库

模块 5:拿到锁后 → 二次检查缓存(DoubleCheck)

java

运行

// DoubleCheck 获取锁成功后在此检测redis缓存是否存在,不存在则根据id查询数据库
String shopLockJson = stringRedisTemplate.opsForValue().get(shop_key);

if (shopLockJson != null) {
    return JSONUtil.toBean(shopLockJson, Shop.class);
}

为什么还要再查一次缓存?

因为:

  • 上一个线程可能已经把数据查好放进缓存了
  • 你不需要再查数据库了
  • 这就是DoubleCheck 双重检查,经典高并发写法

模块 6:查数据库 + 缓存空值 + 写缓存

java

运行

shop = getById(id);
if (shop == null) {
    // 数据库也没有 → 缓存空字符串,防缓存穿透
    stringRedisTemplate.opsForValue().set(shop_key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
    return null;
}

// 数据库有 → 写入Redis缓存
stringRedisTemplate.opsForValue().set(shop_key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);

逻辑:

  1. 查数据库
  2. 数据库没有 → 存空值 "" → 返回 null
  3. 数据库有 → 转 JSON → 存 Redis → 返回店铺

模块 7:finally 无论如何都要释放锁

java

运行

} finally {
    // 释放互斥锁
    unlock(lock_key);
}

  • finally 保证就算报错了,锁也一定会释放
  • 不释放锁会导致死锁,其他线程永远拿不到锁

用生活例子帮你彻底理解

你可以把这段代码想象成:

场景:很多人同时去图书馆查同一本书

  1. 先查登记簿(Redis 缓存)
  2. 登记簿有 → 直接告诉你
  3. 登记簿写着 “没有”(空值)→ 直接告诉你没有
  4. 登记簿没有 → 去拿钥匙(加锁)
  5. 只有一个人能拿到钥匙
  6. 其他人等一会儿再重试
  7. 拿到钥匙的人再看一眼登记簿(DoubleCheck)
  8. 确认没有 → 去书架查书(数据库)
  9. 有书 → 写回登记簿
  10. 没书 → 写 “没有”(防穿透)
  11. 最后一定把钥匙放回去(释放锁)

这套流程就是:

缓存 + 互斥锁解决缓存击穿 + 空值缓存解决缓存穿透

是企业级标准方案!

这段代码解决了哪 3 个经典缓存问题?(面试必问)

  1. 缓存穿透:查不存在的数据 → 缓存空值
  2. 缓存击穿:高并发下缓存失效 → 互斥锁
  3. 缓存压力:优先查 Redis,不查数据库

总结(最核心的 3 句话)

  1. 先查缓存,有就直接返回
  2. 缓存没有就加锁,只让一个请求查数据库
  3. 查完数据库写回缓存,空数据也写(防穿透)
全部评论

相关推荐

评论
1
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务