日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

一步步实现一个基本的缓存模块

發(fā)布時(shí)間:2025/3/13 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 一步步实现一个基本的缓存模块 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

一步步實(shí)現(xiàn)一個(gè)基本的緩存模塊

注意后續(xù)代碼及改進(jìn)見后后文及github,文章上的并沒有更新。

??? 1. 前言
??? 2.? 請(qǐng)求級(jí)別緩存
??? 2.1 多線程
??? 3.? 進(jìn)程級(jí)別緩存
??? 3.1 分區(qū)與計(jì)數(shù)
??? 3.2 可空緩存值
??? 3.3 封裝與集成
??? 4.? 小結(jié)

1. 前言

  • 面向讀者:初、中級(jí)用戶;
  • 涉及知識(shí):HttpContext、HttpRuime.Cache、DictionaryEntry、Unit Test等;
  • 文章目的:這里的內(nèi)容不會(huì)涉及 Memcached、Redies 等進(jìn)程外緩存的使用,只針對(duì)包含WEB應(yīng)用的常見場(chǎng)景,實(shí)現(xiàn)一個(gè)具有線程安全、分區(qū)、過期特性的緩存模塊,略微提及DI等內(nèi)容。
  • jusfr 原創(chuàng),轉(zhuǎn)載請(qǐng)注明來自博客園。


2.? 請(qǐng)求級(jí)別緩存

如果需要線程安全地存取數(shù)據(jù),System.Collections.Concurrent 命名空間下的像 ConcurrentDictionary 等實(shí)現(xiàn)是首選;更復(fù)雜的特性像過期策略、文件依賴等就需要其他實(shí)現(xiàn)了。ASP.NET中的HttpContext.Current.Items 常常被用作自定義數(shù)據(jù)容器,注入工具像Unity、Autofac 等便借助自定義 HttpModule 將容器掛接在 HttpContext.Current 上以進(jìn)行生命周期管理。

基本接口 ICacheProvider,請(qǐng)求級(jí)別的緩存從它定義,考慮到請(qǐng)求級(jí)別緩存的運(yùn)用場(chǎng)景有限,故只定義有限特性;

1 public interface ICacheProvider { 2 Boolean TryGet<T>(String key, out T value); 3 T GetOrCreate<T>(String key, Func<T> function); 4 T GetOrCreate<T>(String key, Func<String, T> factory); 5 void Overwrite<T>(String key, T value); 6 void Expire(String key); 7 }

HttpContext.Current.Items 從 IDictionary 定義,存儲(chǔ) Object-Object 鍵值對(duì),出于便利與直觀,ICacheProvider 只接受String類型緩存鍵,故HttpContextCacheProvider內(nèi)部使用 BuildCacheKey(String key) 方法生成真正緩存鍵以避免鍵值重復(fù);

同時(shí) HashTable 可以存儲(chǔ)空引用作為緩存值,故 TryGet() 方法先進(jìn)行 Contains() 判斷存在與否,再進(jìn)行類型判斷,避免緩存鍵重復(fù)使用;??

1 public class HttpContextCacheProvider : ICacheProvider { 2 protected virtual String BuildCacheKey(String key) { 3 return String.Concat("HttpContextCacheProvider_", key); 4 } 5 6 public Boolean TryGet<T>(String key, out T value) { 7 key = BuildCacheKey(key); 8 Boolean exist = false; 9 if (HttpContext.Current.Items.Contains(key)) { 10 exist = true; 11 Object entry = HttpContext.Current.Items[key]; 12 if (entry != null && !(entry is T)) { 13 throw new InvalidOperationException(String.Format("緩存項(xiàng)`[{0}]`類型錯(cuò)誤, {1} or {2} ?", 14 key, entry.GetType().FullName, typeof(T).FullName)); 15 } 16 value = (T)entry; 17 } 18 else { 19 value = default(T); 20 } 21 return exist; 22 } 23 24 public T GetOrCreate<T>(String key, Func<T> function) { 25 T value; 26 if (TryGet(key, out value)) { 27 return value; 28 } 29 value = function(); 30 Overwrite(key, value); 31 return value; 32 } 33 34 public T GetOrCreate<T>(String key, Func<String, T> factory) { 35 T value; 36 if (TryGet(key, out value)) { 37 return value; 38 } 39 value = factory(key); 40 Overwrite(key, value); 41 return value; 42 } 43 44 public void Overwrite<T>(String key, T value) { 45 key = BuildCacheKey(key); 46 HttpContext.Current.Items[key] = value; 47 } 48 49 public void Expire(String key) { 50 key = BuildCacheKey(key); 51 HttpContext.Current.Items.Remove(key); 52 } 53 }

這里使用了 Func<T> 委托的運(yùn)用,合并查詢、判斷和添加緩存項(xiàng)的操作以簡(jiǎn)化接口調(diào)用;如果用戶期望不同類型緩存值可以存儲(chǔ)到相同的 key 上,則需要重新定義 BuildCacheKey() 方法將緩存值類型作為參數(shù)參與生成緩存鍵,此時(shí) Expire() 方法則同樣需要了。測(cè)試用例:

1 [TestClass] 2 public class HttpContextCacheProviderTest { 3 [TestInitialize] 4 public void Initialize() { 5 HttpContext.Current = new HttpContext(new HttpRequest(null, "http://localhost", null), new HttpResponse(null)); 6 } 7 8 [TestMethod] 9 public void NullValue() { 10 var key = "key-null"; 11 HttpContext.Current.Items.Add(key, null); 12 Assert.IsTrue(HttpContext.Current.Items.Contains(key)); 13 Assert.IsNull(HttpContext.Current.Items[key]); 14 } 15 16 [TestMethod] 17 public void ValueType() { 18 var key = "key-guid"; 19 ICacheProvider cache = new HttpContextCacheProvider(); 20 var id1 = Guid.NewGuid(); 21 var id2 = cache.GetOrCreate(key, () => id1); 22 Assert.AreEqual(id1, id2); 23 24 cache.Expire(key); 25 Guid id3; 26 var exist = cache.TryGet(key, out id3); 27 Assert.IsFalse(exist); 28 Assert.AreNotEqual(id1, id3); 29 Assert.AreEqual(id3, Guid.Empty); 30 } 31 } View Code

引用類型測(cè)試用例忽略。


2.1 多線程

異步等情況下,HttpContext.Current并非無處不在,故異步等情況下 HttpContextCacheProvider 的使用可能拋出空引用異常,需要被處理,對(duì)此園友有過思考 ,這里貼上A大的方案 ,有需求的讀者請(qǐng)按圖索驥。

3.? 進(jìn)程級(jí)別緩存

HttpRuntime.Cache 定義在 System.Web.dll 中,System.Web 命名空間下,實(shí)際上是可以使用在非 Asp.Net 應(yīng)用里的;另外 HttpContext 對(duì)象包含一個(gè) Cache 屬性,它們的關(guān)系可以閱讀 HttpContext.Cache 和 HttpRuntime.Cache;

HttpRuntime.Cache 為 System.Web.Caching.Cache 類型,支持滑動(dòng)/絕對(duì)時(shí)間過期策略、支持緩存優(yōu)先級(jí)、緩存更新/過期回調(diào)、基于文件的緩存依賴項(xiàng)等,功能十分強(qiáng)大,這里借用少數(shù)特性來實(shí)現(xiàn)進(jìn)程級(jí)別緩存,更多文檔請(qǐng)自行檢索。

從 ICacheProvider 定義 IHttpRuntimeCacheProvider,添加相對(duì)過期與絕對(duì)過期、添加批量的緩存過期接口 ExpireAll();

1 public interface IHttpRuntimeCacheProvider : ICacheProvider { 2 T GetOrCreate<T>(String key, Func<T> function, TimeSpan slidingExpiration); 3 T GetOrCreate<T>(String key, Func<T> function, DateTime absoluteExpiration); 4 void Overwrite<T>(String key, T value, TimeSpan slidingExpiration); 5 void Overwrite<T>(String key, T value, DateTime absoluteExpiration); 6 void ExpireAll(); 7 }

System.Web.Caching.Cache 只繼承 IEnumerable,內(nèi)部使用 DictionaryEntry 存儲(chǔ)Object-Object 鍵值對(duì),但 HttpRuntime.Cache 只授受字符串類型緩存鍵及非空緩存值,關(guān)于空引用緩存值的問題,我們?cè)?.2中討論;

故 TryGet() 與 HttpContextCacheProvider.TryGet() 具有顯著差異,前者需要拿出值來進(jìn)行非空判斷,后者則是使用 IDictionary.Contains() 方法;

除了 TryGet() 方法與過期過期參數(shù)外的差異外,接口實(shí)現(xiàn)與 HttpContextCacheProvider 類似;

1 public class HttpRuntimeCacheProvider : IHttpRuntimeCacheProvider { 2 private static readonly Object _sync = new Object(); 3 4 protected virtual String BuildCacheKey(String key) { 5 return String.Concat("HttpRuntimeCacheProvider_", key); 6 } 7 8 public Boolean TryGet<T>(String key, out T value) { 9 key = BuildCacheKey(key); 10 Boolean exist = false; 11 Object entry = HttpRuntime.Cache.Get(key); 12 if (entry != null) { 13 exist = true; 14 if (!(entry is T)) { 15 throw new InvalidOperationException(String.Format("緩存項(xiàng)[{0}]類型錯(cuò)誤, {1} or {2} ?", 16 key, entry.GetType().FullName, typeof(T).FullName)); 17 } 18 value = (T)entry; 19 } 20 else { 21 value = default(T); 22 } 23 return exist; 24 } 25 26 public T GetOrCreate<T>(String key, Func<String, T> factory) { 27 T result; 28 if (TryGet<T>(key, out result)) { 29 return result; 30 } 31 result = factory(key); 32 Overwrite(key, result); 33 return result; 34 } 35 36 public T GetOrCreate<T>(String key, Func<T> function) { 37 T result; 38 if (TryGet<T>(key, out result)) { 39 return result; 40 } 41 result = function(); 42 Overwrite(key, result); 43 return result; 44 } 45 46 47 public T GetOrCreate<T>(String key, Func<T> function, TimeSpan slidingExpiration) { 48 T result; 49 if (TryGet<T>(key, out result)) { 50 return result; 51 } 52 result = function(); 53 Overwrite(key, result, slidingExpiration); 54 return result; 55 } 56 57 public T GetOrCreate<T>(String key, Func<T> function, DateTime absoluteExpiration) { 58 T result; 59 if (TryGet<T>(key, out result)) { 60 return result; 61 } 62 result = function(); 63 Overwrite(key, result, absoluteExpiration); 64 return result; 65 } 66 67 public void Overwrite<T>(String key, T value) { 68 HttpRuntime.Cache.Insert(BuildCacheKey(key), value); 69 } 70 71 //slidingExpiration 時(shí)間內(nèi)無訪問則過期 72 public void Overwrite<T>(String key, T value, TimeSpan slidingExpiration) { 73 HttpRuntime.Cache.Insert(BuildCacheKey(key), value, null, 74 Cache.NoAbsoluteExpiration, slidingExpiration); 75 } 76 77 //absoluteExpiration 絕對(duì)時(shí)間過期 78 public void Overwrite<T>(String key, T value, DateTime absoluteExpiration) { 79 HttpRuntime.Cache.Insert(BuildCacheKey(key), value, null, 80 absoluteExpiration, Cache.NoSlidingExpiration); 81 } 82 83 public void Expire(String key) { 84 HttpRuntime.Cache.Remove(BuildCacheKey(key)); 85 } 86 87 public void ExpireAll() { 88 lock (_sync) { 89 var entries = HttpRuntime.Cache.OfType<DictionaryEntry>() 90 .Where(entry => (entry.Key is String) && ((String)entry.Key).StartsWith("HttpRuntimeCacheProvider_")); 91 foreach (var entry in entries) { 92 HttpRuntime.Cache.Remove((String)entry.Key); 93 } 94 } 95 } 96 }

測(cè)試用例與 HttpContextCacheProviderTest 類似,這里貼出緩存過期的測(cè)試:??

1 public class HttpRuntimeCacheProviderTest { 2 [TestMethod] 3 public void GetOrCreateWithAbsoluteExpirationTest() { 4 var key = Guid.NewGuid().ToString(); 5 var val = Guid.NewGuid(); 6 7 IHttpRuntimeCacheProvider cacheProvider = new HttpRuntimeCacheProvider(); 8 var result = cacheProvider.GetOrCreate<Guid>(key, () => val, DateTime.UtcNow.AddSeconds(2D)); 9 Assert.AreEqual(result, val); 10 11 var exist = cacheProvider.TryGet<Guid>(key, out val); 12 Assert.IsTrue(exist); 13 Assert.AreEqual(result, val); 14 15 Thread.Sleep(2000); 16 exist = cacheProvider.TryGet<Guid>(key, out val); 17 Assert.IsFalse(exist); 18 Assert.AreEqual(val, Guid.Empty); 19 } 20 21 [TestMethod] 22 public void ExpireAllTest() { 23 var key = Guid.NewGuid().ToString(); 24 var val = Guid.NewGuid(); 25 26 IHttpRuntimeCacheProvider cacheProvider = new HttpRuntimeCacheProvider(); 27 var result = cacheProvider.GetOrCreate<Guid>(key, () => val); 28 Assert.AreEqual(result, val); 29 30 cacheProvider.ExpireAll(); 31 Guid val2; 32 var exist = cacheProvider.TryGet<Guid>(key, out val2); 33 Assert.IsFalse(exist); 34 Assert.AreEqual(val2, Guid.Empty); 35 } 36 } View Code

3.1 分區(qū)與計(jì)數(shù)

緩存分區(qū)是常見需求,緩存用戶A、用戶B的認(rèn)證信息可以拿用戶標(biāo)識(shí)作為緩存鍵,但每個(gè)用戶分別有一整套包含授權(quán)的其他數(shù)據(jù)時(shí),為創(chuàng)建以用戶分區(qū)的緩存應(yīng)該是更好的選擇;
常規(guī)的想法是為緩存添加類似 `Region` 或 `Partition`的參數(shù),個(gè)人覺得這不是很好的實(shí)踐,因?yàn)榻涌诒恍薷?#xff0c;同時(shí)過多的參數(shù)非常讓人困惑;

讀者可能對(duì)前文中 BuildCacheKey() 方法被 protected virtual 修飾覺得很奇怪,是的,個(gè)人覺得定義新的接口,配合從緩存Key的生成算法作文章來分區(qū)貌似比較巧妙,也迎合依賴注冊(cè)被被廣泛使用的現(xiàn)狀;

分區(qū)的進(jìn)程級(jí)別緩存定義,只需多出一個(gè)屬性:

1 public interface IHttpRuntimeRegionCacheProvider : IHttpRuntimeCacheProvider { 2 String Region { get; } 3 }

分區(qū)的緩存實(shí)現(xiàn),先為 IHttpRuntimeCacheProvider 添加計(jì)數(shù),然后重構(gòu)HttpRuntimeCacheProvider,提取出過濾算法,接著重寫 BuildCacheKey() 方法的實(shí)現(xiàn),使不同分區(qū)的生成不同的緩存鍵,緩存項(xiàng)操作方法無須修改;??

1 public interface IHttpRuntimeCacheProvider : ICacheProvider { 2 ... 3 Int32 Count { get; } 4 } 5 6 public class HttpRuntimeCacheProvider : IHttpRuntimeCacheProvider { 7 ... 8 protected virtual Boolean Hit(DictionaryEntry entry) { 9 return (entry.Key is String) && ((String)entry.Key).StartsWith("HttpRuntimeCacheProvider_"); 10 } 11 12 public void ExpireAll() { 13 lock (_sync) { 14 var entries = HttpRuntime.Cache.OfType<DictionaryEntry>().Where(Hit); 15 foreach (var entry in entries) { 16 HttpRuntime.Cache.Remove((String)entry.Key); 17 } 18 } 19 } 20 21 public Int32 Count { 22 get { 23 lock (_sync) { 24 return HttpRuntime.Cache.OfType<DictionaryEntry>().Where(Hit).Count(); 25 } 26 } 27 } 28 } 29 30 public class HttpRuntimeRegionCacheProvider : HttpRuntimeCacheProvider, IHttpRuntimeRegionCacheProvider { 31 private String _prefix; 32 public virtual String Region { get; private set; } 33 34 private String GetPrifix() { 35 if (_prefix == null) { 36 _prefix = String.Concat("HttpRuntimeRegionCacheProvider_", Region, "_"); 37 } 38 return _prefix; 39 } 40 41 public HttpRuntimeRegionCacheProvider(String region) { 42 Region = region; 43 } 44 45 protected override String BuildCacheKey(String key) { 46 //Region 為空將被當(dāng)作 String.Empty 處理 47 return String.Concat(GetPrifix(), base.BuildCacheKey(key)); 48 } 49 50 protected override Boolean Hit(DictionaryEntry entry) { 51 return (entry.Key is String) && ((String)entry.Key).StartsWith(GetPrifix()); 52 } 53 }

測(cè)試用例示例了兩個(gè)分區(qū)緩存對(duì)相同 key 的操作:?

1 [TestClass] 2 public class HttpRuntimeRegionCacheProviderTest { 3 [TestMethod] 4 public void ValueType() { 5 var key = "key-guid"; 6 IHttpRuntimeCacheProvider cache1 = new HttpRuntimeRegionCacheProvider("Region1"); 7 var id1 = cache1.GetOrCreate(key, Guid.NewGuid); 8 9 IHttpRuntimeCacheProvider cache2 = new HttpRuntimeRegionCacheProvider("Region2"); 10 var id2 = cache2.GetOrCreate(key, Guid.NewGuid); 11 Assert.AreNotEqual(id1, id2); 12 13 cache1.ExpireAll(); 14 Assert.AreEqual(cache1.Count, 0); 15 Assert.AreEqual(cache2.Count, 1); 16 } 17 } View Code

至此一個(gè)基本的緩存模塊已經(jīng)完成;

3.2 可空緩存值

前文提及過,HttpRuntime.Cache 不授受空引用作為緩存值,與 HttpContext.Current.Items表現(xiàn)不同,另一方面實(shí)際需求中,空值作為字典的值仍然是有意義,此處給出一個(gè)支持空緩存值的實(shí)現(xiàn);

HttpRuntime.Cache 斷然是不能把 null 存入的,查看 HttpRuntimeCacheProvider.TryGet() 方法,可知 HttpRuntime.Cache.Get() 獲取的總是 Object 類型,思路可以這樣展開:

1) 添加緩存時(shí)進(jìn)行判斷,如果非空,常規(guī)處理,否則把用一個(gè)特定的自定義對(duì)象存入;
2) 取出緩存時(shí)進(jìn)行判斷,如果為特定的自定義對(duì)象,返回 null;

為 HttpRuntimeCacheProvider 的構(gòu)造函數(shù)添加可選參數(shù),TryGet() 加入 null 判斷邏輯;添加方法 BuildCacheEntry(),替換空的緩存值為 _nullEntry,其他方法不變;??

1 public class HttpRuntimeCacheProvider : IHttpRuntimeCacheProvider { 2 private static readonly Object _sync = new Object(); 3 private static readonly Object _nullEntry = new Object(); 4 private Boolean _supportNull; 5 6 public HttpRuntimeCacheProvider(Boolean supportNull = false) { 7 _supportNull = supportNull; 8 } 9 10 protected virtual String BuildCacheKey(String key) { 11 return String.Concat("HttpRuntimeCacheProvider_", key); 12 } 13 14 protected virtual Object BuildCacheEntry<T>(T value) { 15 Object entry = value; 16 if (value == null) { 17 if (_supportNull) { 18 entry = _nullEntry; 19 } 20 else { 21 throw new InvalidOperationException(String.Format("Null cache item not supported, try ctor with paramter 'supportNull = true' ")); 22 } 23 } 24 return entry; 25 } 26 27 public Boolean TryGet<T>(String key, out T value) { 28 Object entry = HttpRuntime.Cache.Get(BuildCacheKey(key)); 29 Boolean exist = false; 30 if (entry != null) { 31 exist = true; 32 if (!(entry is T)) { 33 if (_supportNull && !(entry == _nullEntry)) { 34 throw new InvalidOperationException(String.Format("緩存項(xiàng)`[{0}]`類型錯(cuò)誤, {1} or {2} ?", 35 key, entry.GetType().FullName, typeof(T).FullName)); 36 } 37 value = (T)((Object)null); 38 } 39 else { 40 value = (T)entry; 41 } 42 } 43 else { 44 value = default(T); 45 } 46 return exist; 47 } 48 49 public T GetOrCreate<T>(String key, Func<String, T> factory) { 50 T value; 51 if (TryGet<T>(key, out value)) { 52 return value; 53 } 54 value = factory(key); 55 Overwrite(key, value); 56 return value; 57 } 58 59 public T GetOrCreate<T>(String key, Func<T> function) { 60 T value; 61 if (TryGet<T>(key, out value)) { 62 return value; 63 } 64 value = function(); 65 Overwrite(key, value); 66 return value; 67 } 68 69 public T GetOrCreate<T>(String key, Func<T> function, TimeSpan slidingExpiration) { 70 T value; 71 if (TryGet<T>(key, out value)) { 72 return value; 73 } 74 value = function(); 75 Overwrite(key, value, slidingExpiration); 76 return value; 77 } 78 79 public T GetOrCreate<T>(String key, Func<T> function, DateTime absoluteExpiration) { 80 T value; 81 if (TryGet<T>(key, out value)) { 82 return value; 83 } 84 value = function(); 85 Overwrite(key, value, absoluteExpiration); 86 return value; 87 } 88 89 public void Overwrite<T>(String key, T value) { 90 HttpRuntime.Cache.Insert(BuildCacheKey(key), BuildCacheEntry<T>(value)); 91 } 92 93 //slidingExpiration 時(shí)間內(nèi)無訪問則過期 94 public void Overwrite<T>(String key, T value, TimeSpan slidingExpiration) { 95 HttpRuntime.Cache.Insert(BuildCacheKey(key), BuildCacheEntry<T>(value), null, 96 Cache.NoAbsoluteExpiration, slidingExpiration); 97 } 98 99 //absoluteExpiration 時(shí)過期 100 public void Overwrite<T>(String key, T value, DateTime absoluteExpiration) { 101 HttpRuntime.Cache.Insert(BuildCacheKey(key), BuildCacheEntry<T>(value), null, 102 absoluteExpiration, Cache.NoSlidingExpiration); 103 } 104 105 public void Expire(String key) { 106 HttpRuntime.Cache.Remove(BuildCacheKey(key)); 107 } 108 109 protected virtual Boolean Hit(DictionaryEntry entry) { 110 return (entry.Key is String) && ((String)entry.Key).StartsWith("HttpRuntimeCacheProvider_"); 111 } 112 113 public void ExpireAll() { 114 lock (_sync) { 115 var entries = HttpRuntime.Cache.OfType<DictionaryEntry>().Where(Hit); 116 foreach (var entry in entries) { 117 HttpRuntime.Cache.Remove((String)entry.Key); 118 } 119 } 120 } 121 122 public Int32 Count { 123 get { 124 lock (_sync) { 125 return HttpRuntime.Cache.OfType<DictionaryEntry>().Where(Hit).Count(); 126 } 127 } 128 } 129 }

然后是分區(qū)緩存需要修改構(gòu)造函數(shù):

1 public HttpRuntimeRegionCacheProvider(String region) 2 : base(false) { 3 Region = region; 4 } 5 6 public HttpRuntimeRegionCacheProvider(String region, Boolean supportNull) 7 : base(supportNull) { 8 Region = region; 9 } 10 ... 11 }

測(cè)試用例:?

1 [TestClass] 2 public class HttpRuntimeCacheProviderTest { 3 [TestMethod] 4 public void NullCacheErrorTest() { 5 var key = "key-null"; 6 Person person = null; 7 IHttpRuntimeCacheProvider cacheProvider = new HttpRuntimeCacheProvider(false); 8 try { 9 cacheProvider.GetOrCreate<Person>(key, () => person); //error 10 Assert.Fail(); 11 } 12 catch (Exception ex) { 13 Assert.IsTrue(ex is InvalidOperationException); 14 } 15 16 Person person2; 17 var exist = cacheProvider.TryGet(key, out person2); 18 Assert.IsFalse(exist); 19 Assert.AreEqual(person2, null); 20 } 21 22 [TestMethod] 23 public void NullableCacheTest() { 24 var key = "key-nullable"; 25 Person person = null; 26 IHttpRuntimeCacheProvider cacheProvider = new HttpRuntimeCacheProvider(true); 27 cacheProvider.GetOrCreate<Person>(key, () => person); 28 Person person2; 29 var exist = cacheProvider.TryGet(key, out person2); 30 Assert.IsTrue(exist); 31 Assert.AreEqual(person2, null); 32 } 33 34 class Person { 35 public Int32 Id { get; set; } 36 public String Name { get; set; } 37 } 38 } View Code

3.3 封裝與集成

多數(shù)情況下我們不需要暴露實(shí)現(xiàn)和手動(dòng)創(chuàng)建上文所提各種 CacheProvider,實(shí)踐中它們被 internal 修飾,再配合工廠類使用:??

1 public static class CacheProviderFacotry { 2 public static ICacheProvider GetHttpContextCache() { 3 return new HttpContextCacheProvider(); 4 } 5 6 public static IHttpRuntimeCacheProvider GetHttpRuntimeCache(Boolean supportNull = false) { 7 return new HttpRuntimeCacheProvider(supportNull); 8 } 9 10 public static IHttpRuntimeRegionCacheProvider GetHttpRuntimeRegionCache(String region, Boolean supportNull = false) { 11 return new HttpRuntimeRegionCacheProvider(region, supportNull); 12 } 13 14 public static IHttpRuntimeRegionCacheProvider Region(this IHttpRuntimeCacheProvider runtimeCacheProvider, String region, Boolean supportNull = false) { 15 return GetHttpRuntimeRegionCache(region, supportNull); 16 } 17 }

然后在依賴注入中的聲明如下,這里是 Autofac 下的組件注冊(cè):?????

1 ... 2 //請(qǐng)求級(jí)別緩存, 使用 HttpContext.Current.Items 作為容器 3 builder.Register(ctx => CacheProviderFacotry.GetHttpContextCache()).As<ICacheProvider>().InstancePerLifetimeScope(); 4 //進(jìn)程級(jí)別緩存, 使用 HttpRuntime.Cache 作為容器 5 builder.RegisterInstance(CacheProviderFacotry.GetHttpRuntimeCache()).As<IRuntimeCacheProvider>().ExternallyOwned(); 6 //進(jìn)程級(jí)別且隔離的緩存, 若出于key算法唯一考慮而希望加入上下文件信息, 則仍然需要 CacheModule 類的實(shí)現(xiàn) 7 builder.Register(ctx => CacheProviderFacotry.GetHttpRuntimeRegionCache(/*... 分區(qū)依據(jù) ...*/)) 8 .As<IRuntimeRegionCacheProvider>().InstancePerLifetimeScope(); 9 ...

4. 小結(jié)

本文簡(jiǎn)單探討了一個(gè)具有線程安全、分區(qū)、過期特性緩存模塊的實(shí)現(xiàn)過程,只使用了HttpRuntime.Cache的有限特性,有更多需求的同學(xué)可以自行擴(kuò)展;見解有限,謬誤之處還請(qǐng)園友指正。

園友Jusfr 原創(chuàng),轉(zhuǎn)載請(qǐng)注明來自博客園? 。

注意后續(xù)代碼及改進(jìn)見后后文及github,文章上的并沒有更新。

轉(zhuǎn)載于:https://www.cnblogs.com/Jusfr/p/4150954.html

總結(jié)

以上是生活随笔為你收集整理的一步步实现一个基本的缓存模块的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。