Azure应用性能监控与问题解决

在开发Azure应用时,性能监控是一个不可忽视的环节。大多数文章只提供了基础的示例,并没有深入讨论实际开发中可能遇到的问题。本文将分享在Azure应用性能监控过程中遇到的一些问题以及相应的解决方案。

应用概述

创建了一个Azure应用,包含Web角色(1个实例)和Worker角色(2个实例),并使用Windows Azure Storage。为了检查性能,在Worker角色中启用了诊断功能,并创建了自定义的性能计数器,这些计数器属于“多实例”类别,通过启动任务以提升权限运行的PowerShell脚本创建。这些计数器随后在应用程序代码中被递增,应用程序代码也以提升权限运行。

性能计数器的创建

PowerShell脚本创建了一个AverageTimer32和AverageBase计数器对。这些计数器(例如,'ResponseProcessTime'和'ResponseProcessTimeBase')帮助计算完成方法执行所需的时间。

遇到的问题

尽管自定义性能计数器在Windows机器上可见(通过perfmon和服务器资源管理器的“机器名”性能计数器查看),但它们并没有被传输到WADPerformanceCounters表中,无论是在本地仿真器还是云存储中。偶尔可以看到计数器被保存在WADPerformanceCounters表中。有时,计数器只从单个实例出现在WADPerformanceCounters表中。如果没有这些计数器,将无法计算性能。非自定义计数器“已提交字节”和“%处理器时间”始终在WADPerformanceCounters表中可见。

错误处理

在仿真器中遇到的错误在WADDiagnosticInfrastructureLogTable中可见。错误详细信息如下:

PdhGetFormattedCounterArray(\MSBAdapter(deployment18(283).MyAdapter.MyAdapter.Worker_IN_0)\ResponseProcessTime) failed pdhStatus=c0000bc6; retry 2 PdhGetFormattedCounterArray(\MSBAdapter(deployment18(283).MyAdapter.MyAdapter.Worker_IN_0)\ResponseProcessTimeBase) failed pdhStatus=800007d5; retry 2

AverageTimer32计数器并不总是如预期那样表现。有时,当计数器值成功传输到WADPerformanceCounterTable时,计数器的值为零。

解决方案

文章的剩余部分将讨论如何解决这些问题。

本文不会详细介绍性能计数器的起源和类型,而是专注于问题及其解决方案。如需了解有关性能计数器的信息,请参阅以下资源:

上述问题被发现是Windows的一个已知限制,即一个进程在启动后无法检索另一个进程创建的实例计数器。这表明MonAgent(MA)启动和计数器实例创建之间存在竞态条件,因为使用了多个Worker实例。

“最佳解决方案可能是在启动任务中将实例计数器创建为‘Singleinstance’,但由于实例计数器需要在角色的生命周期中任意时间创建,因此需要按如下方式通过程序重置WAD配置,同时创建新的计数器实例”,因此继续通过PowerShell在启动时将性能计数器类别创建为'multiinstance',而不是在不同级别上任意时间创建计数器,将创建一个包含想要维护的每个计数器的公共属性的类,并在构造函数中创建它们。然后通过这些属性访问实际需要的所有计数器。可以使用Ninject注入单个实例。代码如下:

public interface IDiagnosticHelper { PerformanceCounter ResponseProcessTime { get; } PerformanceCounter ResponseProcessTimeBase { get; } } public class DiagnosticHelper : IDiagnosticHelper { public static string defaultPerformanceCounterCategory = "MyAdapter"; public static string defaultPerformanceCounterInstance = RoleEnvironment.CurrentRoleInstance.Id; public PerformanceCounter ResponseProcessTime { get; private set; } public PerformanceCounter ResponseProcessTimeBase { get; private set; } public DiagnosticHelper() { ResponseProcessTime = new PerformanceCounter(defaultPerformanceCounterCategory, "ResponseProcessTime ", defaultPerformanceCounterInstance, false); ResponseProcessTimeBase = new PerformanceCounter(defaultPerformanceCounterCategory, "ResponseProcessTimeBase", defaultPerformanceCounterInstance, false); Trace.TraceWarning("DiagnosticHelper Created performance counters."); } }

使用Ninject进行依赖注入。在Ninject模块中,指定单例范围(为了避免由于运行2个Worker角色实例而创建的竞态条件),如下绑定:

Bind<IDiagnosticHelper>().To<DiagnosticHelper>().InSingletonScope();

在Worker角色中,在Onstart和Run中注入这个接口,以便如下创建计数器:

ninjectKernel.Get<IDiagnosticHelper>();

现在在任何想要递增计数器的类/处理程序中,不会显式地再次创建计数器,而是调用诊断助手:

public class MyHandler { private readonly IDiagnosticHelper diagnosticHelper; public MyHandler(IDiagnosticHelper diagnosticHelper) { this.diagnosticHelper = diagnosticHelper; } void CallMe() { var watch = new Stopwatch(); watch.Start(); int i = 3; int j = 7; int h = i + j; Thread.Sleep(5000); watch.Stop(); if (PerformanceCounterCategory.Exists("MyAdapter")) { if (null != diagnosticHelper.GetFacilityTime) { diagnosticHelper.GetFacilityTime.IncrementBy(watch.ElapsedTicks); diagnosticHelper.GetFacilityTimeBase.Increment(); } } } }

这段代码帮助解决了pdhStatus=c0000bc6错误(这意味着数据无效),因为现在竞态条件不再是问题。

错误pdhStatus=800007d5(没有数据返回)发生是因为最初将基础计数器‘ResponseProcessTimeBase’添加到了PerformanceCounters.DataSources中,这是不需要的,因为它是一个平均基础计数器,仅用于补充AverageTimer32计数器,本身不需要添加到数据源。

要点

如果不需要显式地使用多实例,最好选择单实例性能类别。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485