緩存更新的策略

緩存更新的策略

 

1、先淘汰緩存,再更新數據庫
    public void updateData1(DataObject dataObject) {
        //第一步,淘汰緩存
        deleteFromCache(dataObject.getId());
        //第二步,操作數據庫
        updateFromDB(dataObject);
    }

問題分析:

兩個併發操作,操作時序如下:

  • 1、更新請求刪除了緩存

  • 2、查詢請求沒有命中緩存

  • 3、查詢請求從數據庫中讀出數據放入緩存

  • 4、更新請求更新了數據庫中的數據。
    於是,在緩存中的數據還是老的數據,導致緩存中的數據是髒的

     

2、先更新數據庫,再修改緩存
    public void updateData3(DataObject dataObject) {
        //第一步,更新數據庫
        updateFromDB(dataObject);
        //第二步,更新緩存
        setDataToCache(dataObject.getId(), dataObject);
    }

問題分析:

兩個併發更新操作,操作時序

  • 1、請求1更新數據庫

  • 2、請求2更新數據庫

  • 3、請求2set緩存

  • 4、請求1set緩存

    數據庫中的數據是請求2設置的,而緩存中的數據是請求1設置的,數據庫與緩存的數據不一致

     

3、先更新數據庫,再淘汰緩存
    public void updateData2(DataObject dataObject) {
        //第一步,操作數據庫
        updateFromDB(dataObject);
        //第二步,淘汰緩存
        deleteFromCache(dataObject.getId());
    }

這是經典的Cache Aside Pattern

問題分析:

第一步數據庫更新成功,第二步緩存操作失敗,會導致緩存中的是髒數據,原子性無法保證。

兩個併發操作,操作時序如下:
1、查詢請求沒有命中緩存
2、查詢請求從數據庫中讀出數據
3、更新請求更新了數據庫
4、更新請求刪除緩存
5、查詢請求把讀取到的老數據放入緩存
於是,在緩存中的數據還是老的數據,導致緩存中的數據是髒的

但這個case理論上會出現,不過實際上出現的概率可能非常低,
因爲這個條件需要發生在讀緩存時緩存失效,而且併發着有一個寫操作。
而實際上數據庫的寫操作會比讀操作慢得多,而且還要鎖表,
而讀操作必需在寫操作前進入數據庫操作,而又要晚於寫操作更新緩存,所有的這些條件都具備的概率基本並不大。

 

4、先更新數據庫,再淘汰緩存,並且保證原子性
@Transactional    
public void updateData(DataObject dataObject) {
        //第一步,操作數據庫
        updateFromDB(dataObject);
        //第二步,淘汰緩存
        try {
            deleteFromCache(dataObject.getId());
        } catch (Exception e) {
            throw new RuntimeException();
        }
    }

問題分析:

這是Cache Aside Pattern的改進版,查詢和更新請求併發的問題同樣存在

將方法置於事務中執行,緩存操作失敗拋出RuntimeException會回滾事務,保證了原子性
缺點是redis遠程操作會導致事務執行時間變長,降低併發

 

任何技術方案的設計,都是折衷。只有適合的方案,未必有最優的方案。

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