缓存穿透的常见解决方案有两种:
一、缓存空值
当客户端发送的请求在Reids缓存中和数据库中都查询失败,我们设置一个空值缓存起来,这样即使用户访问了不存在的数据,也不会让请求直接穿透的数据库上。
注意:缓存空值的过期时间不宜太长,假设我们之后真的添加了新数据,也不会造成过久的数据不一致问题。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
缓存空值方案的优点与缺点:
缺点:
优点:
- 实现简单,我们只需要在数据库查询失败后添加个空值缓存即可
实现思路:
1、从Redis中查询信息
2、击中缓存了直接返回即可
3、击中空值直接返回错误信息
4、缓存没击中,查询数据库
5、数据库查询失败,说明当前所查询信息不存在,缓存一个空值到Redis(同时设置较短的过期时间)
6、数据库查询成功,添加缓存并返回
核心代码如下:
?完整实现(带注释):
//需求,通过ID查询店铺信息,并添加到缓存。需要保证数据一致性
@Override
public Result queryById(Long id) {
//定义个cacheID作为key,后续方便使用
String cacheId = CACHE_SHOP_KEY + id;
//1、从Redis中查询商户信息
String shopJson = stringRedisTemplate.opsForValue().get(cacheId);
//2、击中了,转成对象直接返回
if(StrUtil.isNotBlank(shopJson)){
return Result.ok(JSONUtil.toBean(shopJson,Shop.class));
}
//3、击中了空值,直接返回
//这里不能直接用shopJson.equals("")做为条件,因为第一次查询没有缓存任何数据,shopJson肯定是个null,null.equals()直接报错了
//而上面的isNotBlank方法排除后只剩下null和""的两种情况,我们只需要再排除null的情况,剩下的就是""了。
if(shopJson != null){
return Result.fail(SHOP_ERROR);
}
//4、缓存没击中,去数据库查
Shop shop = getById(id);
//5、数据库里也没查到,返回空对象到Redis(防止缓存穿透)
if(shop == null){
stringRedisTemplate.opsForValue().set(cacheId,"");
stringRedisTemplate.expire(cacheId,CACHE_NULL_TTL,TimeUnit.MINUTES);
return Result.fail(SHOP_ERROR);
}
//6、添加数据到redis缓存
stringRedisTemplate.opsForValue().set(cacheId,JSONUtil.toJsonStr(shop));
//7、设置过期时间,30分钟
stringRedisTemplate.expire(cacheId,CACHE_SHOP_TTL, TimeUnit.MINUTES);
//8、返回数据
return Result.ok(shop);
}
|