一个为高流量网站设计的 ASP.NET 抽象类,用于缓存管理






4.16/5 (12投票s)
提供一个简单的泛型基类,可以快速轻松地访问缓存方法。该类专为高流量网站设计,因为它将对数据源的并发查询数量降到最低。
引言
本文提供了一个简单的泛型基类,可以快速轻松地访问缓存方法。该类专为高流量网站设计,因为它将对数据源的并发查询数量降到最低。
Using the Code
提供的源代码包含一个 Visual Studio 2008 网站。
这是调用缓存方法的语法
Product product = new LoadProductMethod(
1,
1,
System.Web.Caching.CacheItemPriority.Normal,
true).GetData();
此方法尝试从数据库(在代码中模拟)检索一个产品(key = 1),并要求将结果放入缓存中,缓存时间为 1 分钟,优先级为普通。
创建了 LoadProductMethod
类以实现单个特定查询。对于任何新查询,都必须创建类似的新类。当然,您可以选择公开一个完整的构造函数(如示例中所示),或者限制最终用户可以控制的参数。
让我们来看看它
public class LoadProductMethod : BaseCachedMethod<Product>
{
public LoadProductMethod(int productId,
int expiration,
System.Web.Caching.CacheItemPriority priority,
bool useCache)
{
_productId = productId;
_Expiration = expiration;
_Priority = priority;
_UseCache = useCache;
}
/// <summary>
/// This is the only parameter used by this method
/// </summary>
int _productId;
/// <summary>
/// This method builds a unique string generated by the parameters set
/// (in this case only one)
/// </summary>
/// <returns>
protected override string GetCacheKey()
{
return _productId.ToString();
}
/// <summary>
/// This method is a concrete implementation of an abstract method and
/// contains the code that retrieves the data from the data source
/// </summary>
/// <returns>
protected override Product LoadData()
{
//This call simulate a long time running query
System.Threading.Thread.Sleep(2000);
Product product = new Product(_productId);
return product;
}
}
}
此缓存方法实现了 abstract
BaseCachedMethod<T>
类。GetCacheKey()
方法返回一个唯一的 string
,用于标识查询参数。LoadData()
方法包含从数据库检索实际数据的实际代码(查询延迟通过 Sleep
调用来模拟)。
到目前为止编写的内容是实现新缓存方法时唯一需要编写的自定义代码。
现在让我们看看 BaseCachedMethod<T>
类。
using System;
using System.Collections.Generic;
using System.Text;
using System.Web.Caching;
namespace CacheManager
{
public abstract class BaseCachedMethod<T>
{
private const string _CONTROL_VALUE_APPENDIX = "ControlValue";
/// <summary>
/// cache expiration in minutes. Default is 60
/// </summary>
protected int _Expiration = 1;
/// <summary>
/// if the class uses the control value with deferred expiration mechanism. Default is true
/// </summary>
protected bool _UseControlValue = true;
/// <summary>
/// The difference in minutes between the control value expiration
/// and the real data expiration. Default is one
/// </summary>
protected int _ControlValueExpirationDifference = 1;
/// <summary>
/// Cache priority
/// </summary>
protected System.Web.Caching.CacheItemPriority _Priority =
System.Web.Caching.CacheItemPriority.Normal;
/// <summary>
/// Deprecated, not used anymore
/// </summary>
protected bool _DoCallBack = false;
/// <summary>
/// If true the object is saved in cache, otherwise it's always retrieved from data source
/// </summary>
protected bool _UseCache = true;
/// <summary>
/// This property builds the cache key by using the reflected name
/// of the class and the GetCacheKey
/// method implemented in the concrete class
/// </summary>
private string CacheKey
{
get
{
return this.GetType().ToString() + "-" + this.GetCacheKey();
}
}
private int DataExpiration
{
get {
return _Expiration + (_UseControlValue ? _ControlValueExpirationDifference : 0);
}
}
/// <summary>
/// Adds data do cache
/// </summary>
/// <param name="localResult"></param>
private void AddDataToCache(T localResult)
{
System.Web.HttpContext.Current.Trace.Warn("AddDataToCache", CacheKey);
CacheManager.CurrentCache.Insert(CacheKey, localResult, null,
DateTime.Now.AddMinutes(DataExpiration),
System.Web.Caching.Cache.NoSlidingExpiration, _Priority, null);
AddControlValueToCache();
}
/// <summary>
/// This abstract method has to be redefined in the concrete class
/// in order to define a unique cache key
/// </summary>
/// <returns></returns>
protected abstract string GetCacheKey();
/// <summary>
/// This abstract method has to be implemented in the concrete class
/// and wiil contain the code that performs the query
/// </summary>
/// <returns></returns>
protected abstract T LoadData();
/// <summary>
/// This method calls the LoadData method and is passed
/// to the Cache.Insert method as a callback
/// </summary>
/// <param name="cacheKey"></param>
/// <param name="obj"></param>
/// <param name="reason"></param>
private void LoadCache(string cacheKey, object obj,
System.Web.Caching.CacheItemRemovedReason reason)
{
//If an object has been explicitly removed or is expired due to underusage,
//it is not added to cache.
if (reason != System.Web.Caching.CacheItemRemovedReason.Removed &&
reason != System.Web.Caching.CacheItemRemovedReason.Underused)
{
if (obj != null)
{
//Expired object is immediately added again to cache
//so the user doesn't have to wait till the end of the query
CacheManager.CurrentCache.Insert(cacheKey, obj);
}
T localResult = LoadData();
AddDataToCache(localResult);
}
}
/// <summary>
/// Gets the method data from data source or cache
/// </summary>
/// <returns></returns>
public T GetData()
{
T result = default(T);
if (_UseCache)
{
bool reloadData = false;
object objInCache = null;
if(_UseControlValue)
{
object singleReloadObj = CacheManager.CurrentCache.Get
(CacheKey + _CONTROL_VALUE_APPENDIX);
if(singleReloadObj==null)
{
System.Web.HttpContext.Current.Trace.Warn("Control value is null", CacheKey);
reloadData=true;
//The control value is immediately re-inserted in the cache
//so other user will not see the object as expired
AddControlValueToCache();
}
}
if(!reloadData)
{
objInCache = CacheManager.CurrentCache.Get(CacheKey);
}
if (objInCache == null)
{
System.Web.HttpContext.Current.Trace.Warn("Load real data", CacheKey);
result = LoadData();
AddDataToCache(result);
}
else
{
System.Web.HttpContext.Current.Trace.Warn("Get object from cache", CacheKey);
result = (T)objInCache;
}
}
else
{
result = LoadData();
}
return result;
}
public T GetDataIfInCache()
{
return (T)CacheManager.CurrentCache.Get(CacheKey);
}
private void AddControlValueToCache()
{
if (_UseControlValue)
{
System.Web.HttpContext.Current.Trace.Warn("AddControlValueToCache", CacheKey);
CacheManager.CurrentCache.Insert(CacheKey + _CONTROL_VALUE_APPENDIX,
true, null, DateTime.Now.AddMinutes(_Expiration),
System.Web.Caching.Cache.NoSlidingExpiration, _Priority, null);
}
}
}
}
此类有一个 public
方法 (GetData
),其中包含检查对象是否包含在缓存中或必须从数据源检索的逻辑。
LoadCache
方法在调用 LoadData
方法之前,将过期的对象插入缓存中,因此用户永远不会直接体验对数据源的查询,因为数据将始终在缓存中可用(当然,为了保持可伸缩性,当对象被 ASP.NET Cache 或代码显式删除时,这不会发生)。
如果变量 _UseControlValue
为 true
(这是默认值),则每次将对象添加到缓存时,还会添加另一个简单的布尔对象,称为“控制值”,其过期值较低(默认差值为一分钟,但可以更改)。当调用 GetData()
方法时,代码首先检查所谓的“控制值”是否可用。如果不是,则立即重新创建控制值并将其放入缓存中,并执行实际查询并将其数据刷新为更新后的值。通过这种方式,当第一个用户(非常不幸!)正在从数据源加载实际数据时,其他需要相同数据的用户仍然可以从缓存中获取它,因为这还没有过期。这允许在高流量网站中进行长时间运行的查询:实际上只有一位用户会执行查询。