缓存写法总结
基本寫(xiě)法
為了方便演示,這里使用Runtime.Cache做緩存容器,并定義個(gè)簡(jiǎn)單操作類。如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 | public?class?CacheHelper ???{ ???????public?static?object?Get(string?cacheKey) ???????{ ???????????return?HttpRuntime.Cache[cacheKey]; ???????} ???????public?static?void?Add(string?cacheKey,?object?obj,?int?cacheMinute) ???????{ ???????????HttpRuntime.Cache.Insert(cacheKey, obj,?null, DateTime.Now.AddMinutes(cacheMinute), ???????????????Cache.NoSlidingExpiration, CacheItemPriority.Normal,?null); ???????} ???} |
?簡(jiǎn)單讀取:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | public?object?GetMemberSigninDays1() ????{ ????????const?int?cacheTime = 5; ????????const?string?cacheKey =?"mushroomsir"; ????????var?cacheValue = CacheHelper.Get(cacheKey); ????????if?(cacheValue !=?null) ????????????return?cacheValue; ????????cacheValue =?"395";?//這里一般是 sql查詢數(shù)據(jù)。 例:395 簽到天數(shù) ????????CacheHelper.Add(cacheKey, cacheValue, cacheTime); ????????return?cacheValue; ????} |
在項(xiàng)目中,有不少這樣寫(xiě)法,這樣寫(xiě)并沒(méi)有錯(cuò),但在并發(fā)量上來(lái)后就容易出問(wèn)題。
?緩存雪崩
緩存雪崩是由于緩存失效(過(guò)期),新緩存未到期間。
這個(gè)中間時(shí)間內(nèi),所有請(qǐng)求都去查詢數(shù)據(jù)庫(kù),而對(duì)數(shù)據(jù)庫(kù)CPU和內(nèi)存造成巨大壓力,前端連接數(shù)不夠、查詢阻塞。
這個(gè)中間時(shí)間并沒(méi)有那么短,比如sql查詢1秒,加上傳輸解析0.5秒。 ?就是說(shuō)1.5秒內(nèi)所有用戶查詢,都是直接查詢數(shù)據(jù)庫(kù)的。
碰到這種情況,使用最多的解決方案就是加鎖排隊(duì)。
全局鎖,實(shí)例鎖
public static object obj1 = new object();public object GetMemberSigninDays2(){const int cacheTime = 5;const string cacheKey = "mushroomsir";var cacheValue = CacheHelper.Get(cacheKey);if (cacheValue != null)return cacheValue;//lock (obj1) //全局鎖//{// cacheValue = CacheHelper.Get(cacheKey);// if (cacheValue != null)// return cacheValue;// cacheValue = "395"; //這里一般是 sql查詢數(shù)據(jù)。 例:395 簽到天數(shù)// CacheHelper.Add(cacheKey, cacheValue, cacheTime);//}lock (this){cacheValue = CacheHelper.Get(cacheKey);if (cacheValue != null)return cacheValue;cacheValue = "395"; //這里一般是 sql查詢數(shù)據(jù)。 例:395 簽到天數(shù)CacheHelper.Add(cacheKey, cacheValue, cacheTime);}return cacheValue;}第一種:lock (obj1)??是全局鎖可以滿足,但要為每個(gè)函數(shù)都聲明一個(gè)obj,不然在A、B函數(shù)都鎖obj1時(shí),必然會(huì)讓其中一個(gè)阻塞。
第二種:lock (this) ?這個(gè)鎖當(dāng)前實(shí)例,對(duì)其他實(shí)例無(wú)效,那這個(gè)鎖就沒(méi)什么效果了,當(dāng)然使用單例模式的對(duì)象可以鎖。
? ? ? ? ? ? 在當(dāng)前實(shí)例中:A函數(shù)鎖當(dāng)前實(shí)例,其他也鎖當(dāng)前實(shí)例的函數(shù)的讀寫(xiě),也被阻塞,這種做法也不可取。
字符串鎖
既然鎖對(duì)象不行,利用字符串的特性,直接鎖緩存的key呢
public object GetMemberSigninDays3(){const int cacheTime = 5;const string cacheKey = "mushroomsir";var cacheValue = CacheHelper.Get(cacheKey);if (cacheValue != null)return cacheValue;const string lockKey = cacheKey + "n(*≧▽≦*)n";//lock (cacheKey)//{// cacheValue = CacheHelper.Get(cacheKey);// if (cacheValue != null)// return cacheValue;// cacheValue = "395"; //這里一般是 sql查詢數(shù)據(jù)。 例:395 簽到天數(shù)// CacheHelper.Add(cacheKey, cacheValue, cacheTime);//}lock (lockKey){cacheValue = CacheHelper.Get(cacheKey);if (cacheValue != null)return cacheValue;cacheValue = "395"; //這里一般是 sql查詢數(shù)據(jù)。 例:395 簽到天數(shù)CacheHelper.Add(cacheKey, cacheValue, cacheTime);}return cacheValue;}第一種:lock (cacheName)??有問(wèn)題,因?yàn)樽址彩枪蚕淼?#xff0c;會(huì)阻塞其他使用這個(gè)字符串的操作行為。 ?
? ? ? ? ?具體請(qǐng)參考之前的博文?c#語(yǔ)言-多線程中的鎖系統(tǒng)(一)。?
? ? ? ? ?因?yàn)樽址还舱Z(yǔ)言運(yùn)行庫(kù) (CLR)暫留,這意味著整個(gè)程序中任何給定字符串都只有一個(gè)實(shí)例,所以才會(huì)用下面第二種方法。
第二種:lock (lockKey) ?可以滿足。其目的就是為了保證鎖的粒度最小并且全局唯一性,只鎖當(dāng)前緩存的查詢行為。
緩存穿透
先舉個(gè)簡(jiǎn)單例子:一般網(wǎng)站經(jīng)常會(huì)緩存用戶搜索的結(jié)果,如果數(shù)據(jù)庫(kù)查詢不到,是不會(huì)做緩存的。但如果頻繁查這個(gè)空關(guān)鍵字,會(huì)導(dǎo)致每次請(qǐng)求都直接查詢數(shù)據(jù)庫(kù)了。
例子就是緩存穿透,請(qǐng)求繞過(guò)緩存直接查數(shù)據(jù)庫(kù),這也是經(jīng)常提的緩存命中率問(wèn)題。
public object GetMemberSigninDays4(){const int cacheTime = 5;const string cacheKey = "mushroomsir";var cacheValue = CacheHelper.Get(cacheKey);if (cacheValue != null)return cacheValue;const string lockKey = cacheKey + "n(*≧▽≦*)n";lock (lockKey){cacheValue = CacheHelper.Get(cacheKey);if (cacheValue != null)return cacheValue;cacheValue = null; //數(shù)據(jù)庫(kù)查詢不到,為空。//if (cacheValue2 == null)//{// return null; //一般為空,不做緩存//}if (cacheValue == null){cacheValue = string.Empty; //如果發(fā)現(xiàn)為空,我設(shè)置個(gè)默認(rèn)值,也緩存起來(lái)。}CacheHelper.Add(cacheKey, cacheValue, cacheTime);}return cacheValue;}如果把查詢不到的空結(jié)果,也給緩存起來(lái),這樣下次同樣的請(qǐng)求就可以直接返回null了,即可以避免當(dāng)查詢的值為空時(shí)引起的緩存穿透。
可以單獨(dú)設(shè)置個(gè)緩存區(qū)域存儲(chǔ)空值,對(duì)要查詢的key進(jìn)行預(yù)先校驗(yàn),然后再放行給后面的正常緩存處理邏輯。
再談緩存雪崩
前面不是用加鎖排隊(duì)方式就解決了嗎?其實(shí)加鎖排隊(duì)只是為了減輕數(shù)據(jù)庫(kù)的壓力,本質(zhì)上并沒(méi)有提高系統(tǒng)吞吐量。
假設(shè)在高并發(fā)下,緩存重建期間key是鎖著的,這是過(guò)來(lái)1000個(gè)請(qǐng)求999個(gè)都在阻塞的。導(dǎo)致的結(jié)果是用戶等待超時(shí),這是非常不優(yōu)化的體驗(yàn)。
這種行為本質(zhì)上是把多線程的Web服務(wù)器,在此時(shí)給變成單線程處理了,會(huì)導(dǎo)致大量的阻塞。對(duì)于系統(tǒng)資源也是一種浪費(fèi),因緩存重建而阻塞的線程本可以處理更多請(qǐng)求的。
這里提出一種解決方案是:
public object GetMemberSigninDays5(){const int cacheTime = 5;const string cacheKey = "mushroomsir";//緩存標(biāo)記。const string cacheSign = cacheKey + "_Sign";var sign = CacheHelper.Get(cacheSign);//獲取緩存值var cacheValue = CacheHelper.Get(cacheKey);if (sign != null)return cacheValue; //未過(guò)期,直接返回。lock (cacheSign){sign = CacheHelper.Get(cacheSign);if (sign != null)return cacheValue;CacheHelper.Add(cacheSign, "1", cacheTime);ThreadPool.QueueUserWorkItem((arg) =>{cacheValue = "395"; //這里一般是 sql查詢數(shù)據(jù)。 例:395 簽到天數(shù)CacheHelper.Add(cacheKey, cacheValue, cacheTime*2); //日期設(shè)緩存時(shí)間的2倍,用于臟讀。});}return cacheValue;}從代碼中看出,我們多使用了一個(gè)緩存標(biāo)記key,并使用雙檢鎖校驗(yàn)保證后面邏輯不會(huì)多次執(zhí)行。
緩存標(biāo)記key: 緩存標(biāo)記key只是一個(gè)記錄實(shí)際key過(guò)期時(shí)間的標(biāo)記,它的緩存值可以是任意值,比如1。 它主要用來(lái)在實(shí)際key過(guò)期后,觸發(fā)通知另外的線程在后臺(tái)去更新實(shí)際key的緩存。
實(shí)際key: ?它的過(guò)期時(shí)間會(huì)延長(zhǎng)1倍,例:本來(lái)5分鐘,現(xiàn)在設(shè)置為10分鐘。 這樣做的目的是,當(dāng)緩存標(biāo)記key過(guò)期后,實(shí)際緩存還能以臟數(shù)據(jù)返回給調(diào)用端,直到另外的線程在后臺(tái)更新完成后,才會(huì)返回新緩存。
關(guān)于實(shí)際key的過(guò)期時(shí)間延長(zhǎng)1倍,還是2、3倍都是可以的。只要大于正常緩存過(guò)期時(shí)間,并且能保證在延長(zhǎng)的時(shí)間內(nèi)足夠拉取數(shù)據(jù)即可。
還一個(gè)好處就是,如果突然db掛了,臟數(shù)據(jù)的存在可以保證前端系統(tǒng)不會(huì)拿不到數(shù)據(jù)。
這樣做后,就可以一定程度上提高系統(tǒng)吞吐量。
總結(jié)
文中說(shuō)的阻塞其他函數(shù)指的是,并發(fā)情況下鎖同一對(duì)象,比如一個(gè)函數(shù)鎖A對(duì)象,另外的函數(shù)就必須等待A對(duì)象的鎖釋放后才能再次進(jìn)鎖。
關(guān)于更新緩存,可以單開(kāi)一個(gè)線程去專門(mén)跑緩存更新,圖方便的話扔線程池里面即可。
實(shí)際項(xiàng)目中,緩存層框架的封裝往往要復(fù)雜的多,如果并發(fā)量比較小,這樣寫(xiě)反而會(huì)增加代碼的復(fù)雜度,具體要根據(jù)實(shí)際情況來(lái)取舍。 ? ?
**************************************************************
緩存預(yù)熱
上次有同學(xué)問(wèn)過(guò),在第一次加載時(shí)緩存都為空,怎么進(jìn)行預(yù)熱。
單機(jī)Web情況下一般使用RunTimeCache,這種情況下:
可以在啟動(dòng)事件里面刷新
void Application_Start(object sender, EventArgs e){//刷新}另外可以單寫(xiě)個(gè)刷新緩存頁(yè)面,上線后手動(dòng)刷新下或發(fā)布時(shí)自動(dòng)調(diào)用刷新,再或者由用戶自行觸發(fā)。
分布式緩存(Redis、Memcached)情況下:
比如在幾十臺(tái)服務(wù)器緩存時(shí),單刷滿緩存都需要不少一段時(shí)間。
這種預(yù)熱就復(fù)雜一些,有的會(huì)單寫(xiě)個(gè)應(yīng)用程序去跑,也有的會(huì)單寫(xiě)套框架機(jī)制去處理(更智能化)。
其目的是在系統(tǒng)上線之前,所有的緩存都預(yù)先加載完畢。
多級(jí)緩存
計(jì)算機(jī)結(jié)構(gòu)中CPU和內(nèi)存之間一般都配有一級(jí)緩存、二級(jí)緩存來(lái)增加交換速度。
這樣當(dāng)CPU調(diào)用大量數(shù)據(jù)時(shí),就可避開(kāi)內(nèi)存直接從CPU緩存中調(diào)用,加快讀取速度。
根據(jù)CPU緩存得出多級(jí)緩存的特點(diǎn):
? 1:每一級(jí)緩存中儲(chǔ)存的是下一級(jí)緩存的一部分。
? 2:讀取速度按級(jí)別依次遞減,成本也依次遞減,容量依次遞增。
? 3:當(dāng)前級(jí)別未命中時(shí),才會(huì)去下一級(jí)尋找。
而在企業(yè)應(yīng)用級(jí)開(kāi)發(fā)中,使用多級(jí)緩存是同樣的目的及設(shè)計(jì),只是粒度更粗,更靈活。
根據(jù)速度依次排列l(wèi)v1-lv6的緩存類型圖:
?
?3級(jí)緩存的命中流程圖例子:
線程緩存
Web應(yīng)用是天生的多線程開(kāi)發(fā),對(duì)于一些公共資源必須考慮線程安全,為止不得不通過(guò)鎖來(lái)保證數(shù)據(jù)的完整性和正確性。
在實(shí)際當(dāng)中一臺(tái)web服務(wù)器至少也得處理成百上千的請(qǐng)求,想一想在業(yè)務(wù)復(fù)雜的處理流程,函數(shù)每調(diào)用一次都得鎖一下,對(duì)服務(wù)器也是個(gè)不小的浪費(fèi)。
而通過(guò)線程緩存,可以讓當(dāng)前處理用戶請(qǐng)求的線程只拿自己需要的數(shù)據(jù)。
public static ThreadLocal<UserScore> localUserInfo = new ThreadLocal<UserScore>();借助Net提供的線程本地變量,可以在請(qǐng)求入口去拉取當(dāng)前用戶的數(shù)據(jù)。
在之后線程整個(gè)生命周期里面,業(yè)務(wù)邏輯可以毫無(wú)顧慮的使用這些數(shù)據(jù),而不需要考慮線程安全。
因?yàn)椴挥弥匦履眯戮彺鏀?shù)據(jù),所以也不用擔(dān)心數(shù)據(jù)撕裂的問(wèn)題。
其當(dāng)前線程周期里面的數(shù)據(jù)是完整無(wú)誤的,只有用戶第二次發(fā)起請(qǐng)求才會(huì)重新去拿新數(shù)據(jù)。
這樣就能提高不少服務(wù)器吞吐量,注意要在線程的出口處銷(xiāo)毀數(shù)據(jù)。
內(nèi)存緩存
無(wú)論是遠(yuǎn)程數(shù)據(jù)庫(kù)讀取,還是緩存服務(wù)器讀取。避免不了要跨進(jìn)程,跨網(wǎng)絡(luò)通信,有的還跨機(jī)房。
而應(yīng)用程序頻繁讀寫(xiě),對(duì)Web、DB服務(wù)器都是個(gè)不小的消耗,速度相較內(nèi)存也慢的多。
代碼上加鎖、異步,甚至加服務(wù)器在內(nèi),都不是一個(gè)很好的辦法。因?yàn)榧虞d速度,對(duì)用戶體驗(yàn)非常重要。
?所以在有要求的項(xiàng)目中使用本地內(nèi)存做二級(jí)緩存,是非常有必要的。目的就是1:抗并發(fā),2:加快讀取速度。
有個(gè)著名的緩存五分鐘法則法則,就是說(shuō)如果一個(gè)數(shù)據(jù)頻繁被訪問(wèn),那么就應(yīng)該放內(nèi)存中。
舉個(gè)例子:??有100并發(fā)過(guò)來(lái),加鎖會(huì)導(dǎo)致前端99線程等候,這個(gè)99線程等候著,其實(shí)是一直在消耗Web服務(wù)器資源。不加就是緩存雪崩。
??????????????? 如果每分鐘拉取一份緩存,緩存到內(nèi)存,這樣99線程等候時(shí)間極大縮短。?
文件緩存
相對(duì)于內(nèi)存,硬盤(pán)容量大,速度相較于走網(wǎng)絡(luò)還更快。
所以我們完全可以把一些不經(jīng)常變更,放在內(nèi)存又比較浪費(fèi)的數(shù)據(jù)緩存到本地硬盤(pán)。
比如使用sqlite一些文件數(shù)據(jù)庫(kù),我們很容易做到。
分布式緩存
基于內(nèi)存緩存的redis、memcached等。
基于文件nosql的Casssandra、mongodb等。
redis、memcached是主流的分布式內(nèi)存緩存,也是應(yīng)用和DB中間最大的緩存層。
nosql這類的其實(shí)不單單只是做緩存用了,完全用在一些非核心業(yè)務(wù)的DB層了。
DB緩存
這一層DB主要是緩存由原始數(shù)據(jù)計(jì)算出的結(jié)果,從而避免由Web程序通過(guò)SQL或在使用中直接計(jì)算。
當(dāng)然也可以把計(jì)算好的數(shù)據(jù),存儲(chǔ)到redis中當(dāng)緩存。
多層緩存
多層緩存概念在很多地方都用到過(guò):
1:上面介紹的多級(jí)緩存就是一種,把內(nèi)容根據(jù)讀取頻率,分不同的等級(jí)、不同的層次進(jìn)行存儲(chǔ),頻率越高離查詢?cè)浇?/p>
2:還一種多層是緩存索引的做法,類似B樹(shù)查找,這樣能提高檢索效率。
3:從架構(gòu)上來(lái)說(shuō)瀏覽器緩存、CDN緩存、反向代理緩存、服務(wù)端緩存、也是多層緩存。
總結(jié)
在使用上大家根據(jù)實(shí)際場(chǎng)景,進(jìn)行各種組合搭配。本篇談的比較理論些,有些內(nèi)容細(xì)節(jié)沒(méi)展開(kāi)。
比如分布式緩存的使用,緩存置換策略及算法,緩存過(guò)期機(jī)制等。
***************************************************************
分析設(shè)計(jì)
假設(shè)有個(gè)項(xiàng)目有比較高的并發(fā)量,要用到多級(jí)緩存,如下:
在實(shí)際設(shè)計(jì)一個(gè)內(nèi)存緩存前,需要考慮的問(wèn)題:
1:內(nèi)存與Redis的數(shù)據(jù)置換,盡可能在內(nèi)存中提高數(shù)據(jù)命中率,減少下一級(jí)的壓力。
2:內(nèi)存容量的限制,需要控制緩存數(shù)量。
3:熱點(diǎn)數(shù)據(jù)更新不同,需要可配置單個(gè)key過(guò)期時(shí)間。
4:良好的緩存過(guò)期刪除策略。
5:緩存數(shù)據(jù)結(jié)構(gòu)的復(fù)雜度盡可能的低。
關(guān)于置換及命中率:采用LRU算法,因?yàn)樗鼘?shí)現(xiàn)簡(jiǎn)單,緩存key命中率也很好。
????????????????????????? LRU即是:把最近最少訪問(wèn)的數(shù)據(jù)給淘汰掉,經(jīng)常被訪問(wèn)到即是熱點(diǎn)數(shù)據(jù)。
關(guān)于LRU數(shù)據(jù)結(jié)構(gòu):因?yàn)閗ey優(yōu)先級(jí)提升和key淘汰,所以需要順序結(jié)構(gòu),網(wǎng)上大多實(shí)現(xiàn)都采用的這種鏈表結(jié)構(gòu)。
???????????????????????? 即新數(shù)據(jù)插入到鏈表頭部、被命中時(shí)的數(shù)據(jù)移動(dòng)到頭部,添加復(fù)雜度O(1),移動(dòng)和獲取復(fù)雜度O(N)。
有沒(méi)復(fù)雜度更低的呢? 有Dictionary,復(fù)雜度為O(1),性能最好。 那如何保證緩存的優(yōu)先級(jí)提升呢?
O(1)LRU實(shí)現(xiàn)
定義個(gè)LRUCache<TValue>類,構(gòu)造參數(shù)maxKeySize 來(lái)控制緩存最大數(shù)量。
使用ConcurrentDictionary來(lái)作為我們的緩存容器,并能保證線程安全。
public class LRUCache<TValue> : IEnumerable<KeyValuePair<string, TValue>>{private long ageToDiscard = 0; //淘汰的年齡起點(diǎn)private long currentAge = 0; //當(dāng)前緩存最新年齡private int maxSize = 0; //緩存最大容量private readonly ConcurrentDictionary<string, TrackValue> cache;public LRUCache(int maxKeySize){cache = new ConcurrentDictionary<string, TrackValue>();maxSize = maxKeySize;}}上面定義了?ageToDiscard、currentAge?這2個(gè)自增值參數(shù),作用是標(biāo)記緩存列表中各個(gè)key的新舊程度。
實(shí)現(xiàn)步驟如下:
每次添加key時(shí),currentAge自增并將currentAge值分配給這個(gè)緩存值的age,currentAge一直自增。
public void Add(string key, TValue value){Adjust(key);var result = new TrackValue(this, value);cache.AddOrUpdate(key, result, (k, o) => result);}public class TrackValue{public readonly TValue Value;public long Age;public TrackValue(LRUCache<TValue> lv, TValue tv){Age = Interlocked.Increment(ref lv.currentAge);Value = tv;}}在添加時(shí),如超過(guò)最大數(shù)量,檢查字典里是否有ageToDiscard年齡的key,如沒(méi)有循環(huán)自增檢查,有則刪除、添加成功。
其ageToDiscard+maxSize=?currentAge?,這樣設(shè)計(jì)就能在O(1)下保證可以淘汰舊數(shù)據(jù),而不是使用鏈表移動(dòng)。?
public void Adjust(string key){while (cache.Count >= maxSize){long ageToDelete = Interlocked.Increment(ref ageToDiscard);var toDiscard =cache.FirstOrDefault(p => p.Value.Age == ageToDelete);if (toDiscard.Key == null)continue;TrackValue old;cache.TryRemove(toDiscard.Key, out old);}}獲取key的時(shí)候表示它又被人訪問(wèn),將最新的currentAge賦值給它,增加它的年齡:
public TValue Get(string key){TrackValue value=null;if (cache.TryGetValue(key, out value)){value.Age = Interlocked.Increment(ref currentAge);}return value.Value;}過(guò)期刪除策略
大多數(shù)情況下,LRU算法對(duì)熱點(diǎn)數(shù)據(jù)命中率是很高的。 但如果突然大量偶發(fā)性的數(shù)據(jù)訪問(wèn),會(huì)讓內(nèi)存中存放大量冷數(shù)據(jù),也即是緩存污染。
會(huì)引起LRU無(wú)法命中熱點(diǎn)數(shù)據(jù),導(dǎo)致緩存系統(tǒng)命中率急劇下降,也可以使用LRU-K、2Q、MQ等變種算法來(lái)提高命中率。
過(guò)期配置
通過(guò)設(shè)定最大過(guò)期時(shí)間來(lái)盡量避免冷數(shù)據(jù)常駐內(nèi)存。
多數(shù)情況每個(gè)數(shù)據(jù)緩存的時(shí)間要求不一致的,所以需要再增加單個(gè)key的過(guò)期時(shí)間字段。
private TimeSpan maxTime;public LRUCache(int maxKeySize,TimeSpan maxExpireTime){}//TrackValue增加創(chuàng)建時(shí)間和過(guò)期時(shí)間public readonly DateTime CreateTime;public readonly TimeSpan ExpireTime;刪除策略
關(guān)于key過(guò)期刪除,最好的方式是使用定時(shí)刪除,這樣可以最快的釋放被占用的內(nèi)存,但很明顯大量的定時(shí)器對(duì)CPU來(lái)說(shuō)是非常不友好的。
所以需要采用惰性刪除、在獲取key的時(shí)檢查是否過(guò)期,過(guò)期直接刪除。
public Tuple<TrackValue, bool> CheckExpire(string key){TrackValue result;if (cache.TryGetValue(key, out result)){var age = DateTime.Now.Subtract(result.CreateTime);if (age >= maxTime || age >= result.ExpireTime){TrackValue old;cache.TryRemove(key, out old);return Tuple.Create(default(TrackValue), false);}}return Tuple.Create(result, true);}惰性刪除雖然性能最好,但對(duì)于冷數(shù)據(jù)來(lái)說(shuō)還是沒(méi)解決緩存污染的問(wèn)題,所以還需增加個(gè)定期清理和惰性刪除配合使用。
比如單開(kāi)個(gè)線程每5分鐘去遍歷檢查key是否過(guò)期,這個(gè)時(shí)間策略是可配置的,如果緩存數(shù)量較多可分批遍歷檢查。
public void Inspection(){foreach (var item in this){CheckExpire(item.Key);}}惰性刪除配合定期刪除基本上能滿足絕大多數(shù)要求了。
總結(jié)
本篇參考了redis、Orleans的相關(guān)實(shí)現(xiàn)。
如果繼續(xù)完善下去就是內(nèi)存數(shù)據(jù)庫(kù)的雛形,類似redis,比如增加刪除key的通知回調(diào),支持更多的數(shù)據(jù)類型存儲(chǔ)。
http://www.cnblogs.com/kevingrace/p/5575385.html
總結(jié)
- 上一篇: 怎么在网上买福利彩票?
- 下一篇: MVC-RedirectToAction