最近看到nginx的合并回源,這個和下面的思路有點像。不過nginx的思路還是在控制緩存失效時的并發請求,而不是當緩存快要失效時,及時地更新緩存。
nginx合并回源,參考:http://blog.csdn.net/brainkick/article/details/8570698
update: 2015-04-23
======================
當memcached緩存失效時,容易出現高并發的查詢db,導緻db壓力驟然上升。
這篇blog主要是探讨如何在緩存将要失效時,及時地更新緩存,而不是如何在緩存失效之後,如何防止高并發的db查詢。
個人認為,當緩存将要失效時,及時地把新的資料刷到memcached裡,這個是解決緩存失效瞬間高并發查db的最好方法。那麼如何及時地知道緩存将要失效?
解決這個問題有幾種思路:
比如一個key是aaa,失效時間是30s。
這種方法有個缺點是,有些業務的key可能是變化的,不确定的。
而且不好界定哪些資料是應該查詢出來放到緩存中的,難以區分冷熱資料。
這種方式不太靠譜,不多讨論。而且如果是多個web伺服器的話,還是有可能有并發的操作。
當get得到資料時,如果目前時間 - 過期時間 > 5s,則背景啟動一個任務去查詢db,更新緩存。
當然,這裡的背景任務必須保證同一個key,隻有一個線程在執行查詢db的任務,不然這個還是高并發查詢db。
缺點是要把過期時間和value合在一起序列化,取出資料後,還要反序列化。很不友善。
網上大部分文章提到的都是前面兩種方式,有少數文章提到第3種方式。下面提出一種基于兩個key的方法:
比如key是aaa,設定失效時間為30s,則另一個key為expire_aaa,失效時間為25s。
在取資料時,用multiget,同時取出aaa和expire_aaa,如果expire_aaa的value == null,則背景啟動一個任務去查詢db,更新緩存。和上面類似。
對于背景啟動一個任務去查詢db,更新緩存,要保證一個key隻有一個線程在執行,這個如何實作?
對于同一個程序,簡單加鎖即可。拿到鎖的就去更新db,沒拿到鎖的直接傳回。
對于叢集式的部署的,如何實作隻允許一個任務執行?
這裡就要用到memcached的add指令了。
add指令是如果不存在key,則設定成功,傳回true,如果已存在key,則不存儲,傳回false。
當get expired_aaa是null時,則add expired_aaa 過期時間由自己靈活處理。比如設定為3秒。
如果成功了,再去查詢db,查到資料後,再set expired_aaa為25秒。set aaa 為30秒。
綜上所述,來梳理下流程:
比如一個key是aaa,失效時間是30s。查詢db在1s内。
put資料時,設定aaa過期時間30s,設定expire_aaa過期時間25s;
get資料時,multiget aaa 和 expire_aaa,如果expired_aaa對應的value != null,則直接傳回aaa對應的資料給使用者。如果expire_aaa傳回value == null,則背景啟動一個任務,嘗試add expire_aaa,并設定逾時過間為3s。這裡設定為3s是為了防止背景任務失敗或者阻塞,如果這個任務執行失敗,那麼3秒後,如果有另外的使用者通路,那麼可以再次嘗試查詢db。如果add執行成功,則查詢db,再更新aaa的緩存,并設定expire_aaa的逾時時間為25s。
update:2014-06-29
最近重新思考了下這個問題。發現第4種兩個key的辦法比較耗memcached的記憶體,因為key數翻倍了。結合第3種方式,重新設計了下,思路如下:
仍然使用兩個key的方案:
key
__load_{key}
其中,__load_{key} 這個key相當于一個鎖,隻允許add成功的線程去更新資料,而這個key的逾時時間是比較短的,不會一直占用memcached的記憶體。
在set 到memcached的value中,加上一個時間,(time, value),time是memcached上的key未來會過期的時間,并不是目前系統時間。
當get到資料時,檢查時間是否快要逾時: time - now < 5 * 1000,假定設定了快要逾時的時間是5秒。
* 如果是,則背景啟動一個新的線程:
* 嘗試 add __load_{key},
* 如果成功,則去加載新的資料,并set到memcached中。
* 原來的線程直接傳回value給調用者。
按上面的思路,用xmemcached封裝了下:
dataloader,使用者要實作的加載資料的回調接口:
refreshcachemanager,使用者隻需要關心這這兩個接口函數:
其中autoretryget函數如果get到是null,内部會自動重試4次,每次間隔500ms。
refreshcachemanager内部自動處理資料快過期,重新重新整理到memcached的邏輯。
詳細的封裝代碼在這裡:https://gist.github.com/hengyunabc/cc57478bfcb4cd0553c2
我個人是傾向于第5種方式的,因為很簡單,直覺。比第4種方式要節省記憶體,而且不用mget,在使用memcached叢集時不用擔心出麻煩事。
這種兩個key的方式,還有一個好處,就是資料是自然冷熱适應的。如果是冷資料,30秒都沒有人通路,那麼資料會過期。
如果是熱門資料,一直有大流量通路,那麼資料就是一直熱的,而且資料一直不會過期。