首先奉獻(xiàn)caching的開(kāi)源地址[微軟源碼]
1.工程架構(gòu)
為了提高程序效率,我們經(jīng)常將一些不頻繁修改,但是使用了還很大的數(shù)據(jù)進(jìn)行緩存。尤其是互聯(lián)網(wǎng)產(chǎn)品,緩存可以說(shuō)是提升效率優(yōu)化第一利器。微軟為我們實(shí)現(xiàn)了倆種緩存方式:內(nèi)存緩存、分布式緩存。個(gè)人理解如果緩存在前端電腦內(nèi)存的緩存叫做內(nèi)存緩存,如果緩存在其它設(shè)備上,那么叫做分布式緩存。
我開(kāi)發(fā)程序經(jīng)歷過(guò)三個(gè)時(shí)間點(diǎn),開(kāi)始的時(shí)候從來(lái)不使用緩存,之后將數(shù)據(jù)緩存在內(nèi)存中,最后使用分布式緩存。內(nèi)存緩存的優(yōu)點(diǎn)是速度快,缺點(diǎn)是內(nèi)存損耗比較大,可能緩存的數(shù)據(jù)太大的時(shí)候就放不下了,另外一個(gè)缺點(diǎn)就是對(duì)于多前端程序的原則上是不支持的。而分布式緩存的優(yōu)點(diǎn)是,理論上緩存大小沒(méi)有上線,可以通過(guò)擴(kuò)充物理硬件進(jìn)行擴(kuò)展,對(duì)于多前端支持的較好。
Microsoft.Framework.Caching.Abstractions
這個(gè)工程定義的是緩存的整體架構(gòu)。我們的思想是面向接口編程,而不是面向?qū)崿F(xiàn)編程。所以該工程定義了我們想要的接口
從上圖顯而易見(jiàn),微軟將內(nèi)存緩存和分布式緩存割裂開(kāi)來(lái),而不是我們一般意義上定義一個(gè)ICache接口,之后讓IMemoryCache和IDistributedCache分別繼承ICache接口。
所以我們用分布式緩存,內(nèi)存緩存原則不能無(wú)縫的直接切換。需要我們修改程序代碼,或者進(jìn)行適配封裝。
這部分包含內(nèi)容只包含簡(jiǎn)單的倆點(diǎn):配置項(xiàng)(DistributedCacheEntryOptions)、緩存接口(IDistributedCache)。而DistributedCacheEntryExtentions是DistributedCacheEntryOptions的擴(kuò)展方法包裝類(lèi),CaceheExtensions是IDistributedCache擴(kuò)展方法包裝類(lèi),CacheItemPRiority是優(yōu)先級(jí)枚舉。
內(nèi)存緩存,微軟的設(shè)計(jì)就比較復(fù)雜,考慮到方方面面。首先時(shí)緩存的配置項(xiàng)(IMemoryCacheEntryOptions)、緩存接口(IMemoryCache)以及它們擴(kuò)展項(xiàng)(MemoryCacheEntryExtentions、CacheExtentions)。
但是微軟的想法,緩存不止應(yīng)該只有過(guò)期失效,當(dāng)我程序update一個(gè)字段后,我想通知內(nèi)存緩存,我更改了,那又該怎么辦呢?于是微軟設(shè)計(jì)了右上角的部分(*由于代碼的持續(xù)更新原因右上角部分的接口已經(jīng)被去掉,由IList<IChangeToken> ExpirationTokens { get; }屬性替代,但是原則都是一樣的,即外部通知內(nèi)部,數(shù)據(jù)已經(jīng)更新)。
既然外部數(shù)據(jù)更新能通知緩存,那反向呢?緩存更新是否能夠通知外部使用對(duì)象呢?答案是這個(gè)可以支持,所有上邊框,下面的部分。
既然能外部修改通知內(nèi)部,內(nèi)部修改也能通知外部應(yīng)用程序。設(shè)計(jì)已經(jīng)趨近完美了?微軟說(shuō)還不夠,于是上圖左邊的部分產(chǎn)生了。它的意義就是,我可以為緩存創(chuàng)建一個(gè)范圍,至于范圍是做什么的?答案是“我也不知道”,但是從內(nèi)存緩存的實(shí)現(xiàn)上來(lái)看,是用于整體緩存token等信息的。
緩存配置項(xiàng)的時(shí)間選項(xiàng):AbsoluteExpiration、AbsoluteExpirationRelativeToNow、SlidingExpiration。分別表示的是絕對(duì)的過(guò)期時(shí)間點(diǎn)、相對(duì)于現(xiàn)在多久的絕對(duì)過(guò)期時(shí)間點(diǎn),有效期時(shí)長(zhǎng)。我們注意下類(lèi)型AbsoluteExpiration是DateTimeOffset不是DateTime。(*DateTimeOffset 是對(duì)于1970年1月1日0時(shí)的時(shí)間偏移量,和DateTime相比,缺少時(shí)區(qū)的概念。而此處不需要有時(shí)區(qū)相關(guān)概念,所以選用了DateTimeOffset )。
Microsoft.Extensions.Caching.Memory
內(nèi)存緩存的實(shí)現(xiàn)。此處代碼結(jié)構(gòu)如下圖所示:
1,緩存太大時(shí),壓縮緩存空間(個(gè)人理解)
系統(tǒng)創(chuàng)建內(nèi)存緩存對(duì)象(MemoryCache)的時(shí)候,同時(shí)創(chuàng)建GcNotification對(duì)象,之后GcNotification對(duì)象立馬失效。GC需要析構(gòu)的時(shí)候,會(huì)調(diào)用GcNotification的析構(gòu)函數(shù),析構(gòu)函數(shù)被調(diào)用后會(huì)執(zhí)行CallBack函數(shù)(定義在MemoryCache),之后再次注冊(cè)析構(gòu)函數(shù),循環(huán)往復(fù)的如此。所以當(dāng)內(nèi)存占用太高的時(shí)候,緩存會(huì)縮減緩存空間。
if (reRegister && !Environment.HasShutdownStarted) { GC.ReRegisterForFinalize(this); }
2,緩存對(duì)象(MemoryCache)的釋放,沒(méi)有對(duì)象引用緩存的話,難免GC會(huì)回收緩存對(duì)象。那么怎么避免緩存被GC回收?下面代碼的思路還是不錯(cuò)的
~MemoryCache() { Dispose(false); } public void Dispose() { Dispose(true); } protected virtual void Dispose(bool disposing) { if (!_disposed) { if (disposing) { GC.SuppressFinalize(this); } _disposed = true; } } private void CheckDisposed() { if (_disposed) { throw new ObjectDisposedException(typeof(MemoryCache).FullName); } }
3,IEntryLink對(duì)象的跨線程訪問(wèn)
緩存過(guò)期的時(shí)候,很可能不是本線程訪問(wèn)的,可能是另外一個(gè)線程,通過(guò)獲取IEntryLink,之后通過(guò)IChangeToken對(duì)象通知緩存,所以不同線程間必須是可以共享IEntryLink對(duì)象。此處使用的是CallContext.LogicalGetData與CallContext.LogicalSetData。關(guān)于線程見(jiàn)數(shù)據(jù)通信,請(qǐng)參考“如何實(shí)現(xiàn)對(duì)上下文(Context)數(shù)據(jù)的統(tǒng)一管理”
internal static class EntryLinkHelpers { private const string ContextLinkDataName = "EntryLinkHelpers.ContextLink"; public static EntryLink ContextLink { get { var handle = CallContext.LogicalGetData(ContextLinkDataName) as ObjectHandle; if (handle == null) { return null; } return handle.Unwrap() as EntryLink; } set { CallContext.LogicalSetData(ContextLinkDataName, new ObjectHandle(value)); } } internal static IEntryLink CreateLinkingScope() { var parentLink = ContextLink; var newLink = new EntryLink(parent: parentLink); ContextLink = newLink; return newLink; } internal static void DisposeLinkingScope() { var currentLink = ContextLink; var priorLink = ((EntryLink)currentLink).Parent; ContextLink = priorLink; } }
未完待續(xù)......
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注