緩存筆記三(緩存擊穿雪崩詳解)

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();
        }
    }
}


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