一般缓存使用流程
一般的缓存使用流程:
- 从缓存中查询数据
- 判断缓存中数据是否存在
- 存在则直接返回数据
- 数据不存在,查询数据库
- 判断数据库中数据是否存在
- 存在更新缓存,返回结果
- 不存在直接返回null
private static String prefix = "product_cache_product";
@Override
public SkuDetailEntity findEntityBySkuCodeWithCache(String skuCode) {
if (StringUtils.isBlank(skuCode)) {
return null;
}
//查询缓存
SkuDetailEntity skuEntity = cache.query(Key.valueOf(prefix, skuCode));
//缓存未命中
if (Objects.isNull(skuEntity)) {
//查询数据库
skuEntity = skuInfoMapper.selectDetailByCode(skuCode);
//数据库命中
if(Objects.nonNull(skuEntity)){
//将结果更新至缓存 缓存时间为300秒
cache.save(Key.valueOf(prefix, skuCode),skuEntity,300);
}
}
return skuEntity;
}
缓存穿透
缓存穿透:是指请求的数据即没有命中缓存也没有命中DB中(如:黑客伪造skuCode发送大量的请求),此时请求会直接作用的DB上,流量大时可造成DB宕机。即有缓存屏障但是被恶意绕过,穿透:从缝隙、漏洞中穿过,此处漏洞即为请求的数据更本不存在。
缓存穿透发生步步骤:1、2、4、5、7
解决方法:
1.对请求来的参数进行合法校验,如:有规则的skuCode可以对skuCode进行校验
2.对null也进行缓存
如:
private static String prefix = "product_cache_product";
@Override
public SkuDetailEntity findEntityBySkuCodeWithCache(String skuCode) {
if (StringUtils.isBlank(skuCode)) {
return null;
}
//查询缓存
Optional<SkuDetailEntity> optional = cache.query(Key.valueOf(prefix, skuCode));
//缓存未命中
if (Objects.isNull(optional)) {
//查询数据库
SkuDetailEntity skuEntity = skuInfoMapper.selectDetailByCode(skuCode);
optional = Optional.fromNullable(skuEntity);
//缓存5分钟
cache.save(Key.valueOf(prefix, skuCode),optional,300);
}
return optional.orNull();
}
缓存击穿
缓存击穿:是指请求的数据没有命中缓存但是命中数据库,当大量的请求在极短的时间段内一起请求某条数据的缓存并都未命中(如:缓存失效时还存在大量的请求,热点数据没有预热等),这些请求会继续作用到数据库(DB)上,造成DB压力剧增,甚至宕机。即有缓存屏障但是屏障没有拦截住流量,击穿:打孔,此时数据是存在的只不过并未缓存。
缓存击穿发生步步骤:1、2、3、4、5、6
解决方法:
1.热点数据不敏感或变化不频繁的情况下可以对热点数据永久缓存
2.添加分布式锁
如:
private static String prefix = "product_cache_product";
private static String lockPrefix = "product_cache_product_lock";
@Override
public SkuDetailEntity findEntityBySkuCodeWithCache(String skuCode) {
if (StringUtils.isBlank(skuCode)) {
return null;
}
//查询缓存
SkuDetailEntity skuEntity = cache.query(Key.valueOf(prefix, skuCode));
//缓存未命中
if (Objects.isNull(skuEntity)) {
try {
//加锁
lock.lock(Key.valueOf(lockPrefix, skuCode), 5L, 10L);
//再次查询缓存
skuEntity = cache.query(Key.valueOf(prefix, skuCode));
if(Objects.isNull(skuEntity)){
//查询数据库
skuEntity = skuInfoMapper.selectDetailByCode(skuCode);
//数据库命中
if(Objects.nonNull(skuEntity)){
//将结果更新至缓存 缓存时间为300秒
cache.save(Key.valueOf(prefix, skuCode),skuEntity,300);
}
}
} catch (Exception e) {
LogUtil.error("数据查询异常", e);
} finally {
try {
//释放锁
lock.unlock(Key.valueOf(lockPrefix, skuCode));
} catch (Exception e) {
LogUtil.error("释放锁异常", e);
}
}
}
return skuEntity;
}
缓存雪崩
缓存雪崩:是指请求的不同数据没有命中缓存但是命中数据库,当大量的请求在极短的时间段内一起请求不同数据的缓存,并都未命中(如:大批量的数据缓存到期,热点数据没有预热等),这些请求会继续作用到数据库(DB)上,造成DB压力剧增,甚至宕机。缓存击穿是指并发查询某一条数据,缓存雪崩是指不同数据都过期,查询这些数据时都查不到
解决方法:
1.设置热点数据永远不过期。
2.缓存不同数据时过期时间可以设置为随机时间,防止同一时间大量不同缓存数据过期现象发生。
防止缓存穿透、击穿、雪崩实例:
private static String prefix = "product_cache_product";
private static String lockPrefix = "product_cache_product_lock";
@Override
public SkuDetailEntity findEntityBySkuCodeWithCache(String skuCode) {
if (StringUtils.isBlank(skuCode)) {
return null;
}
//查询缓存
Optional<SkuDetailEntity> optional = cache.query(Key.valueOf(prefix, skuCode));
//缓存未命中
if (Objects.isNull(optional)) {
try {
//加锁
lock.lock(Key.valueOf(lockPrefix, skuCode), 5L, 10L);
//再次查询缓存
optional = cache.query(Key.valueOf(prefix, skuCode));
if(Objects.isNull(optional)){
//查询数据库
SkuDetailEntity skuEntity = skuInfoMapper.selectDetailByCode(skuCode);
optional = Optional.fromNullable(skuEntity);
//缓存200+随机数秒
cache.save(Key.valueOf(prefix, skuCode),optional,200+(new Random().nextInt(100));
}
} catch (Exception e) {
LogUtil.error("数据查询异常", e);
} finally {
try {
//释放锁
lock.unlock(Key.valueOf(lockPrefix, skuCode));
} catch (Exception e) {
LogUtil.error("释放锁异常", e);
}
}
}
return optional.orNull();
}