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);
逻辑:
- 查数据库
- 数据库没有 → 存空值
""→ 返回 null - 数据库有 → 转 JSON → 存 Redis → 返回店铺
模块 7:finally 无论如何都要释放锁
java
运行
} finally {
// 释放互斥锁
unlock(lock_key);
}
finally保证就算报错了,锁也一定会释放- 不释放锁会导致死锁,其他线程永远拿不到锁
用生活例子帮你彻底理解
你可以把这段代码想象成:
场景:很多人同时去图书馆查同一本书
- 先查登记簿(Redis 缓存)
- 登记簿有 → 直接告诉你
- 登记簿写着 “没有”(空值)→ 直接告诉你没有
- 登记簿没有 → 去拿钥匙(加锁)
- 只有一个人能拿到钥匙
- 其他人等一会儿再重试
- 拿到钥匙的人再看一眼登记簿(DoubleCheck)
- 确认没有 → 去书架查书(数据库)
- 有书 → 写回登记簿
- 没书 → 写 “没有”(防穿透)
- 最后一定把钥匙放回去(释放锁)
这套流程就是:
缓存 + 互斥锁解决缓存击穿 + 空值缓存解决缓存穿透
是企业级标准方案!
这段代码解决了哪 3 个经典缓存问题?(面试必问)
- 缓存穿透:查不存在的数据 → 缓存空值
- 缓存击穿:高并发下缓存失效 → 互斥锁
- 缓存压力:优先查 Redis,不查数据库
总结(最核心的 3 句话)
- 先查缓存,有就直接返回
- 缓存没有就加锁,只让一个请求查数据库
- 查完数据库写回缓存,空数据也写(防穿透)