Redis緩存、雪崩、穿透,數(shù)據(jù)一致性

  1. 緩存雪崩概念

故障原因: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)。

  1. 緩存擊穿概念
    一個(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容