(1)、什么是缓存
缓存就是数据交换的缓冲区,称作Cache,是存储数据的临时地方,一般读写性能高
(2)、添加Redis缓存
service层代码
@Override
public Result getShopById(Long id) {
String key = RedisConstants.CACHE_SHOP_KEY + id;
// TODO 1. 在缓存中查找数据
String shopJson = stringRedisTemplate.opsForValue().get(key);
// TODO 2. 缓存中存在,直接将数据返回
if (!StrUtil.isBlank(shopJson)) {
Shop shop = JSONUtil.toBean(shopJson, Shop.class);
return Result.ok(shop);
}
// TODO 3. 缓存中不存在,在数据库中进行查找
Shop shop = shopMapper.selectById(id);
// TODO 4. 数据库中不存在,返回【商铺信息不存在】
if (shop == null) return Result.fail("商铺信息不存在");
// TODO 5. 数据库中存在,将数据写入到缓存中并且将数据返回给前端
String shopJsonFromDataBase = JSONUtil.toJsonStr(shop);
stringRedisTemplate.opsForValue().set(key,shopJsonFromDataBase);
return Result.ok(shop);
}
(3)、缓存更新策略
主动更新策略
案列: 给查询商铺的缓存添加超时剔除和主动更新策略
(4)、缓存穿透
是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库
缓存穿透解决方案
1. 缓存空对象
优点: 实现简单,维护方便
缺点: 额外内存消耗(可以设置TTL)
可能造成短期的不一致
2.布隆过滤
是一个算法
优点: 内存占用较少,没有多余key
缺点:实现复杂,存在误判可能
--------------------------------------------------------
在数据库中查找不存在的时候,将商铺信息写成 "" 写入redis中
并且在redis查找的时候,先判断是否为空,不为空,判断是否为空字符串
public Result getShopById(Long id) {
String key = RedisConstants.CACHE_SHOP_KEY + id;
// TODO 1. 在缓存中查找数据
String shopJson = stringRedisTemplate.opsForValue().get(key);
// TODO 2. 缓存中存在,直接将数据返回
if (!StrUtil.isBlank(shopJson)) {
Shop shop = JSONUtil.toBean(shopJson, Shop.class);
return Result.ok(shop);
}
// 现在不在数据库中进行查找,而是继续判断查找出来的信息是否为空字符串
if (shopJson != null){
return Result.fail("店铺信息不存在");
}
/**
* 这个逻辑目前是这样的:
* 存入缓存的只有: 1.{"key":value} 2.""
* 由于已经在第一个判断语句中判断了是否为空字符串
*/
// TODO 3. 缓存中不存在,在数据库中进行查找
Shop shop = shopMapper.selectById(id);
//现在,将数据库中查出来的数据,如果为空的话,那么将【""】存入缓存中,过期时间为2分钟
stringRedisTemplate.opsForValue().set(key,"",RedisConstants.CACHE_NULL_TTL);
// TODO 4. 数据库中不存在,返回【商铺信息不存在】
if (shop == null) {
//现在,将数据库中查出来的数据,如果为空的话,那么将【""】存入缓存中,过期时间为2分钟
stringRedisTemplate.opsForValue().set(key,"",RedisConstants.CACHE_NULL_TTL);
return Result.fail("商铺信息不存在");
}
// TODO 5. 数据库中存在,将数据写入到缓存中并且将数据返回给前端
String shopJsonFromDataBase = JSONUtil.toJsonStr(shop);
stringRedisTemplate.opsForValue().set(key,shopJsonFromDataBase, RedisConstants.CACHE_SHOP_TTL);
return Result.ok(shop);
}
(5)、缓存雪崩
是指在同一时间段大量的缓存key同时失效或者redis服务器宕机,导致大量请求到达数据库,带来巨大压力
解决方案:
(6)缓存击穿
缓存击穿问题也叫热点key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效,无数的请求访问会在瞬间给数据库带来巨大冲击
解决方案:
案例:
(1)基于互斥锁方式解决缓存击穿问题
代码
public Result queryThroughMultex(Long id) {
String key = RedisConstants.CACHE_SHOP_KEY + id;
// TODO 1. 在缓存中查找数据
String shopJson = stringRedisTemplate.opsForValue().get(key);
// TODO 2. 缓存中存在,直接将数据返回
if (!StrUtil.isBlank(shopJson)) {
Shop shop = JSONUtil.toBean(shopJson, Shop.class);
return null;
}
// 现在不在数据库中进行查找,而是继续判断查找出来的信息是否为空字符串
if (shopJson != null){
return null;
}
/**
* 以下进行缓存重建
*/
// 1) 获取锁
// 2) 判断是否获取成功
// 3) 成功 去数据库中进行查找
// 失败 【休眠并且进行重试】 <----important
Shop shop = null;
String lockKey = "lock:shop:" + id;
try {
if (!getLock(lockKey)) { // 获取锁失败
Thread.sleep(50);
return queryThroughMultex(id);
}
// ======================================================================================
// TODO 3. 缓存中不存在,在数据库中进行查找
shop = shopMapper.selectById(id);
//现在,将数据库中查出来的数据,如果为空的话,那么将【""】存入缓存中,过期时间为2分钟
stringRedisTemplate.opsForValue().set(key,"",RedisConstants.CACHE_NULL_TTL);
// TODO 4. 数据库中不存在,返回【商铺信息不存在】
if (shop == null) {
//现在,将数据库中查出来的数据,如果为空的话,那么将【""】存入缓存中,过期时间为2分钟
stringRedisTemplate.opsForValue().set(key,"",RedisConstants.CACHE_NULL_TTL);
return Result.fail("商铺信息不存在");
}
// TODO 5. 数据库中存在,将数据写入到缓存中并且将数据返回给前端
String shopJsonFromDataBase = JSONUtil.toJsonStr(shop);
stringRedisTemplate.opsForValue().set(key,shopJsonFromDataBase, RedisConstants.CACHE_SHOP_TTL);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
// 释放锁
deleteLock(lockKey);
}
return Result.ok(shop);
}
(2) 、基于逻辑过期方式解决缓存击穿问题
需求: 修改根据id查询商铺的业务,基于逻辑过期方式来解决缓存击穿问题
这里的线程使用线程池
(7)、缓存工具封装