1. 缓存穿透
客户端请求的数据在缓存和数据库中都不存在,导致缓存永远不会生效,请求都被打到数据库。
2. 解决方案
2.1 主动方案:
主动方案是值主动防止缓存穿透的产生,具有以下方案:
- 增加 id 复杂度,避免被猜测到 id 规律
- 做好数据基础格式校验
- 加强用户权限校验
- 做好热点参数的限流
2.2 被动方案
被动方案是指缓存穿透已经发生了,我们如何处理。
1. 方法一:缓存空对象,并为其设置一个 TTL
- 优点:简单、容易维护
- 缺点:额外内存消耗,可能造成短期的不一致
不一致的产生场景:用户查询的时候,碰巧真的给这个地方插入了一条数据,而用户读出的是缓存中的 null 。
2. 方法二: 布隆过滤
在客户端 和 Redis 中再加一层布隆过滤器,把数据库中的数据转换为 hash 值,存在过滤器中,但是其不一定是百分之百准确的,有一定的穿透风险 (过滤器说不存在,一定不存在,但是过滤器说存在,不一定存在)
- 优点:内存占用较少,没有多余的 key
- 缺点:实现复杂、还有误判的可能性
3. 缓存空对象解决缓存穿透具体实现
具体实现时,我们需要在本来缓存数据的基础上,加上如下步骤:
- 当数据库查询为 null 时,把空数据写到缓存中
- 当缓存查到为 “” 时,返回数据,而不是去查询数据库
代码示例:
@Override
public Result queryById(Long id) {
String key = CACHE_SHOP_KEY + id;
// 1. 从 Redis 中查询商户缓存
String shonJson = stringRedisTemplate.opsForValue().get(key); // 2. 判断是否存在
// 2. 命中真实值,返回数据
if(!StringUtil.isNullOrEmpty(shonJson)){
Shop shop = JSONUtil.toBean(shonJson, Shop.class);
//System.out.println("**************** 我是从缓存中拿的 *****************" );
return Result.ok(shop);
}
// 3. 命中空值(空字符串),返回错误,而不是去查询数据库
if(shonJson != null){
return Result.fail("缓存店铺不存在");
}
// 4. 查询数据库
Shop shop = getById(id);
// 5. 数据库不存在 —— 将空值写入缓存,返回错误
if(shop == null){
// 将空字符串写入 Redis
stringRedisTemplate.opsForValue().set(key, "", 2L, TimeUnit.MINUTES);
return Result.fail("数据库店铺不存在");
}
// 6. 数据库存在 —— 写入 redis ,然后返回
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), 30L, TimeUnit.MINUTES);
return Result.ok(shop);
}