【发布时间】:2010-10-29 15:46:39
【问题描述】:
我正在构建 WCF 服务。我需要将参考数据存储在缓存中,每次我收到来自该方法的输入时都会查找这些数据……正确的方法是什么?我还想为缓存定义一个过期策略,在一定时间间隔后使其失效。
【问题讨论】:
我正在构建 WCF 服务。我需要将参考数据存储在缓存中,每次我收到来自该方法的输入时都会查找这些数据……正确的方法是什么?我还想为缓存定义一个过期策略,在一定时间间隔后使其失效。
【问题讨论】:
如果您使用的是 .NET 4,推荐的方法是使用 MemoryCache
【讨论】:
任何缓存解决方案都应该解决两个基本问题
1) 缓存项的存储和检索
2) 缓存失效
由于 Http 缓存是众所周知的,我不打算详细解释它。您可以将 asp 兼容性属性单独与一些 Web 配置一起使用,您将在其中通过魅力获得缓存。
[AspNetCacheProfile("MyProfile")]
public Customer GetName(string id)
{
// ...
}
网络配置就像
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
</system.serviceModel>
<system.web>
<caching>
<outputCacheSettings>
<outputCacheProfiles>
<add name=" MyProfile" duration="600" varyByParam="none" sqlDependency="MyTestDatabase:MyTable"/>
</outputCacheProfiles>
</outputCacheSettings>
</caching>
</system.web>
但这不适用于大多数场景,尤其是当您有大型复杂对象要缓存时。例如我有一种情况,我想缓存一个系统生成的图像(操作合约的输出是一个系统生成的图像,它依赖于输入)。 在这种情况下,您必须实现自己的缓存。我使用了 Microsoft 企业库缓存块来满足我所有的缓存存储需求。但是,您仍然需要进行管道以将 Microsoft 企业库缓存块与您的 WCF 服务集成。首先你必须拦截 WCF 通信通道来实现缓存。可以在http://msdn.microsoft.com/en-us/magazine/cc163302.aspx 找到有关如何拦截 WCF 通信通道的详细讨论。 这就是你为 WCF 缓存做管道的方式
第 0 步 假设您有一个如下操作合约,并且您想缓存该方法返回的项目。
[OperationContract]
MyCompositeClass Rotate(int angle)
第 1 步 首先,您必须在 WCF 管道中注册您的自定义缓存器。为此,我将使用一个属性,以便我可以根据面向方面的编程原则很好地装饰我的 WCF 调用。
using System;
using System.ServiceModel.Description;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.Reflection;
[AttributeUsage(AttributeTargets.Method)]
public class MyCacheRegister : Attribute, IOperationBehavior
{
ConstructorInfo _chacherImplementation;
public ImageCache(Type provider)
{
if (provider == null)
{
throw new ArgumentNullException("Provider can't be null");
}
else if (provider.IsAssignableFrom(typeof(IOperationInvoker)))
{
throw new ArgumentException("The type " + provider.AssemblyQualifiedName + " does not implements the interface " + typeof(IOperationInvoker).AssemblyQualifiedName);
}
else
{
try
{
Type[] constructorSignatureTypes = new Type[1];
constructorSignatureTypes[0] = typeof(IOperationInvoker);
_chacherImplementation = provider.GetConstructor(constructorSignatureTypes);
}
catch
{
throw new ArgumentException("There is no constructor in " + provider.AssemblyQualifiedName + " that accept " + typeof(IOperationInvoker).AssemblyQualifiedName + " as a parameter");
}
}
}
public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
{
return;
}
public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
{
return;
}
/// <summary>
/// Decorate the method call with the cacher
/// </summary>
public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
//decorator pattern, decorate with a cacher
object[] constructorParam = new object[1];
constructorParam[0] = dispatchOperation.Invoker;
dispatchOperation.Invoker = (IOperationInvoker)_chacherImplementation.Invoke(constructorParam);
}
public void Validate(OperationDescription operationDescription)
{
return;
}
}
第 2 步
然后你必须实现缓存对象将被检索的点。
using System;
using System.ServiceModel.Dispatcher;
using Microsoft.Practices.EnterpriseLibrary.Caching;
using Microsoft.Practices.EnterpriseLibrary.Common;
using System.IO;
class RotateCacher : IOperationInvoker
{
private IOperationInvoker _innerOperationInvoker;
public RotateImageCacher(IOperationInvoker innerInvoker)
{
_innerOperationInvoker = innerInvoker;
}
public object[] AllocateInputs()
{
Object[] result = _innerOperationInvoker.AllocateInputs();
return result;
}
public object Invoke(object instance, object[] inputs, out object[] outputs)
{
object result=null;
///TODO: You will have more object in the input if you have more ///parameters in your method
string angle = inputs[1].ToString();
///TODO: create a unique key from the inputs
string key = angle;
string provider = System.Configuration.ConfigurationManager.AppSettings["CacheProviderName"];
///Important Provider will be DiskCache or MemoryCache for the moment
provider =”DiskCache”;
///TODO: call enterprise library cache manager, You can have your own
/// custom cache like Hashtable
ICacheManager manager = CacheFactory.GetCacheManager(provider);
if (manager.Contains(key))
{
result =(MyCompositeClass) manager[key];
}
else
{
result =(MyCompositeClass) _innerOperationInvoker.Invoke(instance, inputs, out outputs);
manager.Add(key, result);
}
return result;
}
public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
{
IAsyncResult result = _innerOperationInvoker.InvokeBegin(instance, inputs, callback, state);
return result;
}
public object InvokeEnd(object instance, out object[] outputs, IAsyncResult asyncResult)
{
object result = _innerOperationInvoker.InvokeEnd(instance, out outputs, asyncResult);
return result;
}
public bool IsSynchronous
{
get { return _innerOperationInvoker.IsSynchronous; }
}
}
第 3 步
最后在您的服务调用上方添加您的属性
[OperationContract]
[MyCacheRegister(typeof(RotateCacher)]
MyCompositeClass Rotate(int angle)
企业库缓存块的配置超出了这个答案的范围。您可以使用以下链接来学习它。企业库的好处是您可以使用现成的方法来扩展缓存策略。它内置了缓存到期和存储的方法。您还可以编写自己的缓存过期和存储策略。 http://www.codeproject.com/KB/web-cache/CachingApplicationBlock.aspx
最后一件事,为了让您的企业库缓存正常工作,您需要添加以下配置详细信息。您还需要将相关的 dll 添加到您的项目参考中。
<configSections>
<section name="cachingConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Caching.Configuration.CacheManagerSettings, Microsoft.Practices.EnterpriseLibrary.Caching, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="true" />
</configSections>
<cachingConfiguration defaultCacheManager="Cache Manager">
<cacheManagers>
<add name="MemoryCache" type="Microsoft.Practices.EnterpriseLibrary.Caching.CacheManager, Microsoft.Practices.EnterpriseLibrary.Caching, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
expirationPollFrequencyInSeconds="60" maximumElementsInCacheBeforeScavenging="1000"
numberToRemoveWhenScavenging="10" backingStoreName="NullBackingStore" />
<add name="DiskCache" type="Microsoft.Practices.EnterpriseLibrary.Caching.CacheManager, Microsoft.Practices.EnterpriseLibrary.Caching, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
expirationPollFrequencyInSeconds="60" maximumElementsInCacheBeforeScavenging="1000"
numberToRemoveWhenScavenging="10" backingStoreName="IsolatedStorageCacheStore" />
</cacheManagers>
<backingStores>
<add type="Microsoft.Practices.EnterpriseLibrary.Caching.BackingStoreImplementations.NullBackingStore, Microsoft.Practices.EnterpriseLibrary.Caching, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
name="NullBackingStore" />
<add name="IsolatedStorageCacheStore" type="Microsoft.Practices.EnterpriseLibrary.Caching.BackingStoreImplementations.IsolatedStorageBackingStore, Microsoft.Practices.EnterpriseLibrary.Caching, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
encryptionProviderName="" partitionName="MyCachePartition" />
</backingStores>
</cachingConfiguration>
【讨论】:
link 链接(它是一个永久链接)。您也可以在浏览器窗口中使用 URL。
您可以查看Velocity。这是微软的分布式内存caching framework。但这可能有点太beta了...
【讨论】:
如果您要在负载平衡的无状态系统中横向扩展至多台服务器,您需要design for use of a distributed cache。这里要做的主要事情是:
同时使用本地和分布式缓存。只放会话或短 生活在分布式缓存中的东西,其他东西在本地缓存。
为项目设置适当的超时。这将取决于 信息的类型以及它需要离源多近。
当你知道它会失禁时从缓存中删除它(比如 更新、删除等)。
注意设计唯一的缓存键。建立一个模型 您计划缓存的信息类型并将其用作模板 构建密钥。
【讨论】:
您可以使用 System.Web.Cache(即使您不在网络环境中),这就是我要做的。它基本上是一个很大的内存哈希表,带有一些用于过期内容的细节。
【讨论】:
有很多方法可以做到这一点。一种相当简单的方法是自己托管 System.Web.Cache 对象并使用它来存储引用数据。这里有一个很好的例子:http://kjellsj.blogspot.com/2007/11/wcf-caching-claims-using.html
【讨论】:
WCF REST Starter Kit 具有缓存功能,这里有一篇关于使用它的文章...带有示例代码。
【讨论】:
您实际上可以确保在缓存的基础数据发生更改时使缓存失效,而不是每隔一段时间就使缓存数据过期。
请参阅 info Q 中的此示例 http://www.infoq.com/news/2011/04/Attribute-Caching
[Cache.Cacheable("UserTransactionCache")]
public DataTable GetAllTransactionsForUser(int userId)
{
return new DataProvider().GetAllTransactionsForUser(userId);
}
[Cache.TriggerInvalidation("UserTransactionCache")]
public void DeleteAllTransactionsForUser(int userId)
{
...
}
【讨论】: