場景
Mutex主要用於有大量並發訪問並存在cache過期的場合,如
首頁top 10, 由數據庫加載到memcache緩存n分鐘
微博中名人的content cache, 一旦不存在會大量請求不能命中並且載入資料庫
需要執行多個IO操作產生的資料存在cache中, 例如查詢db多次
問題
在大並發的場合,當cache失效時,大量並發同時取不到cache,會同一瞬間去存取db並回設cache,可能會為系統帶來潛在的超負荷風險。我們曾經在線上系統出現過類似故障。
解決方法
方法一
在load db之前先add一個mutex key, mutex key add成功之後再去做加載db, 如果add失敗則sleep之後重試讀取原cache資料。為了防止死鎖,mutex key也需要設定過期時間。偽代碼如下
(註:下文偽代碼僅供了解思路,可能有bug,歡迎隨時指出。)
Java代碼
if (memcache.get(key) == null) { // 3 min timeout to avoid mutex holder crash if (memcache.add(key_mutex, 3 * 60 * 1000) == true) { value = db.get(key); memcache.set(key, value); memcache.delete(key_mutex); } else { sleep(50); retry(); } }
方法
v = memcache.get(key); if (v == null) { if (memcache.add(key_mutex, 3 * 60 * 1000) == true) { value = db.get(key); memcache.set(key, value); memcache.delete(key_mutex); } else { sleep(50); retry(); } } else { if (v.timeout <= now()) { if (memcache.add(key_mutex, 3 * 60 * 1000) == true) { // extend the timeout for other threads v.timeout += 3 * 60 * 1000; memcache.set(key, v, KEY_TIMEOUT * 2); // load the latest value from db v = db.get(key); v.timeout = KEY_TIMEOUT; memcache.set(key, value, KEY_TIMEOUT * 2); memcache.delete(key_mutex); } else { sleep(50); retry(); } } }
在valueue timeout1比實際的memcache timeout(timeout2)小。當從cache讀取到timeout1發現它已經過期時候,馬上延長timeout1並重新設定到cache。然後再從資料庫載入資料並設定到cache。偽代碼如下
Java代碼
rrreee
相對於方案一
缺點:優點:避免cache失效時刻大量請求獲取不到mutex並進行sleep
缺點:代碼:複雜性增大方案已經足夠。