什么是缓存
添加Redis缓存
流程:

实现业务:请求获取商铺信息
期间各个数据结构的变化:前端传给后端的是JSON类型,在controller层将JSON转换为java对象,后端通过SpringBoot自动将java对象转换为JSON传送给前端。
完整流程图:
浏览器请求
│
▼
Controller
│
▼
Service层
│
▼
返回Java对象
│
▼
SpringBoot调用Jackson
│
▼
对象 → JSON
│
▼
HTTP Response
│
▼
浏览器收到JSON
缓存更新策略
为了实现帮助Redis节省内存,我们需要将Redis数据进行更新,如下三种方法为主流,具体根据业务需求进行选择。

数据库缓存不一致问题
问题原因:数据库数据因为更新导致了缓存数据与数据库信息不一致。
有如下三种解决方案



先操作缓存还是先操作数据库?
先操作数据库,再删除缓存
- 先删除缓存,再操作数据库(发生错误概率高)
- 先操作数据库,再删除缓存(发生错误概率低)
实现数据双写
主要完成业务就是对上述问题进行代码改进。

缓存击穿,雪崩,穿透
关于缓存击穿,雪崩,穿透,在前期已经具体进行过区分,详细可见下文。
从底层理解缓存问题:雪崩、击穿、穿透与一致性设计
业务流程更新图:

利用互斥锁解决缓存击穿问题
通过互斥锁等方法实现了缓存击穿问题的解决
业务流程图:

业务实现方式:写了两种方法
缓存穿透
Shop shop = queryWithPassThrough(id);
互斥锁解决缓存击穿
Shop shop = queryWithMutex(id);
来实现缓存存在的问题
基于逻辑过期方式解决缓存击穿问题
流程图:

实现逻辑:通过缓存逻辑过期时间将业务实现改进
java
/**
* 缓存重建线程池
*/
public static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
专为缓存重建开一个专门的线程池
java
// 1、从Redis中查询店铺数据,并判断缓存是否命中
String shopJson = stringRedisTemplate.opsForValue().get(key);
if (StrUtil.isBlank(shopJson)) {
// 1.1 缓存未命中,直接返回失败信息
return Result.fail("店铺数据不存在");
}
// 1.2 缓存命中,将JSON字符串反序列化未对象,并判断缓存数据是否逻辑过期
RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);
// 这里需要先转成JSONObject再转成反序列化,否则可能无法正确映射Shop的字段
JSONObject data = (JSONObject) redisData.getData();
Shop shop = JSONUtil.toBean(data, Shop.class);
LocalDateTime expireTime = redisData.getExpireTime();
if (expireTime.isAfter(LocalDateTime.now())) {
// 当前缓存数据未过期,直接返回
return Result.ok(shop);
}
// 2、缓存数据已过期,获取互斥锁,并且重建缓存
String lockKey = LOCK_SHOP_KEY + id;
boolean isLock = tryLock(lockKey);
if (isLock) {
// 获取锁成功,开启一个子线程去重建缓存
CACHE_REBUILD_EXECUTOR.submit(() -> {
try {
this.saveShopToCache(id, 20L);
} finally {
unlock(lockKey);
}
});
}
// 3、获取锁失败,再次查询缓存,判断缓存是否重建(这里双检是有必要的)
shopJson = stringRedisTemplate.opsForValue().get(key);
if (StrUtil.isBlank(shopJson)) {
// 3.1 缓存未命中,直接返回失败信息
return Result.fail("店铺数据不存在");
}
// 3.2 缓存命中,将JSON字符串反序列化未对象,并判断缓存数据是否逻辑过期
redisData = JSONUtil.toBean(shopJson, RedisData.class);
// 这里需要先转成JSONObject再转成反序列化,否则可能无法正确映射Shop的字段
data = (JSONObject) redisData.getData();
shop = JSONUtil.toBean(data, Shop.class);
expireTime = redisData.getExpireTime();
if (expireTime.isAfter(LocalDateTime.now())) {
// 当前缓存数据未过期,直接返回
return Result.ok(shop);
}
// 4、返回过期数据
return Result.ok(shop);
}