C# 缓存最佳实践-单个对象还是多个条目?
在C#ASP.net应用程序中缓存数据时,有人对哪种方法更好有什么建议吗 我目前正在使用两种方法的组合,一些数据(列表、字典、通常特定于域的信息)直接放入缓存中,并在需要时装箱,一些数据保存在globaldata类中,并通过该类检索(即GlobalData类被缓存,其属性为实际数据) 两种方法都可取吗 我觉得,从并发性的角度来看,单独缓存每个项会更明智,但是从长远来看,它会创建更多的工作,使用更多的函数来处理从实用程序类中的缓存位置获取数据 建议将不胜感激。两个世界中最好的(最坏的?)如何 让C# 缓存最佳实践-单个对象还是多个条目?,c#,asp.net,caching,C#,Asp.net,Caching,在C#ASP.net应用程序中缓存数据时,有人对哪种方法更好有什么建议吗 我目前正在使用两种方法的组合,一些数据(列表、字典、通常特定于域的信息)直接放入缓存中,并在需要时装箱,一些数据保存在globaldata类中,并通过该类检索(即GlobalData类被缓存,其属性为实际数据) 两种方法都可取吗 我觉得,从并发性的角度来看,单独缓存每个项会更明智,但是从长远来看,它会创建更多的工作,使用更多的函数来处理从实用程序类中的缓存位置获取数据 建议将不胜感激。两个世界中最好的(最坏的?)如何 让g
globaldata
类在内部管理所有缓存访问。剩下的代码就可以使用globaldata
,这意味着它根本不需要缓存感知
只要更新
globaldata
,您就可以随时更改缓存实现,而您的其余代码将不知道或不关心内部发生了什么。在什么情况下需要使缓存无效?应该存储对象,以便在对象无效时,重新填充缓存只需要重新缓存对象无效的项目
例如,如果您缓存了一个客户对象,该对象包含订单和购物篮的交付详细信息。由于购物篮添加或删除了某个项目,因此要使购物篮无效,还需要不必要地重新填充交付详细信息
(注意:这是一个迟钝的例子,我不是在鼓吹这一点,只是试图证明这一原则,我今天的想象力有点欠缺)在构建缓存策略时,要考虑的不仅仅是要考虑缓存的存储,就好像它是内存中的DB一样。因此,仔细处理存储在其中的每种类型的依赖关系和过期策略。(system.web,其他商业解决方案,滚动您自己的…) 不过我会尝试将其集中化,并使用某种可插入的体系结构。让您的数据使用者通过公共API(公开它的抽象缓存)访问它,并在运行时插入缓存层(比如asp.net缓存)
在缓存数据时,您确实应该采取自上而下的方法,以避免任何类型的数据完整性问题(如我所说的适当依赖),然后注意提供同步。通常,缓存的性能要比底层源(例如数据库)好得多缓存的性能不是一个问题。主要目标是获得尽可能高的缓存命中率(除非您正在大规模开发,因为优化缓存也会带来回报) 为了实现这一点,我通常尝试让开发人员尽可能直接地使用缓存(这样我们就不会因为开发人员懒得使用缓存而错过缓存命中的机会)。在一些项目中,我们使用了Microsoft企业库中提供的缓存处理程序的修改版本 使用CacheHandler(使用),只需向方法添加一个属性,即可轻松使其成为“可缓存”的方法。例如:
[CacheHandler(0, 30, 0)]
public Object GetData(Object input)
{
}
将对该方法的所有调用缓存30分钟。所有调用都会根据输入数据和方法名称获取唯一的缓存键,因此,如果使用不同的输入调用该方法两次,则不会缓存该方法,但如果使用相同的输入在超时间隔内调用该方法>1次,则该方法只执行一次
我们的修改版本如下所示:
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.Remoting.Contexts;
using System.Text;
using System.Web;
using System.Web.Caching;
using System.Web.UI;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.Unity.InterceptionExtension;
namespace Middleware.Cache
{
/// <summary>
/// An <see cref="ICallHandler"/> that implements caching of the return values of
/// methods. This handler stores the return value in the ASP.NET cache or the Items object of the current request.
/// </summary>
[ConfigurationElementType(typeof (CacheHandler)), Synchronization]
public class CacheHandler : ICallHandler
{
/// <summary>
/// The default expiration time for the cached entries: 5 minutes
/// </summary>
public static readonly TimeSpan DefaultExpirationTime = new TimeSpan(0, 5, 0);
private readonly object cachedData;
private readonly DefaultCacheKeyGenerator keyGenerator;
private readonly bool storeOnlyForThisRequest = true;
private TimeSpan expirationTime;
private GetNextHandlerDelegate getNext;
private IMethodInvocation input;
public CacheHandler(TimeSpan expirationTime, bool storeOnlyForThisRequest)
{
keyGenerator = new DefaultCacheKeyGenerator();
this.expirationTime = expirationTime;
this.storeOnlyForThisRequest = storeOnlyForThisRequest;
}
/// <summary>
/// This constructor is used when we wrap cached data in a CacheHandler so that
/// we can reload the object after it has been removed from the cache.
/// </summary>
/// <param name="expirationTime"></param>
/// <param name="storeOnlyForThisRequest"></param>
/// <param name="input"></param>
/// <param name="getNext"></param>
/// <param name="cachedData"></param>
public CacheHandler(TimeSpan expirationTime, bool storeOnlyForThisRequest,
IMethodInvocation input, GetNextHandlerDelegate getNext,
object cachedData)
: this(expirationTime, storeOnlyForThisRequest)
{
this.input = input;
this.getNext = getNext;
this.cachedData = cachedData;
}
/// <summary>
/// Gets or sets the expiration time for cache data.
/// </summary>
/// <value>The expiration time.</value>
public TimeSpan ExpirationTime
{
get { return expirationTime; }
set { expirationTime = value; }
}
#region ICallHandler Members
/// <summary>
/// Implements the caching behavior of this handler.
/// </summary>
/// <param name="input"><see cref="IMethodInvocation"/> object describing the current call.</param>
/// <param name="getNext">delegate used to get the next handler in the current pipeline.</param>
/// <returns>Return value from target method, or cached result if previous inputs have been seen.</returns>
public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
{
lock (input.MethodBase)
{
this.input = input;
this.getNext = getNext;
return loadUsingCache();
}
}
public int Order
{
get { return 0; }
set { }
}
#endregion
private IMethodReturn loadUsingCache()
{
//We need to synchronize calls to the CacheHandler on method level
//to prevent duplicate calls to methods that could be cached.
lock (input.MethodBase)
{
if (TargetMethodReturnsVoid(input) || HttpContext.Current == null)
{
return getNext()(input, getNext);
}
var inputs = new object[input.Inputs.Count];
for (int i = 0; i < inputs.Length; ++i)
{
inputs[i] = input.Inputs[i];
}
string cacheKey = keyGenerator.CreateCacheKey(input.MethodBase, inputs);
object cachedResult = getCachedResult(cacheKey);
if (cachedResult == null)
{
var stopWatch = Stopwatch.StartNew();
var realReturn = getNext()(input, getNext);
stopWatch.Stop();
if (realReturn.Exception == null && realReturn.ReturnValue != null)
{
AddToCache(cacheKey, realReturn.ReturnValue);
}
return realReturn;
}
var cachedReturn = input.CreateMethodReturn(cachedResult, input.Arguments);
return cachedReturn;
}
}
private object getCachedResult(string cacheKey)
{
//When the method uses input that is not serializable
//we cannot create a cache key and can therefore not
//cache the data.
if (cacheKey == null)
{
return null;
}
object cachedValue = !storeOnlyForThisRequest ? HttpRuntime.Cache.Get(cacheKey) : HttpContext.Current.Items[cacheKey];
var cachedValueCast = cachedValue as CacheHandler;
if (cachedValueCast != null)
{
//This is an object that is reloaded when it is being removed.
//It is therefore wrapped in a CacheHandler-object and we must
//unwrap it before returning it.
return cachedValueCast.cachedData;
}
return cachedValue;
}
private static bool TargetMethodReturnsVoid(IMethodInvocation input)
{
var targetMethod = input.MethodBase as MethodInfo;
return targetMethod != null && targetMethod.ReturnType == typeof (void);
}
private void AddToCache(string key, object valueToCache)
{
if (key == null)
{
//When the method uses input that is not serializable
//we cannot create a cache key and can therefore not
//cache the data.
return;
}
if (!storeOnlyForThisRequest)
{
HttpRuntime.Cache.Insert(
key,
valueToCache,
null,
System.Web.Caching.Cache.NoAbsoluteExpiration,
expirationTime,
CacheItemPriority.Normal, null);
}
else
{
HttpContext.Current.Items[key] = valueToCache;
}
}
}
/// <summary>
/// This interface describes classes that can be used to generate cache key strings
/// for the <see cref="CacheHandler"/>.
/// </summary>
public interface ICacheKeyGenerator
{
/// <summary>
/// Creates a cache key for the given method and set of input arguments.
/// </summary>
/// <param name="method">Method being called.</param>
/// <param name="inputs">Input arguments.</param>
/// <returns>A (hopefully) unique string to be used as a cache key.</returns>
string CreateCacheKey(MethodBase method, object[] inputs);
}
/// <summary>
/// The default <see cref="ICacheKeyGenerator"/> used by the <see cref="CacheHandler"/>.
/// </summary>
public class DefaultCacheKeyGenerator : ICacheKeyGenerator
{
private readonly LosFormatter serializer = new LosFormatter(false, "");
#region ICacheKeyGenerator Members
/// <summary>
/// Create a cache key for the given method and set of input arguments.
/// </summary>
/// <param name="method">Method being called.</param>
/// <param name="inputs">Input arguments.</param>
/// <returns>A (hopefully) unique string to be used as a cache key.</returns>
public string CreateCacheKey(MethodBase method, params object[] inputs)
{
try
{
var sb = new StringBuilder();
if (method.DeclaringType != null)
{
sb.Append(method.DeclaringType.FullName);
}
sb.Append(':');
sb.Append(method.Name);
TextWriter writer = new StringWriter(sb);
if (inputs != null)
{
foreach (var input in inputs)
{
sb.Append(':');
if (input != null)
{
//Diffrerent instances of DateTime which represents the same value
//sometimes serialize differently due to some internal variables which are different.
//We therefore serialize it using Ticks instead. instead.
var inputDateTime = input as DateTime?;
if (inputDateTime.HasValue)
{
sb.Append(inputDateTime.Value.Ticks);
}
else
{
//Serialize the input and write it to the key StringBuilder.
serializer.Serialize(writer, input);
}
}
}
}
return sb.ToString();
}
catch
{
//Something went wrong when generating the key (probably an input-value was not serializble.
//Return a null key.
return null;
}
}
#endregion
}
}
使用系统;
使用系统诊断;
使用System.IO;
运用系统反思;
使用System.Runtime.Remoting.Context;
使用系统文本;
使用System.Web;
使用System.Web.Caching;
使用System.Web.UI;
使用Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
使用Microsoft.Practices.Unity.InterceptionExtension;
名称空间中间件.Cache
{
///
///实现对的返回值进行缓存的
///此处理程序将返回值存储在ASP.NET缓存或当前请求的Items对象中。
///
[ConfigurationElementType(typeof(CacheHandler)),同步]
公共类CacheHandler:ICallHandler
{
///
///缓存项的默认过期时间:5分钟
///
公共静态只读TimeSpan DefaultExpirationTime=新的TimeSpan(0,5,0);
私有只读对象缓存数据;
私有只读DefaultCacheKeyGenerator密钥生成器;
private readonly bool storeOnlyForThisRequest=true;
私有时间跨度到期时间;
私有GetNextHandlerDelegate getNext;
私人职业输入;
公共缓存处理程序(TimeSpan expirationTime,bool storeOnlyForThisRequest)
{
keyGenerator=新的DefaultCacheKeyGenerator();
this.expirationTime=到期时间;
this.storeOnlyForThisRequest=storeOnlyForThisRequest;
}
///
///当我们将缓存数据包装到CacheHandler中时,将使用此构造函数,以便
///我们可以在从缓存中删除对象后重新加载该对象。
///
///
///
///
///
///
public CacheHandler(TimeSpan expirationTime,bool storeOnlyForThisRequest,
i方法输入,GetNextHandlerElegate getNext,
对象缓存(数据)
:此(过期时间,仅限StoreyFortisRequest)
{
这个输入=输入;
this.getNext=getNext;
this.cachedData=cached