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

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

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     }

引用类型测试用例忽略。

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     }

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     }

至此一个基本的缓存模块已经完成;

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     }

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 原创,转载请注明来自博客园  。

时间: 2024-10-10 14:25:04

一步步实现一个基本的缓存模块的相关文章

实现一个简单的缓存模块&#183;续, 添加Memcached调用实现

jusfr 原创,转载请注明来自博客园. 在之前的实现中,我们初步实现了一个缓存模块:包含一个基于Http请求的缓存实现,一个基于HttpRuntime.Cache进程级的缓存实现,但观察代码,会发现如下问题: 1. 有部分逻辑如 Boolean TryGet<T>(String key, out T entry) 的实现有重复现象,Do not repeat yourself 提醒我们这里可以改进:2. 分区特性虽然实现了,但是使用了额外的接口承载,而大多数运用中,调用者无论是操作缓存项的创

写了一个ios缓存模块,非常方便好用,欢迎帮忙加星~

写了一个ios磁盘缓存的模块,基于ISDishCache,添加文件校验,相同的文件只会缓存一次,采用了引用计数的方式对文件进行淘汰,之前的按文件访问时间进行淘汰会对经常使用的文件造成误删,使用很方便,一般用到就两个方法cacheObejct和objectForKey,将key和要缓存的文件放进去就可以了,地址https://github.com/abbothzhang/ZHCache,欢迎使用,欢迎帮忙加星~~~

nginx三 之缓存模块

友情提示: 缓存模块是在动静分离的环境基础上搭建,动静分离可以参考http://www.cnblogs.com/dahuandan/p/6759212.html 介绍 提高网站响应速度是web应用不容忽视的目标,在之前动静分离的基础上,我们已经降低了后端服务器压力,提高了处理请求的性能,但是用户请求的静态资源是从硬盘读取,相比内存的性能还有很大的提高: Nginx自带的缓存模块可以把静态资源缓存到内存中,提高了用户请求静态资源的速度,并且nginx自带缓存模块配置简单,使用灵活,搭配第三方插件可

缓存模块设计

NET 缓存模块设计 上一篇谈了我对缓存的概念,框架上的理解和看法,这篇承接上篇讲讲我自己的缓存模块设计实践. 基本的缓存模块设计 最基础的缓存模块一定有一个统一的CacheHelper,如下: public interface ICacheHelper { T Get<T>(string key); void Set<T>(string key, T value); void Remove(string key); } 然后业务层是这样调用的 public User Get(in

如何编写一个有效的缓存

缓存作为计算机历史上最重要的发明之一,对计算机历史起到了举足轻重的作用,因为缓存可以协调两个速度不一致的组件之间的并行运作.内存作为CPU和非易失性存储介质之间的缓存,避免CPU每次读取指令,读取数据都去速度缓慢的硬盘读取.快速缓存作为内存和CPU之间的缓存进一步提高了CPU的效率,现在大部分CPU都支持指令预取,CPU会预测后面要执行的指令预取到快速缓存中.而我们平时也直接或间接地会用到缓存技术,那如果要自己实现一个线程安全的缓存,要注意哪些问题呢?我们一步步来探讨这个问题. 假设我们提供一个

手把手教你编写一个简单的PHP模块形态的后门

看到Freebuf 小编发表的用这个隐藏于PHP模块中的rootkit,就能持久接管服务器文章,很感兴趣,苦无作者没留下PoC,自己研究一番,有了此文 0×00. 引言 PHP是一个非常流行的web server端的script语言.目前很多web应用程序都基于php语言实现.由于php是个开源软件并易于扩展,所以我们可以通过编写一个PHP模块(module 或者叫扩展 extension)来实现一个Backdoor. 本文就简单介下如何一步步编写一个简单的php 动态扩展后门. 0×01. p

[.NET] 一步步打造一个简单的 MVC 电商网站 - BooksStore(二)

一步步打造一个简单的 MVC 电商网站 - BooksStore(二) 本系列的 GitHub地址:https://github.com/liqingwen2015/Wen.BooksStore 前:<一步步打造一个简单的 MVC 电商网站 - BooksStore(一)> 简介 上一次我们尝试了:创建项目架构.创建域模型实体.创建单元测试.创建控制器与视图.创建分页和加入样式,而这一节我们会完成两个功能,分类导航与购物车. 主要功能与知识点如下: 分类.产品浏览.购物车.结算.CRUD(增删

从头开始编写一个Orchard网上商店模块(6) - 创建购物车服务和控制器

原文地址: http://skywalkersoftwaredevelopment.net/blog/writing-an-orchard-webshop-module-from-scratch-part-6创建购物车服务和控制器 这是从头开始编写一个新的Orchard模块的教程的第6篇.对于本教程的概述,请参阅介绍. 在本篇,我们将使我们的用户可以添加商品到他们的购物车.要创建这样的功能,我们需要: 一个“添加到购物车”按钮,要被添加我们的产品目录上,将产品添加到购物车 某种购物车服务,以存储

IOS编程 图片缓存模块设计

手机客户端为什么会留存下来?而不是被一味的Wap替代掉?因为手机客户端有Wap无可替代的优势,就是自身较强的计算能力. 手机中不可避免的一环:图片缓存,在软件的整个运行过程中显得尤为重要. 先简单说一下图片缓存的作用: 提高响应速度 减少网络流量 提高用户体验 提高响应速度:因为图片一旦缓存在本地之后,那么本地IO数据的读取,远比网络中得IO读取效率要高的多.所以可以提高响应速度 减少网络流量:一张图片在某些情况下,只加载一次,之后便不会重新加载,减少了网络流量.减少流量肯定是必然的.介于国内的