65.9K
CodeProject 正在变化。 阅读更多。
Home

利用 MemoryCache 和 AOP 处理高开销调用

starIconstarIconstarIconstarIconstarIcon

5.00/5 (5投票s)

2012年11月15日

CPOL

3分钟阅读

viewsIcon

26270

如何使用 AOP 和 System.Runtime.Caching.MemoryCache 来提高应用程序性能

背景

自从我发现 SharpCrafters 的 PostSharp 以来,我立即成为了该工具以及面向切面编程 (AOP) 模型的客户和忠实拥护者。我不会用它消除了多少样板代码的细节来烦扰您,关于这方面已经有很多文章了。本文也不是关于 AOP 是什么以及 PostSharp 如何将额外的代码编织到您编译的应用程序中的。如果您对这些细节感兴趣,您需要访问他们的支持论坛和博客。我强烈推荐您这样做。

Using the Code

最近,我遇到了一个有趣的性能问题。我的应用程序需要来自第三方提供商的繁重的规则处理引擎,并且作为一名体贴的开发人员,我选择在使用后正确释放所有 Disposable 对象。但是,这导致处理时间增加了一倍甚至四倍。您可能也遇到过类似的情况,例如在昂贵的 SOA 或数据库调用期间,因此我将要向您介绍的技术将有助于在给定相同参数集和时间范围的情况下,响应始终相同的情况下。

首先,让我们开始使用 PostSharp 的 AOP 创建一个缓存属性

using System;
using System.Linq;
using System.Reflection;
using System.Runtime.Caching;
using PostSharp.Aspects;
using PostSharp.Extensibility;

namespace InRuleLocalStressLoad.Lib.Attibutes
{
    [Serializable]
    public class CacheAttribute : MethodInterceptionAspect
    {
        [NonSerialized] private static readonly MemoryCache Cache;

        [NonSerialized] private CacheItemPolicy cachePolicy;
        private string _methodName;
        private string _declaringType;
        private string _prefix;

        static CacheAttribute()
        {
            if (!PostSharpEnvironment.IsPostSharpRunning)
            {
                Cache = MemoryCache.Default;
            }
        }

        public override void CompileTimeInitialize(MethodBase method, AspectInfo aspectInfo)
        {
            _methodName = method.Name;
            _declaringType = method.DeclaringType != null ? 
                                 method.DeclaringType.FullName : String.Empty;
            _prefix = String.Concat(_declaringType, ".", _methodName, ":");
        }

        public override void RuntimeInitialize(MethodBase method)
        {
            cachePolicy = new CacheItemPolicy {SlidingExpiration = TimeSpan.FromMinutes(15)};
        }

        public override void OnInvoke(MethodInterceptionArgs args)
        {
            var key = BuildCacheKey(args.Arguments);
            args.ReturnValue = Cache.Get(key, null) ?? Cache.AddOrGetExisting(key, 
              args.Invoke(args.Arguments), cachePolicy, null) ?? Cache.Get(key, null);
        }
        
        private string BuildCacheKey(Arguments arguments)
        { 
            return String.Concat(_prefix, 
                String.Join("_", 
                  arguments.Select(a => a != null ? a.ToString() : String.Empty)));
        } 
    } 
} 

关于此示例的一些要点

  • 正如您所看到的,PostSharp 允许两种类型的初始化,编译时和运行时。我已决定将昂贵的反射调用放在 CompileTimeInitialize 中,以避免执行期间的性能下降。我将 CacheItemPolicy 的创建保留在 RunTimeInitialize 中,因为最终我希望使用应用程序配置文件对其进行配置。
  • MemoryCache 类是线程安全的,可以在多线程应用程序中使用。如果添加了值,则对 AddOrGetExisting 的调用将执行对 args.Invoke() 的调用并返回 null 值,因此两次使用了 null 合并运算符。这让我感到惊讶,因为我希望 MemoryCache 的行为类似于 ConcurrentDictionary,如果该值已经存在,则不会执行对 Invoke 的调用。
  • 给定类、方法和参数值,BuildCacheKey 将“始终”是唯一的。如果您需要确保它是 100% 唯一的,您将需要开发自己的哈希函数。

一旦您创建了 Cache 属性,剩下的就非常简单了。假设您有一个类,该类在其构造函数中对文件有很大的依赖性

var myInstance = new HeavyObject(someFileName)      

您需要利用内存缓存所做的就是创建一个辅助方法并使用 [Cache] 属性对其进行修饰

[Cache]
private HeavyObject GetMyHeavyObject(string fileName){
    return new HeavyObject(fileName);
}  

///later in code 
var myInstance =  GetHeavyObject(someFileName);  

就是这样。PostSharp 负责在编译后的源代码周围编织所有必要的代码。您编写的代码更少,这更易于阅读,并且更易于维护。

关注点

事实上,从 .NET 4 开始,我们可以访问类似 ASP.NET 的缓存,这为我的分布式系统中的改进开辟了全新的领域。下一步将是将其与文件和数据库监视器集成,以便无需按预定计划重新加载缓存内容(就像这篇文章中一样)。

历史

  • 2012 年 11 月 15 日:发布了 1.0 版本
© . All rights reserved.