- 緩存雪崩概念
故障原因:redis掛了 事前:redis高可用,主從+哨兵,redis cluster,避免全盤崩潰 事中:本地cache緩存 + hystrix限流&降級(jí),避免MySQL被打死 事后:redis持久化,快速恢復(fù)緩存數(shù)據(jù)
故障原因2:緩存時(shí)采用了相同的過期時(shí)間,導(dǎo)致緩存在某一時(shí)刻同時(shí)失效 將緩存失效時(shí)間分散開,比如我們可以在原有的失效時(shí)間基礎(chǔ)上增加一個(gè)隨機(jī)值,比如1-5分鐘隨機(jī),這樣每一個(gè)緩存的過期時(shí)間的重復(fù)率就會(huì)降低,就很難引發(fā)集體失效的事件。(和緩存擊穿不同的是,緩存擊穿指并發(fā)查同一條數(shù)據(jù),緩存雪崩是不同數(shù)據(jù)都過期了,很多數(shù)據(jù)都查不到從而查數(shù)據(jù)庫。)
2、緩存穿透
緩存穿透是指緩存和數(shù)據(jù)庫中都沒有的數(shù)據(jù),而用戶不斷發(fā)起請(qǐng)求,如發(fā)起為id為“-1”的數(shù)據(jù)或id為特別大不存在的數(shù)據(jù)。這時(shí)的用戶很可能是攻擊者,攻擊會(huì)導(dǎo)致數(shù)據(jù)庫壓力過大。解決辦法有兩個(gè):
查詢數(shù)據(jù)庫不存在,則在redis中存一份Null值,避免訪問Mysql
采用布隆過濾器,將List數(shù)據(jù)裝載入布隆過濾器中,訪問經(jīng)過布隆過濾器,存在才可以往db中查詢。于是在內(nèi)存中就可以攔截惡意請(qǐng)求。
解決方案:布隆過濾器
布隆過濾器的使用方法,類似java的SET集合,用來判斷某個(gè)元素(key)是否在某個(gè)集合中。和一般的hash set不同的是,這個(gè)算法無需存儲(chǔ)key的值,對(duì)于每個(gè)key,只需要k個(gè)比特位,每個(gè)存儲(chǔ)一個(gè)標(biāo)志,用來判斷key是否在集合中。
使用步驟:1、將List數(shù)據(jù)裝載入布隆過濾器中
private BloomFilter<String> bf =null;
//PostConstruct注解對(duì)象創(chuàng)建后,自動(dò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í),不存在的數(shù)據(jù)請(qǐng)求就可以直接攔截在過濾器層,而不會(huì)影響到底層數(shù)據(jù)庫系統(tǒng)。
- 緩存擊穿概念
一個(gè)存在的key,在緩存過期的一刻,同時(shí)有大量的請(qǐng)求,這些請(qǐng)求都會(huì)擊穿到DB,造成瞬時(shí)DB請(qǐng)求量大、壓力驟增。
解決方案
在訪問key之前,采用SETNX(set if not exists)來設(shè)置另一個(gè)短期key來鎖住當(dāng)前key的訪問,訪問結(jié)束再刪除該短期key。
上面的現(xiàn)象是多個(gè)線程同時(shí)去查詢數(shù)據(jù)庫的這條數(shù)據(jù),那么我們可以在第一個(gè)查詢數(shù)據(jù)的請(qǐng)求上使用一個(gè) 互斥鎖來鎖住它。
其他的線程走到這一步拿不到鎖就等著,等第一個(gè)線程查詢到了數(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);// 小憩一會(huì)兒
return getData04();// 重試
}
}
}
return result;
}
鏈接:http://www.itdecent.cn/p/87896241343c
4、數(shù)據(jù)庫與緩存一致性
Cache Aside Pattern,基本都采用如下的緩存模式:
讀的時(shí)候,先讀緩存,緩存沒有的話,那么就讀數(shù)據(jù)庫,然后取出數(shù)據(jù)后放入緩存,同時(shí)返回響應(yīng)
更新的時(shí)候,先更新數(shù)據(jù)庫,再刪除緩存(先刪除緩存在更新數(shù)據(jù)庫會(huì)存在臟數(shù)據(jù))
參考:
http://www.itdecent.cn/p/dc09f86ca4ba
http://www.itdecent.cn/p/cbc39abb6b94