[轉發][非原創]oschina上的一種雙快取思路

流星線上發表於2011-08-26

oschina上的一種雙快取思路

舉個例子:oschina 的首頁,訪問量很高,這個頁面必須得快取,oschina 使用的是 ehcache。

一般我們的快取偽碼如此:

List<T> objs = (List<T>)CacheManager.get(cache_region, key);
if(objs == null)
{
    objs = database_query(beanClass, sql, params);
    CacheManager.set(cache_region, key, (Serializable)objs);
}
return objs;

問題是一旦快取失效需要從資料庫重新載入資料的時候,大量的併發資料庫訪問會導致響應超級慢,也就是所謂的雪崩。

目前我們對這個問題的處理方法,我簡單的說一下思路,具體的程式碼等有空再整理出來

假設我們前面舉的例子中所使用的快取region(region1),設定了自動失效時間為5分鐘,相當於說每5分鐘就會有一次發現訪問首頁很卡。因此引入了第二個快取 region (region2) ,這個快取的物件不會自動失效,也就說該區域的資料長期有效。

region2 的配置:

<cache name="icache-global"
    maxElementsInMemory="100"
    eternal="true"
    overflowToDisk="true"
    />

引入了第二個長效的region後,資料的讀取流程是這樣的:

  1. 從 region1 讀取資料,有則直接返回
  2. region1 沒資料則啟動資料更新執行緒(下面介紹),然後從 region2 讀資料,有則返回
  3. region2 也沒有資料

這種情況屬於系統剛剛啟動,快取還沒有填充資料的情況,沒辦法,這時候肯定會卡,或者你應該在系統啟動的時候,自行填充一下資料,很簡單,我一般在tomcat啟動後,用命令訪問下首頁就有了快取資料。

這樣做的目的是為了正常的快取失效後,無需等待重新從資料庫中獲取資料,而是直接在 region2 中獲取資料並返回。因此對使用者來講,不會感覺請求被堵塞。

雖然請求順暢了,但是資料還得更新,因此重要的還是啟動資料更新執行緒是如何處理的。

執行緒本身所執行的方法很簡單,無非就是到資料庫中讀取資料,然後將資料填充到 region1 ,但記得要同時填充到 region2,以確保下次快取失效時,獲取得到的是最新的資料。

就這麼簡單,其實也可以工作,但會有一個問題:假設快取失效的時候,同時來了100個請求,那麼這100個請求會同時啟動100個資料更新執行緒,這100個資料更新執行緒會到資料庫執行同樣的SQL語句獲得同樣的結果,因此這種做法對資料庫的壓力並沒有降低。

於是我需要線上程的執行方法裡做一些調整,下面是偽碼:

String data = CacheManager.get(String.class, region1, key);
if(data == null){
    ReentrantLock lock = g_locks.get(key); 
    if(!lock.tryLock())
        return null;
    try
    {
        //1. 執行SQL查詢獲取資料
        //2. 資料填充到 region1
        //3. 資料填充到 region2
    }
    finally
    {
        lock.unlock();
    }
}

首先還是要判斷下快取資料是否已存在,然後使用了一個 ReentrantLock 鎖物件來控制不讓多餘的執行緒去執行資料更新過程。

這只是個大體的思路,僅供參考,目前已經在 oschina 上使用。

別提什麼頁面靜態化,我對那個一點都不感冒。

相關文章