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

Unity Interceptor 用于性能插装

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2013 年 4 月 4 日

CPOL

6分钟阅读

viewsIcon

27709

downloadIcon

455

Unity 拦截。

引言

此拦截器用于检测您希望测量性能的任何方法。它会在每个时间间隔内记录调用次数、最大时间和最小时间。它可以配置为分钟间隔或小时间隔,或者您可以自定义。每个时间间隔都由时钟定义,例如从 01.00.00 到 1.00.59.59。这允许您在一天/一周等时间内绘制日志图表,并且它将与一天中的确切时间(而不是程序开始后的分钟数)完全对应。

什么是拦截?

拦截是一种设计模式(即装饰器模式),它会改变它正在拦截(或装饰)的对象的行为。Unity 和其他容器有特定的机制可以动态地装饰任何对象,而无需为每种类型的对象编写特定的装饰器类——这个机制就是拦截。

您之所以要这样做,是为了设计横切关注点,这是面向切面编程(AOP)的一部分。一个经常使用的例子就是日志记录。例如,如果您想记录任何方法的性能,如果您不使用任何类型的设计模式,您将不得不在每个需要测量性能的方法中编写一些日志记录代码。这会导致大量重复的代码,并且会稀释方法实际功能的核心业务代码,使其难以阅读和维护。

如前所述,本文使用 Unity 及其拦截器机制来实现一个日志记录的横切关注点。您可以选择 PostSharp 或其他特定的 AOP 框架,或者选择具有拦截器机制的不同 DI 容器来执行此操作。

背景

我使用了 SOLID 原则,特别是开闭原则,因此它可以扩展(例如,添加新的时间间隔类)但封闭修改。

当一个被配置为使用性能监控拦截器的已配置方法被调用时,Unity 将拦截该调用并测量其执行时间。拦截器将此信息放入队列,以尽量减少对方法调用主线程的干扰。使用一个单独的线程从队列中提取时间,并将其与同一配置周期内发生的调用进行平均,同时记录最大和最小调用时间。当拦截器识别到一个“新”周期时,它将记录(前一个周期的)结果。

我设计的线程不需要非常频繁地轮询(来处理和记录),理论上可以不那么频繁,以确保它至少在每个周期内进行一次轮询,这样它就可以处理并记录上一个周期发生的事情。这也取决于您正在监控的内容,如果该方法被频繁调用,则增加轮询频率以防止队列过大是有意义的。默认情况下,我将其设置为周期的 1/3(分钟周期为 20 秒,小时周期为 20 分钟)。

我已将实际工作抽象到一个名为 IAsychInterceptor 的接口(它是 PerformanceLogger 的一个接口),这样,如果其他 DI 容器也具有拦截机制,就可以使用它。可以添加另一个类来执行与 InstrumentationCallHandler 相同的角色,但针对不同的 DI 容器。IAsychInterceptor 是一个泛型类,它对任何 TEventArgs 类型的类进行模板化,基本上指定了异步操作完成后接口将返回的事件参数类型。

使用这些日志,您可以绘制被监控方法的性能图表,与实际一天中的时间进行比较,动态地(例如,如果不是将日志记录到文件,而是记录到数据库(只需为 log4net 配置即可))。

Using the Code

要使用拦截器,请使用 XML 配置或代码配置 Unity。关键部分是用拦截器注册您想要监控的类型。然后定义一个策略。在此,我表示监控 IOrderService 类型,并进行类型匹配,仅监控 Authorise 方法。我使用了基于策略的拦截来定位特定方法,但您也可以使用接口拦截,在这种情况下,您需要有一个不同的类来实现正确的接口拦截接口,类似于 InstrumentationCallHandler,并以不同的方式进行配置。

    <register type="IOrderService" mapTo="OrderService">
      <interceptor type="InterfaceInterceptor" />
      <interceptionBehavior type="PolicyInjectionBehavior"/>
    </register> <span style="font-size: 9pt;">      </span>
    <interception>
      <policy name="InstumentationPolicy">
        <matchingRule name="TypeMatch" type="TypeMatchingRule">
          <constructor>
            <param name="typeName" value ="IOrderService"/>
          </constructor>
        </matchingRule>
        <matchingRule name="MemberMatch" type="MemberNameMatchingRule">
          <constructor>
            <param name="nameToMatch" value="Purchase">
            </param>
          </constructor>
        </matchingRule>      
        <callHandler name="Handler" type="InstrumentationCallHandler">
          <lifetime type="singleton"/>
        </callHandler>
      </policy>
    </interception>   

然后,只需使用 Unity 解析您的接口到一个具体类,然后像往常一样调用它。

var performanceLogging = unityContainer.Resolve<IAsynchInterceptor<LoggedEventArgs>>();

结果

日志文件将如下所示:

2013-03-20 20:41:47,066 [11] DEBUG Purchase [(null)] - 20 calls in previous minute. 
Slowest: 29627.1982ms, Fastest: 1000.1604ms, Average: 2433.719805ms
2013-03-20 20:42:15,229 [11] DEBUG Purchase [(null)] - 0 calls in previous minute. 
Slowest: 0ms, Fastest: 0ms, Average: 0ms</span>
2013-03-20 20:43:13,201 [11] DEBUG Purchase [(null)] - 6 calls in previous minute. 
Slowest: 1000.3109ms, Fastest: 1000.1399ms, Average: 1001.54535ms</span>
2013-03-20 20:44:13,258 [11] DEBUG Purchase [(null)] - 60 calls in previous minute. 
Slowest: 1000.2761ms, Fastest: 1000.12ms, Average: 1000.68573333333ms</span>
2013-03-20 20:45:13,259 [11] DEBUG Purchase [(null)] - 60 calls in previous minute. 
Slowest: 1000.4533ms, Fastest: 1000.1001ms, Average: 1000.595075ms
2013-03-20 20:46:13,262 [11] DEBUG Purchase [(null)] - 24 calls in previous minute. 
Slowest: 1000.704ms, Fastest: 1000.0908ms, Average: 1000.52014583333ms
2013-03-20 20:47:13,276 [11] DEBUG Purchase [(null)] - 0 calls in previous minute. 
Slowest: 0ms, Fastest: 0ms, Average: 0ms  

如果您愿意,可以创建如下所示的图表来帮助分析一段时间内的性能(在本例中为 24 小时(请注意,每行日志显示前一个周期的结果,因此您需要进行调整)。更好的方法是配置 log4net 将数据写入一个表,例如一个前端程序/网页可以读取结果并实时生成图表。

以上结果显示,大约在 09:00 和 17:00 左右有两个高峰,交易时间有所增加。在 02:00 也有一次意想不到的交易时间增加。在 03:00 到 05:00 之间没有交易。最小和最大时间之间也有相当大的差距。

对此的解释(作为改进性能的调查练习的一部分)可能得出结论:

  1. 服务器上大约在 02:00 是否有某个作业在运行,影响了性能?这个作业能否移到另一台服务器或进行改进?另外,由于在 03:00 到 05:00 之间没有交易,如果有一个作业在运行,是否可以将其移到 04:00?
  2. 调查最小和最大时间之间存在显著差异的原因——可能是缓存问题。
  3. 是否有可能在 09:00 和 17:00 左右(例如,如果使用基于云的托管)仅扩展计算资源?

希望上述示例表明,此拦截器可以帮助进行性能调查。

设计

该解决方案由以下项目组成:

  • JML.Main,一个控制台应用程序。您可以运行它来查看其工作原理。
  • JML.OrderService,包含需要拦截的代码。
  • JML.Framework,包含拦截日志代码。
  • JML.Framework.Tests,用于测试框架的单元测试。

请注意,它是使用 **Visual Studio 2012** 编写的。

解决方案已通过 NuGet 添加了以下引用:

  • Unity
  • Unity Interception
  • Moq
  • Log4Net

如果您下载非 EXE 版本,您可能需要自己查找引用。

关注点

考虑从一个周期到另一个周期的时间,以确保它记录了上一个周期发生的情况,同时也允许它处理同一周期内的计时队列,这非常棘手。

我计划添加不同类型的日志记录,用于:

  • 任何对象中运行最慢的前 10 个方法。
  • 发生错误时记录参数。

您可以进行的更改:

  • 您可以通过重新配置 log4net 将日志记录到数据库。
  • 您可以使用代码配置而不是 XML——这允许您通过动态添加/删除拦截器来打开/关闭日志记录。

历史

  • 2013 年 4 月 4 日:初始版本。
© . All rights reserved.