一步步實現一個基本的緩存模塊


一步步實現一個基本的緩存模塊

注意后續代碼及改進見后后文及github,文章上的並沒有更新。

    1. 前言
    2.  請求級別緩存
    2.1 多線程
    3.  進程級別緩存
    3.1 分區與計數
    3.2 可空緩存值
    3.3 封裝與集成
    4.  小結

1. 前言

  • 面向讀者:初、中級用戶;
  • 涉及知識:HttpContext、HttpRuime.Cache、DictionaryEntry、Unit Test等;
  • 文章目的:這里的內容不會涉及 Memcached、Redies 等進程外緩存的使用,只針對包含WEB應用的常見場景,實現一個具有線程安全、分區、過期特性的緩存模塊,略微提及DI等內容。
  • jusfr 原創,轉載請注明來自博客園


2.  請求級別緩存

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

基本接口 ICacheProvider,請求級別的緩存從它定義,考慮到請求級別緩存的運用場景有限,故只定義有限特性;

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 定義,存儲 Object-Object 鍵值對,出於便利與直觀,ICacheProvider 只接受String類型緩存鍵,故HttpContextCacheProvider內部使用 BuildCacheKey(String key) 方法生成真正緩存鍵以避免鍵值重復;

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

 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("緩存項`[{0}]`類型錯誤, {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> 委托的運用,合並查詢、判斷和添加緩存項的操作以簡化接口調用;如果用戶期望不同類型緩存值可以存儲到相同的 key 上,則需要重新定義 BuildCacheKey() 方法將緩存值類型作為參數參與生成緩存鍵,此時 Expire() 方法則同樣需要了。測試用例:

 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

引用類型測試用例忽略。


2.1 多線程

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

3.  進程級別緩存

HttpRuntime.Cache 定義在 System.Web.dll 中,System.Web 命名空間下,實際上是可以使用在非 Asp.Net 應用里的;另外 HttpContext 對象包含一個 Cache 屬性,它們的關系可以閱讀 HttpContext.Cache 和 HttpRuntime.Cache

HttpRuntime.Cache 為 System.Web.Caching.Cache 類型,支持滑動/絕對時間過期策略、支持緩存優先級、緩存更新/過期回調、基於文件的緩存依賴項等,功能十分強大,這里借用少數特性來實現進程級別緩存,更多文檔請自行檢索。

從 ICacheProvider 定義 IHttpRuntimeCacheProvider,添加相對過期與絕對過期、添加批量的緩存過期接口 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,內部使用 DictionaryEntry 存儲Object-Object 鍵值對,但 HttpRuntime.Cache 只授受字符串類型緩存鍵及非空緩存值,關於空引用緩存值的問題,我們在3.2中討論;

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

除了 TryGet() 方法與過期過期參數外的差異外,接口實現與 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("緩存項[{0}]類型錯誤, {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 時間內無訪問則過期
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 絕對時間過期
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     }

測試用例與 HttpContextCacheProviderTest 類似,這里貼出緩存過期的測試:  

 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 分區與計數

緩存分區是常見需求,緩存用戶A、用戶B的認證信息可以拿用戶標識作為緩存鍵,但每個用戶分別有一整套包含授權的其他數據時,為創建以用戶分區的緩存應該是更好的選擇;
常規的想法是為緩存添加類似 `Region` 或 `Partition`的參數,個人覺得這不是很好的實踐,因為接口被修改,同時過多的參數非常讓人困惑;

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

分區的進程級別緩存定義,只需多出一個屬性:

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

分區的緩存實現,先為 IHttpRuntimeCacheProvider 添加計數,然后重構HttpRuntimeCacheProvider,提取出過濾算法,接着重寫 BuildCacheKey() 方法的實現,使不同分區的生成不同的緩存鍵,緩存項操作方法無須修改;  

 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 為空將被當作  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     }

測試用例示例了兩個分區緩存對相同 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

至此一個基本的緩存模塊已經完成;

3.2 可空緩存值

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

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

1) 添加緩存時進行判斷,如果非空,常規處理,否則把用一個特定的自定義對象存入;
2) 取出緩存時進行判斷,如果為特定的自定義對象,返回 null;

為 HttpRuntimeCacheProvider 的構造函數添加可選參數,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("緩存項`[{0}]`類型錯誤, {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 時間內無訪問則過期
 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 時過期
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     }

然后是分區緩存需要修改構造函數:

 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     }

測試用例: 

 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 封裝與集成

多數情況下我們不需要暴露實現和手動創建上文所提各種 CacheProvider,實踐中它們被 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 下的組件注冊:     

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

4. 小結

本文簡單探討了一個具有線程安全、分區、過期特性緩存模塊的實現過程,只使用了HttpRuntime.Cache的有限特性,有更多需求的同學可以自行擴展;見解有限,謬誤之處還請園友指正。

園友Jusfr 原創,轉載請注明來自博客園  。

注意后續代碼及改進見后后文及github,文章上的並沒有更新。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM