緩存擊穿雪崩詳解

5.1 緩存擊穿

 

緩存擊穿是指緩存中沒有但數據庫中有的數據(一般是緩存時間到期),這時由於 併發用戶特別多,同時讀緩存沒讀到數據,又同時去數據庫去取數據,引起數據庫壓力 瞬間增大,造成過大壓力。

有些數據是錯誤數據沒有必要查庫的數據,例如,有些瞎搞的人輸入錯誤的訂單號,這種情況下我們可以採用布隆過濾來做驗證,防止擊穿,去查庫。

guava工具包中提供了布隆過濾的方法。

布隆過濾技術主要是利用hash去做映射,具體的細節,就不在這細說了。

接下來看一下簡單的代碼應用:

 @PostConstruct //對象創建後,自動調用本方法    public void init(){//在bean初始化完成後,實例化bloomFilter,並加載數據        List<Provinces> provinces = this.list();        //當成一個SET----- 佔內存,比hashset佔得小很多        bf = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), provinces.size());// 32個        for (Provinces p : provinces) {            bf.put(p.getProvinceid());        }    }    @Cacheable(value = "province")    public Provinces detail(String provinceid) {        //先判斷布隆過濾器中是否存在該值,值存在才允許訪問緩存和數據庫        if(!bf.mightContain(provinceid)){            System.out.println("非法訪問--------"+System.currentTimeMillis());            return null;        }        System.out.println("數據庫中得到數據--------"+System.currentTimeMillis());        Provinces provinces = super.detail(provinceid);        return provinces;    }

 

 

 

5.2 緩存雪崩

 

緩存雪崩是指緩存中數據大批量到過期時間,而查詢數據量巨大,引起數據庫壓 力過大甚至 down 機。和緩存擊穿不同的是,緩存擊穿指併發查同一條數據,緩存雪崩 是不同數據都過期了,很多數據都查不到從而查數據庫。

可以簡單的理解爲:大量的擊穿造成雪崩。

 

 

解決方案:

 

對操作數據庫枷鎖,只讓一個線程去查庫,查到數據後返回更新到緩存redis中,然後其他人就可以從緩存中獲取,無需再去查庫。

 

此時需要對數據庫的查詢操作,加鎖 ---- lock (因考慮到是對同一個參數數值上 一把鎖,此處 synchronized 機制無法使用) 加鎖的標準流程代碼如下(一樣解決擊穿的問題):

 

/** * 緩存雪崩 *///@Service("provincesService")public class ProvincesServiceImpl3 extends ProvincesServiceImpl implements ProvincesService{    private static final Logger logger = LoggerFactory.getLogger(ProvincesServiceImpl3.class);    @Resource    private CacheManager cm;    private ConcurrentHashMap<String, Lock> locks = new ConcurrentHashMap<>();//線程安全的    private static final String CACHE_NAME = "province";    public Provinces detail(String provinceid) {        // 1.從緩存中取數據        Cache.ValueWrapper valueWrapper = cm.getCache(CACHE_NAME).get(provinceid);        if (valueWrapper != null) {            logger.info("緩存中得到數據");            return (Provinces) (valueWrapper.get());        }        //2.加鎖排隊,阻塞式鎖---100個線程走到這裏---同一個sql的取同一把鎖        doLock(provinceid);//32個省,最多隻有32把鎖,1000個線程        try{//第二個線程進來了            // 一次只有一個線程             //雙重校驗,不加也沒關係,無非是多刷幾次庫            valueWrapper = cm.getCache(CACHE_NAME).get(provinceid);//第二個線程,能從緩存裏拿到值?            if (valueWrapper != null) {                logger.info("緩存中得到數據");                return (Provinces) (valueWrapper.get());//第二個線程,這裏返回            }            Provinces provinces = super.detail(provinceid);            // 3.從數據庫查詢的結果不爲空,則把數據放入緩存中,方便下次查詢            if (null != provinces){                cm.getCache(CACHE_NAME).put(provinceid, provinces);            }            return provinces;        }catch(Exception e){            return null;        }finally{            //4.解鎖            releaseLock(provinceid);        }    }    private void releaseLock(String userCode) {        ReentrantLock oldLock = (ReentrantLock) locks.get(userCode);        if(oldLock !=null && oldLock.isHeldByCurrentThread()){            oldLock.unlock();        }    }    private void doLock(String lockcode) {//給一個搜索條件,對應一個鎖        //provinceid有不同的值,參數多樣化        //provinceid相同的,加一個鎖,---- 不是同一個key,不能用同一個鎖        ReentrantLock newLock = new ReentrantLock();//創建一個鎖        Lock oldLock = locks.putIfAbsent(lockcode, newLock);//若已存在,則newLock直接丟棄        if(oldLock == null){            newLock.lock();        }else{            oldLock.lock();        }    }}

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章