2016-07-10 414 views
-1

我们有一个常见的日志记录问题,我们做3种类型的日志记录(让我们说:跟踪,审计,计数)是我们记录的3个方面。我们同时从REST Web服务中运行的代码为每个请求提供所有请求。生产者 - 消费者多个生产者多个队列单个消费者

对于每个请求,我们会对每个日志记录区域进行多次调用。比方说,平均每个Web请求约100次日志调用,一些跟踪,一些审计,一些计数器,总共100次调用,每个Web请求。每个日志记录调用都会将一些数据写入某个存储以供稍后处理[不重要的是该存储是什么,但将其存入该存储肯定是I/O绑定的 - (实际上它是一个天蓝色的队列,因此它是跨网间的HTTP调用) ]。

我们的问题是,记录任何信息(3个区域中的任何一个)的行为会使服务请求线程太长而无法写入所有日志接收器100次。我们已经测量了请求处理时间花费在记录接收器上的时间的四分之三。所以,你可以看到我们需要显着优化!

我们想加快速度,并从日志写入中释放请求线程。我们希望使用另一个线程在后台按自己的步调将日志记录数据写入3个日志记录存储区。所以我们认为将日志记录排队写入内存队列中,以便日志写入线程可以并行处理它们并发出多个请求,这是一条路。

Stephen Cleary的书“C#Cookbook中的并发”(fab参考)指出,在这种情况下使用像BlockingCollection<T>这样的阻塞集合是理想的。因此,一次一个线程可以生成数据(并写入内存队列),另一个线程可以使用内存队列中的数据。似乎这对我们的情况是理想的。然而,在我们的例子中,因为我们运行在ASP.NET主机中,并且线程池线程对于Web服务器非常宝贵(为了可伸缩性),我们只希望有一个线程专用于[消费]全部3个日志队列。让所有其他线程处理入站Web请求,并[生成]记录数据。

所以问题变为:如何使用的BlockingCollection<T> 3个实例(每个类型的日志记录),并支持:

  1. 任何用于产生数据的生产者[呼入web请求线程]的
  2. 一个单独的(专用)日志线程使用者,可以持续有效地使用所有三个队列。

任何人都可以想到一个设计模式,可以在这里工作吗?对我们来说缺少的一件事是消费者如何有效地清空所有三个队列,而不会阻塞任何队列,并且不断处理所有三个队列。

+0

你可以有一个'BlockingCollection '其中'T'是一些自定义的类,可以包含任何三种类型。您可以在此类型中使用枚举来检测此类真正拥有哪种类型。 –

+0

谢谢,但我没有看到你的回答,我们如何处理所有三个'BlockingCollection '队列与同一个消费者线程,连续不等待任何一个队列。 –

+0

我建议你有一个'BlockingCollection',而不是三个。 –

回答

1

以下是我在类似场景中成功使用的模式。

首先,我通常使用ConcurrentQueue<T>,因为没有任何情况需要阻止。也许这将取决于您的日志记录的数量。该队列包含在Buffer类中。它可以包含多个队列。

所有请求线程都会将项目放入队列中。在同一个线程上,Buffer正在根据最大大小检查数量,如果它确定需要刷新缓冲区,它将在单独的线程上执行此操作。还有一个定时器,即使缓冲区未满,缓冲区也会被刷新。无论哪种情况,都需要进行检查以确保缓冲区尚未刷新。

结果是,除非缓冲区是永久填满的,否则不需要是始终专用于日志的线程,当没有消息时阻塞。

您还可以通过批量发送消息而不是单独发送消息来提高性能。您可以通过发送一个包含50-100条消息的“消息”来消除大量开销,而不是单独发送每条消息。

+0

感谢@Scott,这里有一些很好的想法,以及您建议的一些优化在我们的场景中有一定意义。我特别喜欢增加超时以及最大阈值。你是否建议我们使用Task.Run()踢日志线程(从请求线程)? –

+1

是的。我有一个通用的实现,但我找不到它,所以我再次写它。这个想法是一个通用的'接口','BufferedSink '是一个实现。这又包含它写入的另一个“ISink ”。它还依赖于包含最大缓冲区大小,定时器间隔的'IBufferedSinkSettings'接口,并且还可以指定对内部缓冲区的刷新是否应该同步。 –