- 緩存雪崩概念
故障原因:redis掛了 事前:redis高可用,主從+哨兵乓序,redis cluster诞吱,避免全盤崩潰 事中:本地cache緩存 + hystrix限流&降級,避免MySQL被打死 事后:redis持久化竭缝,快速恢復(fù)緩存數(shù)據(jù)
故障原因2:緩存時采用了相同的過期時間房维,導(dǎo)致緩存在某一時刻同時失效 將緩存失效時間分散開,比如我們可以在原有的失效時間基礎(chǔ)上增加一個隨機(jī)值抬纸,比如1-5分鐘隨機(jī)咙俩,這樣每一個緩存的過期時間的重復(fù)率就會降低,就很難引發(fā)集體失效的事件湿故。(和緩存擊穿不同的是阿趁,緩存擊穿指并發(fā)查同一條數(shù)據(jù),緩存雪崩是不同數(shù)據(jù)都過期了坛猪,很多數(shù)據(jù)都查不到從而查數(shù)據(jù)庫脖阵。)
2、緩存穿透
緩存穿透是指緩存和數(shù)據(jù)庫中都沒有的數(shù)據(jù)墅茉,而用戶不斷發(fā)起請求命黔,如發(fā)起為id為“-1”的數(shù)據(jù)或id為特別大不存在的數(shù)據(jù)。這時的用戶很可能是攻擊者就斤,攻擊會導(dǎo)致數(shù)據(jù)庫壓力過大悍募。解決辦法有兩個:
查詢數(shù)據(jù)庫不存在,則在redis中存一份Null值洋机,避免訪問Mysql
采用布隆過濾器坠宴,將List數(shù)據(jù)裝載入布隆過濾器中,訪問經(jīng)過布隆過濾器绷旗,存在才可以往db中查詢喜鼓。于是在內(nèi)存中就可以攔截惡意請求。
解決方案:布隆過濾器
布隆過濾器的使用方法衔肢,類似java的SET集合庄岖,用來判斷某個元素(key)是否在某個集合中。和一般的hash set不同的是膀懈,這個算法無需存儲key的值顿锰,對于每個key,只需要k個比特位启搂,每個存儲一個標(biāo)志硼控,用來判斷key是否在集合中。
使用步驟:1胳赌、將List數(shù)據(jù)裝載入布隆過濾器中
private BloomFilter<String> bf =null;
//PostConstruct注解對象創(chuàng)建后牢撼,自動調(diào)用本方法
@PostConstruct
public void init(){
//在bean初始化完成后,實(shí)例化bloomFilter疑苫,并加載數(shù)據(jù)
List<Entity> entities= initList();
//初始化布隆過濾器
bf = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), entities.size());
for (Entity entity : entities) {
bf.put(entity.getId());
}
}
2熏版、訪問經(jīng)過布隆過濾器,存在才可以往db中查詢
public Provinces query(String id) {
//先判斷布隆過濾器中是否存在該值捍掺,值存在才允許訪問緩存和數(shù)據(jù)庫
if(!bf.mightContain(id)){
Log.info("非法訪問"+System.currentTimeMillis());
return null;
}
Log.info("數(shù)據(jù)庫中得到數(shù)據(jù)"+System.currentTimeMillis());
Entity entity= super.query(id);
return entity;
}
這樣當(dāng)外界有惡意攻擊時撼短,不存在的數(shù)據(jù)請求就可以直接攔截在過濾器層,而不會影響到底層數(shù)據(jù)庫系統(tǒng)挺勿。
- 緩存擊穿概念
一個存在的key曲横,在緩存過期的一刻,同時有大量的請求不瓶,這些請求都會擊穿到DB禾嫉,造成瞬時DB請求量大、壓力驟增蚊丐。
解決方案
在訪問key之前熙参,采用SETNX(set if not exists)來設(shè)置另一個短期key來鎖住當(dāng)前key的訪問,訪問結(jié)束再刪除該短期key麦备。
上面的現(xiàn)象是多個線程同時去查詢數(shù)據(jù)庫的這條數(shù)據(jù)孽椰,那么我們可以在第一個查詢數(shù)據(jù)的請求上使用一個 互斥鎖來鎖住它。
其他的線程走到這一步拿不到鎖就等著凛篙,等第一個線程查詢到了數(shù)據(jù)弄屡,然后做緩存。后面的線程進(jìn)來發(fā)現(xiàn)已經(jīng)有緩存了鞋诗,就直接走緩存膀捷。
static Lock reenLock = new ReentrantLock();
public List<String> getData04() throws InterruptedException {
List<String> result = new ArrayList<String>();
// 從緩存讀取數(shù)據(jù)
result = getDataFromCache();
if (result.isEmpty()) {
if (reenLock.tryLock()) {
try {
System.out.println("我拿到鎖了,從DB獲取數(shù)據(jù)庫后寫入緩存");
// 從數(shù)據(jù)庫查詢數(shù)據(jù)
result = getDataFromDB();
// 將查詢到的數(shù)據(jù)寫入緩存
setDataToCache(result);
} finally {
reenLock.unlock();// 釋放鎖
}
} else {
result = getDataFromCache();// 先查一下緩存
if (result.isEmpty()) {
System.out.println("我沒拿到鎖,緩存也沒數(shù)據(jù),先小憩一下");
Thread.sleep(100);// 小憩一會兒
return getData04();// 重試
}
}
}
return result;
}
鏈接:http://www.reibang.com/p/87896241343c
4、數(shù)據(jù)庫與緩存一致性
Cache Aside Pattern削彬,基本都采用如下的緩存模式:
讀的時候全庸,先讀緩存,緩存沒有的話融痛,那么就讀數(shù)據(jù)庫壶笼,然后取出數(shù)據(jù)后放入緩存,同時返回響應(yīng)
更新的時候雁刷,先更新數(shù)據(jù)庫,再刪除緩存(先刪除緩存在更新數(shù)據(jù)庫會存在臟數(shù)據(jù))
參考:
http://www.reibang.com/p/dc09f86ca4ba
http://www.reibang.com/p/cbc39abb6b94