我刚刚编写的一些代码如下。
它演示了将 PostSharp 方面应用于方法,以便以异步方式记录方法调用的持续时间 - 这样,如果日志记录过程很慢,则用装饰的方法的调用者不会看到这种性能损失方面。
它似乎有效,MyFirstMethod 完成,日志记录方法在单独的线程中启动,MySecondMethod 并行运行。这个想法是,使用类似的工具来装饰高流量 Web 应用程序(即高度多线程环境)中的方法。
这样做有哪些陷阱? (例如,我担心在任何给定时间达到允许的线程数限制)。
using System;
using System.Threading.Tasks;
using NUnit.Framework;
using PostSharp.Aspects;
namespace Test
{
[TestFixture]
public class TestClass
{
[Test]
public void MyTest()
{
MyFirstMethod();
MySecondMethod();
}
[PerformanceInstrument]
private void MyFirstMethod()
{
//do nothing
}
private void MySecondMethod()
{
for (int x = 0; x < 9999999; x++);
}
}
[Serializable]
public class PerformanceInstrument : MethodInterceptionAspect
{
public override void OnInvoke(MethodInterceptionArgs args)
{
var startDtg = DateTime.Now;
args.Proceed();
var duration = DateTime.Now - startDtg;
Task.Factory.StartNew(() => MyLogger.MyLoggingMethod(duration)); //invoke the logging method asynchronously
}
}
public static class MyLogger
{
public static void MyLoggingMethod(TimeSpan duration)
{
for (int x = 0; x < 9999999; x++);
Console.WriteLine(duration);
}
}
}
我在这里看到的唯一可能的缺点是管理任务的开销,我确信这可能微不足道,但我还没有深入研究 TPL 内容来确定。
我在大型 Web 应用程序中使用的另一种方法是让日志记录将日志消息记录写入内存列表,然后我有一个后台线程负责在后台写入日志消息。目前,该解决方案让线程经常检查列表,如果列表长度超过特定阈值或列表没有刷新超过特定时间,则将列表刷新到磁盘(在我们的示例数据库中),这永远是第一位的。
这类似于生产者/消费者模式,您在其中编码生成日志消息,而消费者负责将这些消息刷新到持久介质。
您的方法可能会产生意想不到的后果,因为 ASP.NET 引擎和任务并行库都是在 .NET 线程池上调度任务。每个 Web 请求都由线程池中的一个线程提供服务。如果您安排任务来处理日志记录,那么您将使用线程池上的任务,而该线程池将不再用于服务 Web 请求。这可能会降低吞吐量。
TPL 团队在博客中对此进行了介绍:在 ASP.NET 应用程序中使用 .NET 4 的并行扩展。
生产者/消费者模式意味着您的 MethodInterceptionAspect 只需将一个条目添加到全局队列中(如 Ben 建议的那样),然后您将拥有一个处理所有条目的(长时间运行的)任务。所以你的插播方法就变成了:
ConcurrentQueue<TimeSpan> _queue;
public override void OnInvoke(MethodInterceptionArgs args)
{
var startDtg = DateTime.Now;
args.Proceed();
var duration = DateTime.Now - startDtg;
Task.Factory.StartNew(() => _queue.Add(duration));
}
在其他地方处理队列:
foreach (var d in _queue.GetConsumingEnumerable())
{
Console.WriteLine(d);
}
下面的文章展示了一个类似的实现,其中由 Parallel.For 循环创建的多个任务将图像添加到 BlockingCollection 中,并且单个任务处理图像。
它的工作效果在一定程度上取决于您的请求处理的长度、每个请求要处理的日志条目数以及总体服务器负载等。您必须注意的一件事是,总体而言您需要能够从队列中删除请求的速度比添加请求的速度更快。
您是否考虑过编写自己的性能计数器并让性能计数器基础设施为您处理繁重的工作的方法?这将节省您实施任何此类记录基础设施的需要。