Fork me on GitHub

.NET4.0的可扩展缓存系统

.NET Framework中,叫做System.Runtime.Caching,这不仅是个缓存库,还是个框架,可以在上面开发自己的库。ObjectCache定义了所有缓存都要实现的通用操作。与之搭配的是个内存缓存实现,叫做MemoryCache。这个缓存系统的结构如下:

image

上图大家可以看出来对应那些产品了吗?

下面我给大家介绍一个实现这样一个架构的代码示例,代码的核心就是ObjectCache:

定义一个抽象的Provider接口:

   1: public interface ICacheBuilder
   2: {
   3:     ObjectCache GetInstance();
   4:     string DefaultRegionName { get; }
   5: }

 

In-memory提供者的实现使用MemoryCache:

   1: public class MemoryCacheBuilder : ICacheBuilder
   2: {
   3:     public MemoryCacheBuilder() { }
   4:  
   5:     public ObjectCache GetInstance()
   6:     {
   7:         return MemoryCache.Default;
   8:     }
   9:  
  10:     public string DefaultRegionName
  11:     {
  12:         get { return null; }
  13:     }
  14: }

分布式缓存提供者Memcached:

   1: public class MemcachedCache : ObjectCache, ICacheBuilder
   2: {
   3:     private long _lDefaultExpireTime = 3600; // default Expire Time
   4:     private MemcachedClient _client = null;
   5:     #region ICache Members
   6:  
   7:     public MemcachedCache()
   8:     {
   9:         this._client = MemcachedClientService.Instance.Client;
  10:     }
  11:  
  12:     public override void Set(string key, object value, System.DateTimeOffset absoluteExpiration, string regionName = null)
  13:     {
  14:         Enforce.NotNull(key, "key");
  15:         CacheItem item = new CacheItem(key, value, regionName);
  16:         CacheItemPolicy policy = new CacheItemPolicy();
  17:         policy.AbsoluteExpiration = absoluteExpiration;
  18:  
  19:         Set(item, policy);
  20:     }
  21:  
  22:     public override void Set(CacheItem item, CacheItemPolicy policy)
  23:     {
  24:         if (item == null || item.Value == null)
  25:             return;
  26:  
  27:         item.Key = item.Key.ToLower();
  28:  
  29:         if (policy != null && policy.ChangeMonitors != null && policy.ChangeMonitors.Count > 0)
  30:             throw new NotSupportedException("Change monitors are not supported");
  31:  
  32:         // max timeout in scaleout = 65535
  33:         TimeSpan expire = (policy.AbsoluteExpiration.Equals(null)) ?
  34:                             policy.SlidingExpiration :
  35:                             (policy.AbsoluteExpiration - DateTimeOffset.Now);
  36:  
  37:         double timeout = expire.TotalMinutes;
  38:         if (timeout > 65535)
  39:             timeout = 65535;
  40:         else if (timeout > 0 && timeout < 1)
  41:             timeout = 1;
  42:  
  43:         this._client.Store(Enyim.Caching.Memcached.StoreMode.Set, item.Key.ToString(), item.Value);
  44:  
  45:     }
  46:  
  47:     public override object this[string key]
  48:     {
  49:         get
  50:         {
  51:             return Get(key);
  52:         }
  53:         set
  54:         {
  55:             Set(key, value, null);
  56:         }
  57:     }
  58:  
  59:     public override object AddOrGetExisting(string key, object value, CacheItemPolicy policy, string regionName = null)
  60:     {
  61:         CacheItem item = GetCacheItem(key, regionName);
  62:         if (item == null)
  63:         {
  64:             Set(new CacheItem(key, value, regionName), policy);
  65:             return value;
  66:         }
  67:  
  68:         return item.Value;
  69:     }
  70:  
  71:     public override CacheItem AddOrGetExisting(CacheItem value, CacheItemPolicy policy)
  72:     {
  73:         CacheItem item = GetCacheItem(value.Key, value.RegionName);
  74:         if (item == null)
  75:         {
  76:             Set(value, policy);
  77:             return value;
  78:         }
  79:  
  80:         return item;
  81:     }
  82:  
  83:     public override object AddOrGetExisting(string key, object value, System.DateTimeOffset absoluteExpiration, string regionName = null)
  84:     {
  85:         CacheItem item = new CacheItem(key, value, regionName);
  86:         CacheItemPolicy policy = new CacheItemPolicy();
  87:         policy.AbsoluteExpiration = absoluteExpiration;
  88:  
  89:         return AddOrGetExisting(item, policy);
  90:     }
  91:  
  92:     public override bool Contains(string key, string regionName = null)
  93:     {
  94:         return false;
  95:     }
  96:  
  97:     public override CacheEntryChangeMonitor CreateCacheEntryChangeMonitor(System.Collections.Generic.IEnumerable<string> keys, string regionName = null)
  98:     {
  99:         throw new System.NotImplementedException();
 100:     }
 101:  
 102:     public override DefaultCacheCapabilities DefaultCacheCapabilities
 103:     {
 104:         get
 105:         {
 106:             return
 107:                 DefaultCacheCapabilities.OutOfProcessProvider |
 108:                 DefaultCacheCapabilities.AbsoluteExpirations |
 109:                 DefaultCacheCapabilities.SlidingExpirations |
 110:                 DefaultCacheCapabilities.CacheRegions;
 111:         }
 112:     }
 113:  
 114:     public override object Get(string key, string regionName = null)
 115:     {
 116:         key = key.ToLower();
 117:  
 118:         return this._client.Get(key);
 119:     }
 120:  
 121:     public override CacheItem GetCacheItem(string key, string regionName = null)
 122:     {
 123:         object value = Get(key, regionName);
 124:         if (value != null)
 125:             return new CacheItem(key, value, regionName);
 126:  
 127:         return null;
 128:     }
 129:  
 130:     public override long GetCount(string regionName = null)
 131:     {
 132:         return -1;
 133:     }
 134:  
 135:     protected override System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<string, object>> GetEnumerator()
 136:     {
 137:           throw new System.NotImplementedException();
 138:     }
 139:  
 140:     public override System.Collections.Generic.IDictionary<string, object> GetValues(System.Collections.Generic.IEnumerable<string> keys, string regionName = null)
 141:     {
 142:         throw new System.NotImplementedException();
 143:     }
 144:  
 145:     public override string Name
 146:     {
 147:         get { return "MemcachedProvider"; }
 148:     }
 149:  
 150:     public override object Remove(string key, string regionName = null)
 151:     {
 152:         key = key.ToLower();
 153:         return this._client.Remove(key);
 154:     }
 155:  
 156:     public override void Set(string key, object value, CacheItemPolicy policy, string regionName = null)
 157:     {
 158:         Set(new CacheItem(key, value, regionName), policy);
 159:     }
 160:  
 161:     #endregion
 162:  
 163:     #region ICacheBuilder Members
 164:  
 165:     public ObjectCache GetInstance()
 166:     {
 167:         return this;
 168:     }
 169:  
 170:     public string DefaultRegionName
 171:     {
 172:         get { throw new NotImplementedException(); }
 173:     }
 174:  
 175:     #endregion
 176: }

分布式缓存提供者Windows Server AppFabric Caching:

   1: public class AppFabricCacheProvider : ObjectCache, ICacheBuilder
   2: {
   3:     public static DataCache factory = null;
   4:     public static object syncObj = new object();
   5:  
   6:     public override object AddOrGetExisting(string key, object value, CacheItemPolicy policy, string regionName = null)
   7:     {
   8:         CacheItem item = GetCacheItem(key, regionName);
   9:         if (item == null)
  10:         {
  11:             Set(new CacheItem(key, value, regionName), policy);
  12:             return value;
  13:         }
  14:  
  15:         return item.Value;
  16:     }
  17:  
  18:     public override CacheItem AddOrGetExisting(CacheItem value, CacheItemPolicy policy)
  19:     {
  20:         CacheItem item = GetCacheItem(value.Key, value.RegionName);
  21:         if (item == null)
  22:         {
  23:             Set(value, policy);
  24:             return value;
  25:         }
  26:  
  27:         return item;
  28:     }
  29:  
  30:     public override object AddOrGetExisting(string key, object value, System.DateTimeOffset absoluteExpiration, string regionName = null)
  31:     {
  32:         CacheItem item = new CacheItem(key, value, regionName);
  33:         CacheItemPolicy policy = new CacheItemPolicy();
  34:         policy.AbsoluteExpiration = absoluteExpiration;
  35:  
  36:         return AddOrGetExisting(item, policy);
  37:     }
  38:  
  39:     public override bool Contains(string key, string regionName = null)
  40:     {
  41:         return Get(key, regionName) != null;
  42:     }
  43:  
  44:     public override CacheEntryChangeMonitor CreateCacheEntryChangeMonitor(System.Collections.Generic.IEnumerable<string> keys, string regionName = null)
  45:     {
  46:         throw new NotImplementedException();
  47:     }
  48:  
  49:     public override DefaultCacheCapabilities DefaultCacheCapabilities
  50:     {
  51:         get
  52:         {
  53:             return
  54:                 DefaultCacheCapabilities.OutOfProcessProvider |
  55:                 DefaultCacheCapabilities.AbsoluteExpirations |
  56:                 DefaultCacheCapabilities.SlidingExpirations |
  57:                 DefaultCacheCapabilities.CacheRegions;
  58:         }
  59:     }
  60:  
  61:     public override object Get(string key, string regionName = null)
  62:     {
  63:         key = key.ToLower();
  64:         CreateRegionIfNeeded();
  65:  
  66:         return (regionName == null) ?
  67:             CacheFactory.Get(key) :
  68:             CacheFactory.Get(key, regionName);
  69:     }
  70:  
  71:     public override CacheItem GetCacheItem(string key, string regionName = null)
  72:     {
  73:         object value = Get(key, regionName);
  74:         if (value != null)
  75:             return new CacheItem(key, value, regionName);
  76:  
  77:         return null;
  78:     }
  79:  
  80:     public override long GetCount(string regionName = null)
  81:     {
  82:         if (string.IsNullOrEmpty(regionName))
  83:             throw new NotSupportedException();
  84:  
  85:         return CacheFactory.GetObjectsInRegion(regionName).LongCount();
  86:     }
  87:  
  88:     protected override System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<string, object>> GetEnumerator()
  89:     {
  90:         throw new NotSupportedException();
  91:     }
  92:  
  93:     public override System.Collections.Generic.IDictionary<string, object> GetValues(System.Collections.Generic.IEnumerable<string> keys, string regionName = null)
  94:     {
  95:         if (string.IsNullOrEmpty(regionName))
  96:             throw new NotSupportedException();
  97:  
  98:         return CacheFactory.GetObjectsInRegion(regionName).ToDictionary(x => x.Key, x => x.Value);
  99:     }
 100:  
 101:     public override string Name
 102:     {
 103:         get { return "AppFabric"; }
 104:     }
 105:  
 106:     public override object Remove(string key, string regionName = null)
 107:     {
 108:         key = key.ToLower();
 109:         CreateRegionIfNeeded();
 110:  
 111:         return (regionName == null) ?
 112:             CacheFactory.Remove(key) :
 113:             CacheFactory.Remove(key, regionName);
 114:     }
 115:  
 116:     public override void Set(string key, object value, CacheItemPolicy policy, string regionName = null)
 117:     {
 118:         Set(new CacheItem(key, value, regionName), policy);
 119:     }
 120:  
 121:     public override void Set(CacheItem item, CacheItemPolicy policy)
 122:     {
 123:         if (item == null || item.Value == null)
 124:             return;
 125:  
 126:         if (policy != null && policy.ChangeMonitors != null && policy.ChangeMonitors.Count > 0)
 127:             throw new NotSupportedException("Change monitors are not supported");
 128:  
 129:         item.Key = item.Key.ToLower();
 130:         CreateRegionIfNeeded();
 131:  
 132:         TimeSpan expire = (policy.AbsoluteExpiration.Equals(null)) ?
 133:             policy.SlidingExpiration :
 134:             (policy.AbsoluteExpiration - DateTimeOffset.Now);
 135:  
 136:         if (string.IsNullOrEmpty(item.RegionName))
 137:             CacheFactory.Put(item.Key, item.Value, expire);
 138:         else
 139:             CacheFactory.Put(item.Key, item.Value, expire, item.RegionName);
 140:     }
 141:  
 142:     private static DataCache CacheFactory
 143:     {
 144:         get
 145:         {
 146:             if (factory == null)
 147:             {
 148:                 lock (syncObj)
 149:                 {
 150:                     if (factory == null)
 151:                     {
 152:                         DataCacheFactory cacheFactory = new DataCacheFactory();
 153:                         factory = cacheFactory.GetDefaultCache();
 154:                     }
 155:                 }
 156:             }
 157:  
 158:             return factory;
 159:         }
 160:     }
 161:  
 162:     private void CreateRegionIfNeeded()
 163:     {
 164:         try
 165:         {
 166:             CacheFactory.CreateRegion(DefaultRegionName);
 167:         }
 168:         catch (DataCacheException ex)
 169:         {
 170:             if (!ex.ErrorCode.Equals(DataCacheErrorCode.RegionAlreadyExists))
 171:                 throw ex;
 172:         }
 173:     }
 174:  
 175:     public override void Set(string key, object value, System.DateTimeOffset absoluteExpiration, string regionName = null)
 176:     {
 177:         CacheItem item = new CacheItem(key, value, regionName);
 178:         CacheItemPolicy policy = new CacheItemPolicy();
 179:         policy.AbsoluteExpiration = absoluteExpiration;
 180:  
 181:         Set(item, policy);
 182:     }
 183:  
 184:     public override object this[string key]
 185:     {
 186:         get
 187:         {
 188:             return Get(key, DefaultRegionName);
 189:         }
 190:         set
 191:         {
 192:             Set(key, value, null, DefaultRegionName);
 193:         }
 194:     }
 195:  
 196:     public ObjectCache GetInstance()
 197:     {
 198:         return this;
 199:     }
 200:  
 201:     public string DefaultRegionName
 202:     {
 203:         get
 204:         {
 205:             string defaultRegion= FrameworkConfiguationManager.GetConfiguration().GetAppVariable("AppFabricCacheDefaultRegion");
 206:             if (string.IsNullOrEmpty(defaultRegion))
 207:             {
 208:                 defaultRegion = "Default";
 209:             }
 210:             return defaultRegion;
 211:         }
 212:     }
 213: }

输出缓存对于改善性能有很大好处,在ASP.NET 4.0中可以自定义输出缓存的策略,比如把输出保存在磁盘中,外部的memcached服务中等等。甚至还可以定义一些高级规则,比如为A页面使用A输出缓存策略来把数据保存于内存中,为B页面使用B输出缓存策略来把数据保存于磁盘中。image

代码例子可以参看文章http://www.buraksenyurt.com/post/AspNet-40-Custom-Cache-Provider.aspx,在web.config中配置

   1: <caching>
   2:         <outputCache defaultProvider="AspNetInternalProvider">
   3:           <providers>
   4:             <add name="DiskBasedCacheProvider" type="CustomCaching.DiskCacheProvider,CustomCaching"/>
   5:           </providers>
   6:         </outputCache>
   7:       </caching>

在ASP.NET 4 的默认输出缓存策略中。所有的HTTP响应、所呈现的页面和控件缓存均使用上例所示的默认输出缓存提供程序(其中defaultProvider属性值为AspNetInternalProvider)。通过为defaultProvider指定不同的提供程序。就可以更改web应用程序的默认输出缓存提供程序。

另外,还可以针对每个用户控件和各个请求选择不同的输出缓存提供程序。要为不同的Web用户控件选择不同的输出缓存提供程序,最简便的方法是设置页面或控件指令中新增加的providerName属性,如下面的示例所示:

<%@ OutputCache Duration="60" VaryByParam="None"  providerName="DiskBasedCacheProvider" %>

若要为某个HTTP请求指定不同的输出缓存提供程序,可以覆盖Global.asax文件中新增加的GetOutputCacheProviderName方法,以编程的方式指定要用于特定请求的提供程序。

参看文章:.NET 4.0新增可扩展缓存

posted @ 2010-07-01 23:03  张善友  阅读(10735)  评论(13编辑  收藏  举报