一般緩存使用流程
一般的緩存使用流程:
- 從緩存中查詢數(shù)據(jù)
- 判斷緩存中數(shù)據(jù)是否存在
- 存在則直接返回?cái)?shù)據(jù)
- 數(shù)據(jù)不存在,查詢數(shù)據(jù)庫
- 判斷數(shù)據(jù)庫中數(shù)據(jù)是否存在
- 存在更新緩存,返回結(jié)果
- 不存在直接返回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)) {
//查詢數(shù)據(jù)庫
skuEntity = skuInfoMapper.selectDetailByCode(skuCode);
//數(shù)據(jù)庫命中
if(Objects.nonNull(skuEntity)){
//將結(jié)果更新至緩存 緩存時(shí)間為300秒
cache.save(Key.valueOf(prefix, skuCode),skuEntity,300);
}
}
return skuEntity;
}
緩存穿透
緩存穿透:是指請求的數(shù)據(jù)即沒有命中緩存也沒有命中DB中(如:黑客偽造skuCode發(fā)送大量的請求),此時(shí)請求會直接作用的DB上,流量大時(shí)可造成DB宕機(jī)。即有緩存屏障但是被惡意繞過,穿透:從縫隙、漏洞中穿過,此處漏洞即為請求的數(shù)據(jù)更本不存在。
緩存穿透發(fā)生步步驟:1、2、4、5、7
解決方法:
1.對請求來的參數(shù)進(jìn)行合法校驗(yàn),如:有規(guī)則的skuCode可以對skuCode進(jìn)行校驗(yàn)
2.對null也進(jìn)行緩存
如:
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)) {
//查詢數(shù)據(jù)庫
SkuDetailEntity skuEntity = skuInfoMapper.selectDetailByCode(skuCode);
optional = Optional.fromNullable(skuEntity);
//緩存5分鐘
cache.save(Key.valueOf(prefix, skuCode),optional,300);
}
return optional.orNull();
}
緩存擊穿
緩存擊穿:是指請求的數(shù)據(jù)沒有命中緩存但是命中數(shù)據(jù)庫,當(dāng)大量的請求在極短的時(shí)間段內(nèi)一起請求某條數(shù)據(jù)的緩存并都未命中(如:緩存失效時(shí)還存在大量的請求,熱點(diǎn)數(shù)據(jù)沒有預(yù)熱等),這些請求會繼續(xù)作用到數(shù)據(jù)庫(DB)上,造成DB壓力劇增,甚至宕機(jī)。即有緩存屏障但是屏障沒有攔截住流量,擊穿:打孔,此時(shí)數(shù)據(jù)是存在的只不過并未緩存。
緩存擊穿發(fā)生步步驟:1、2、3、4、5、6
解決方法:
1.熱點(diǎn)數(shù)據(jù)不敏感或變化不頻繁的情況下可以對熱點(diǎn)數(shù)據(jù)永久緩存
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)){
//查詢數(shù)據(jù)庫
skuEntity = skuInfoMapper.selectDetailByCode(skuCode);
//數(shù)據(jù)庫命中
if(Objects.nonNull(skuEntity)){
//將結(jié)果更新至緩存 緩存時(shí)間為300秒
cache.save(Key.valueOf(prefix, skuCode),skuEntity,300);
}
}
} catch (Exception e) {
LogUtil.error("數(shù)據(jù)查詢異常", e);
} finally {
try {
//釋放鎖
lock.unlock(Key.valueOf(lockPrefix, skuCode));
} catch (Exception e) {
LogUtil.error("釋放鎖異常", e);
}
}
}
return skuEntity;
}
緩存雪崩
緩存雪崩:是指請求的不同數(shù)據(jù)沒有命中緩存但是命中數(shù)據(jù)庫,當(dāng)大量的請求在極短的時(shí)間段內(nèi)一起請求不同數(shù)據(jù)的緩存,并都未命中(如:大批量的數(shù)據(jù)緩存到期,熱點(diǎn)數(shù)據(jù)沒有預(yù)熱等),這些請求會繼續(xù)作用到數(shù)據(jù)庫(DB)上,造成DB壓力劇增,甚至宕機(jī)。緩存擊穿是指并發(fā)查詢某一條數(shù)據(jù),緩存雪崩是指不同數(shù)據(jù)都過期,查詢這些數(shù)據(jù)時(shí)都查不到
解決方法:
1.設(shè)置熱點(diǎn)數(shù)據(jù)永遠(yuǎn)不過期。
2.緩存不同數(shù)據(jù)時(shí)過期時(shí)間可以設(shè)置為隨機(jī)時(shí)間,防止同一時(shí)間大量不同緩存數(shù)據(jù)過期現(xiàn)象發(fā)生。
防止緩存穿透、擊穿、雪崩實(shí)例:
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)){
//查詢數(shù)據(jù)庫
SkuDetailEntity skuEntity = skuInfoMapper.selectDetailByCode(skuCode);
optional = Optional.fromNullable(skuEntity);
//緩存200+隨機(jī)數(shù)秒
cache.save(Key.valueOf(prefix, skuCode),optional,200+(new Random().nextInt(100));
}
} catch (Exception e) {
LogUtil.error("數(shù)據(jù)查詢異常", e);
} finally {
try {
//釋放鎖
lock.unlock(Key.valueOf(lockPrefix, skuCode));
} catch (Exception e) {
LogUtil.error("釋放鎖異常", e);
}
}
}
return optional.orNull();
}