Unity Interceptor 用于性能插装





5.00/5 (2投票s)
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 之间没有交易。最小和最大时间之间也有相当大的差距。
对此的解释(作为改进性能的调查练习的一部分)可能得出结论:
- 服务器上大约在 02:00 是否有某个作业在运行,影响了性能?这个作业能否移到另一台服务器或进行改进?另外,由于在 03:00 到 05:00 之间没有交易,如果有一个作业在运行,是否可以将其移到 04:00?
- 调查最小和最大时间之间存在显著差异的原因——可能是缓存问题。
- 是否有可能在 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 日:初始版本。