緩存穿透
緩存穿透是指客戶端請(qǐng)求的數(shù)據(jù)在緩存中和數(shù)據(jù)庫(kù)中都不存在,這樣緩存永遠(yuǎn)不會(huì)生效,這些請(qǐng)求都會(huì)打到數(shù)據(jù)庫(kù)。
常見的解決方案有兩種:
????????緩存空對(duì)象
???????? ????????優(yōu)點(diǎn):實(shí)現(xiàn)簡(jiǎn)單,維護(hù)方便
????????????????缺點(diǎn): 額外的內(nèi)存消耗 可能造成短期的不一致(惡意攻擊的對(duì)象id對(duì)應(yīng)的redis空值緩存失效前成為了新插入的id,造成真實(shí)客戶只能得到空緩存)
????????布隆過(guò)濾(類似于位示圖0、1代表是否存在)
????????????????優(yōu)點(diǎn):內(nèi)存占用較少,沒(méi)有多余key
????????????????缺點(diǎn): 實(shí)現(xiàn)復(fù)雜 存在誤判可能(對(duì)于過(guò)濾結(jié)果:假的一定為假,真的有小概率為假)
?
?緩存穿透的解決方案有哪些?
緩存null值
布隆過(guò)濾
增強(qiáng)id的復(fù)雜度,避免被猜測(cè)id規(guī)律
做好數(shù)據(jù)的基礎(chǔ)格式校驗(yàn)
加強(qiáng)用戶權(quán)限校驗(yàn)
做好熱點(diǎn)參數(shù)的限流
代碼解釋,這是一個(gè)通用的工具類方法:
public <R,ID> R queryWithPassThrough(
String keyPrefix, ID id, Class<R> type, Function<ID,R> dbFallback,Long time,TimeUnit unit){
String key=keyPrefix+id;
String json = stringRedisTemplate.opsForValue().get(key);
if (StrUtil.isNotBlank(json)){
return JSONUtil.toBean(json,type);
}
if (json!=null){
return null;
}
R apply = dbFallback.apply(id);
if (apply==null){ //生成空值緩存,避免緩存穿透
stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL, TimeUnit.MINUTES); //對(duì)象轉(zhuǎn)Json
return null;
}
this.set(key,JSONUtil.toJsonStr(apply),time, unit);
return apply;
}
緩存雪崩
緩存雪崩是指在同一時(shí)段大量的緩存key同時(shí)失效或者Redis服務(wù)宕機(jī),導(dǎo)致大量請(qǐng)求到達(dá)數(shù)據(jù)庫(kù),帶來(lái)巨大壓力。
解決方案:
給不同的Key的TTL添加隨機(jī)值
利用Redis集群提高服務(wù)的可用性
給緩存業(yè)務(wù)添加降級(jí)限流策略
給業(yè)務(wù)添加多級(jí)緩存
緩存擊穿
緩存擊穿問(wèn)題也叫熱點(diǎn)Key問(wèn)題,就是一個(gè)被高并發(fā)訪問(wèn)并且緩存重建業(yè)務(wù)較復(fù)雜的key突然失效了,無(wú)數(shù)的請(qǐng)求訪問(wèn)會(huì)在瞬間給數(shù)據(jù)庫(kù)帶來(lái)巨大的沖擊。
常見的解決方案有兩種:
互斥鎖、邏輯過(guò)期
文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-431363.html
?文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-431363.html
基于代碼實(shí)現(xiàn)互斥鎖
public <R,ID> R queryWithMutex(String keyPrefix,ID id,Class<R> type,Function<ID,R> dbrFallback,Long time,TimeUnit unit){
String key= keyPrefix+id;
String shopJson= stringRedisTemplate.opsForValue().get(key);
if (StrUtil.isNotBlank(shopJson)){ //命中Redis緩存
return JSONUtil.toBean(shopJson, type);
}
if(shopJson!=null){ //命中空值緩存,代表惡意攻擊
return null;
}
if (!tryLock(LOCK_SHOP_KEY+id)) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
return queryWithMutex(keyPrefix,id,type,dbrFallback,time,unit);
}
String shopJson2= stringRedisTemplate.opsForValue().get(key);
//doubleCheck
if (StrUtil.isNotBlank(shopJson2)){ //命中Redis緩存
unlock(LOCK_SHOP_KEY+id);
return JSONUtil.toBean(shopJson2, type);
}
if(shopJson2!=null){ //命中空值緩存,代表惡意攻擊
unlock(LOCK_SHOP_KEY+id);
return null;
}
R apply = dbrFallback.apply(id);
if (apply==null){ //生成空值緩存,避免緩存穿透
stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL, TimeUnit.MINUTES); //對(duì)象轉(zhuǎn)Json
return null;
}
this.setWithLogicalExpire(key,JSONUtil.toJsonStr(apply),time,unit);
unlock(LOCK_SHOP_KEY+id);
return apply;
}
基于代碼實(shí)現(xiàn)邏輯過(guò)期?
public <R,ID> R queryWithLogicalExpire(
String keyPrefix, ID id, Class<R> type, Function<ID,R> dbFallback,Long time,TimeUnit unit){
String key= CACHE_SHOP_KEY+id;
String Json= stringRedisTemplate.opsForValue().get(key);
if (StrUtil.isBlank(Json)){ //未命中Redis緩存
return null;
}
RedisData redisData = JSONUtil.toBean(Json, RedisData.class);
R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);
if (redisData.getExpireTime().isAfter(LocalDateTime.now())){
return r;
}
if (tryLock(key)){
String json=stringRedisTemplate.opsForValue().get(key);
if (StrUtil.isNotBlank(json)){ //命中Redis緩存 二次命中緩存,兜底
RedisData redisData1 = JSONUtil.toBean(json, RedisData.class);
if (redisData1.getExpireTime().isAfter(LocalDateTime.now())){
unlock(key);
return JSONUtil.toBean((JSONObject) redisData1.getData(), type);
}
}
CACHE_REBUILD_EXECUTOR.submit(()->{
R apply = dbFallback.apply(id);
this.setWithLogicalExpire(key,apply,time,unit);
unlock(key);
});
}
return r;
}
附工具類方法
public void set(String key, Object value, Long time, TimeUnit util){
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value),time,util);
}
public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit util){
RedisData redisData=new RedisData();
redisData.setData(value);
redisData.setExpireTime(LocalDateTime.now().plusSeconds(util.toSeconds(time)));
stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(redisData));
}
//線程池
public static final ExecutorService CACHE_REBUILD_EXECUTOR =
Executors.newFixedThreadPool(10);
/**
* 獲取互斥鎖
* @param key
* @return
*/
public Boolean tryLock(String key){
Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", LOCK_SHOP_TTL, TimeUnit.SECONDS);
return BooleanUtil.isTrue(aBoolean);
}
/**
* 釋放互斥鎖
* @param key
* @return
*/
public void unlock(String key){
stringRedisTemplate.delete(key);
}
到了這里,關(guān)于Redis 緩存穿透、緩存雪崩、緩存擊穿的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!