C# 生产者消费者多生产者多队列单消费者
我们有一个一般的日志记录问题,我们做3种类型的日志记录(比如说:跟踪、审计、计数)是我们做日志记录的3个方面。我们同时执行这些操作,从REST web服务中运行的代码,到传入的每个web请求 对于每个请求,我们都会对每个日志记录区域进行多次调用。比如说,每个web请求、一些跟踪、一些审计、一些计数器平均总共大约有100个日志调用,每个web请求总共有100个调用。每次日志调用都会将一些数据写入某个存储以供以后处理[存储是什么并不重要,但将其放入该存储肯定是受I/O限制的(实际上它是一个azure队列,因此是跨interweb的HTTP调用)] 我们的问题是,记录任何信息(在这三个区域中的任何一个区域)的行为需要服务请求线程太长的时间来写入所有日志接收器100次。我们测量到,多达四分之五的请求处理时间用于写入日志接收器!因此,您可以看到我们需要对其进行显著优化 我们希望加快速度,并将请求线程从日志写入中解放出来。我们希望使用另一个线程在后台以自己的速度将日志数据写入3个日志存储。因此,我们认为在内存队列中对日志写入进行排队,以便日志写入线程能够在多个请求进入时并行处理它们,这是一种可行的方法 Stephen Cleary的书《C#Cookbook中的并发》(fab reference)指出,在这种情况下,使用像C# 生产者消费者多生产者多队列单消费者,c#,multithreading,asynchronous,concurrency,producer-consumer,C#,Multithreading,Asynchronous,Concurrency,Producer Consumer,我们有一个一般的日志记录问题,我们做3种类型的日志记录(比如说:跟踪、审计、计数)是我们做日志记录的3个方面。我们同时执行这些操作,从REST web服务中运行的代码,到传入的每个web请求 对于每个请求,我们都会对每个日志记录区域进行多次调用。比如说,每个web请求、一些跟踪、一些审计、一些计数器平均总共大约有100个日志调用,每个web请求总共有100个调用。每次日志调用都会将一些数据写入某个存储以供以后处理[存储是什么并不重要,但将其放入该存储肯定是受I/O限制的(实际上它是一个azure
BlockingCollection
这样的阻塞集合是理想的。这样,一次一个线程可以生成数据(并写入内存队列),另一个线程可以使用内存队列中的数据。看起来这对我们的情况很理想
然而,在我们的例子中,因为我们在ASP.NET主机上运行,并且线程池线程对于web服务器非常宝贵(为了可伸缩性),所以我们只希望有一个线程专用于[消耗]所有3个日志队列。让所有其他线程处理入站web请求,并[生成]日志数据
因此问题变成了:如何使用3个BlockingCollection
实例(每种日志类型一个),并支持:
有人能想到一种设计模式在这里很有效吗?我们缺少的一点是,消费者如何有效地清空所有三个队列,而不阻塞其中任何一个队列,并持续处理所有三个队列。以下是我在类似场景中成功使用的模式 首先,我通常使用
ConcurrentQueue
,因为没有任何需要阻止的场景。这可能会因日志记录量的不同而有所不同。队列包含在缓冲区
类中。它完全可以包含多个队列
请求线程所做的就是将项目放入队列中。在同一个线程上,缓冲区
根据最大大小检查数量,如果确定需要刷新缓冲区,则在单独的线程上执行此操作。还有一个计时器,这样即使缓冲区未满,也会刷新缓冲区。无论哪种情况,都会进行检查以确保缓冲区尚未刷新
其结果是,除非缓冲区永久充满,否则不需要总是有一个线程专用于日志,在没有消息时阻塞
您还可以通过成批而不是单独向队列发送消息来提高性能。通过发送一条包含50-100条消息的“消息”,而不是单独发送每一条消息,可以减少大量开销。您可以有一个
BlockingCollection
,其中T
是一些自定义类,可以包含这三种类型中的任何一种。您可以在该类型中使用枚举来检测该类真正包含的类型。谢谢,但从您的回答中,我看不出我们如何使用相同的使用者线程连续处理所有三个BlockingCollection
队列,而不必等待其中任何一个队列。我建议您使用单个BlockingCollection
,不是三个,我现在明白了。谢谢你的澄清。枚举描述数据,最终帮助消费者决定将其添加到哪个存储。是的,可以。谢谢@Scott,这里有一些好主意,你建议的一些优化在我们的场景中有一定的意义。我特别喜欢添加超时和最大阈值。您是否建议我们使用Task.Run()启动日志线程(从请求线程启动)?是的。我有一个通用的实现,但我找不到它,所以我再次编写它。其思想是一个通用的ISink
接口,BufferedSink
是一个实现。它又包含另一个它写入的ISink
。它还取决于IBufferedSinkSettings
接口,该接口包含最大缓冲区大小、计时器间隔,还可以指定对内部缓冲区的刷新是否应同步。