我正在编写一个Windows服务来使用MSMQ消息。该服务将具有高活动时段(80k消息很快进入)和长时间不活动(可能是几天没有新消息)。
处理消息是非常网络限制的,所以我从并行性中获得了很大的好处。但是在不活动期间,我不想将一堆线程等待即将发布的消息。
MSMQ界面似乎非常关注同步工作流 - 获取一条消息,处理它,获取另一条消息等。我应该如何构建我的代码,以便在高活动期间我可以利用并行性而不是绑定在没有活动期间一堆线程?使用TPL的奖励积分。伪代码将不胜感激。
答案 0 :(得分:11)
多年来我已经完成了MSMQ(包括移动实施)的分配,你在“同步工作流程”的表征方面是正确的。这并不是说你不能通过TPL将各种消息包络并通过不同的内核处理它们......限制因素是读/写队列......本质上是一个串行操作。例如,您不能一次发送8条消息(具有8个核心的计算机)。
我有类似的需求(不使用System.Messaging命名空间)并在我阅读Campbell和Johnson的“与Microsoft.NET并行编程”一书的帮助下解决了这个问题。
查看他们的“并行任务”章节,特别是使用与每线程本地队列协作进行工作处理的全局队列(即使用“工作窃取”算法执行负载平衡的TPL)的部分。在他们的例子之后,我部分地模仿了我的解决方案。我的系统的最终版本在性能上有很大差异(从每秒23条消息到200多条消息)。
根据系统从0到80,000的时间长短,您需要采用相同的设计并将其分布在多个服务器上(每个服务器都有多个处理器和多个内核)。从理论上讲,我的设置需要不到7分钟才能完成所有80K的抛光,因此通过添加第二台计算机,它可以将其减少到约3分20秒等等等。诀窍是工作窃取逻辑。
思考的食物......
快速编辑:BTW计算机是Dell T7500工作站,配备双核四核Xeons @ 3GHz,24 GB RAM,Windows 7 Ultimate 64位版本。
答案 1 :(得分:6)
以下是我完成工作的简化版本:
while(true) {
int msgCount = 0;
Parallel.ForEach(Enumerable.Range(0,20), (i) => {
MessageQueue queue = new MessageQueue(_queuePath);
try {
msg = queue.Receive(TimeSpan.Zero);
// do work
Interlocked.Increment(ref msgCount);
catch(MessageQueueException mqex) {
if (mqex.MessageQueueErrorCode == MessageQueueErrorCode.IOTimeout) {
return; // nothing in queue
}
else throw;
}
}
if (msgCount < 20)
Thread.Sleep(1000); // nothing more to do, take a break
}
所以我尝试一次收到20条消息,计算我收到的消息。对于那些20岁,我让TPL去镇上。最后,如果我处理的消息少于20条,那么队列是空的,我会在线程休眠一秒钟,然后重新尝试。
答案 2 :(得分:3)
NServiceBus对这个问题有一个很好的概念。它被称为Distributor。这个想法是分发者可以转发要完成的工作并将其分布到任意数量的正在运行的子节点上。取决于正在进行的工作类型,例如繁重的计算与磁盘写入相比,您可以在多个进程甚至多台计算机上进行分发。
答案 3 :(得分:2)
解决方案还部分取决于消息的处理方式。
我使用了Windows Server AppFabric中托管的WorkflowService和Net.Msmq绑定以及事务队列。需要事务性net.msmq绑定来处理乱序消息处理。工作流是.Net 4.0.1状态机,消息从不同系统进入同一队列。例如,有可能让一个系统在另一个系统发送消息以实例化它之前向状态机实例发送更新。要启用无序消息处理,工作流服务主机使用BufferedReceive锁定消息,并反复重试从锁定子队列中获取消息。 BufferedReceive将最大挂起消息设置为最大可能批处理长度,因为锁定队列中的消息将返回到前面的重试队列。
WF还有许多限制设置。我的最大可能批处理长度约为20000.我已将MaxConcurrentCalls设置为64,MaxConcurrentInstances设置为20000.这导致IIS / WAS处理64个并发调用。
但是,这就是问题所在,因为工作流中的Receives是单向的,这并不意味着一旦Receive完成,生成的WF进程就会终止。接下来在我的场景中发生的事情是,在消息出列并且调用了一个WF实例(64个调用之一)之后,工作流引擎会安排许多后续步骤,其中一个是数据库操作。
问题在于64个调用可能是最大值,但如果消息消耗率高于异步进程完成率,则处理传入的消息批次将会有更多的执行线程(在我的案例WF实例)。这可能会导致意外情况发生,例如ADO.NET连接池的默认值为100作为最大连接数。这将导致进程超时,等待来自耗尽池的连接。对于此特定问题,您可以提高MaxPoolSize值,也可以使用Service Broker异步处理数据库操作(这意味着工作流程更复杂)。
希望这有助于某人。
答案 4 :(得分:2)
以下是我的操作方法(动态更改代码,以便可能出现拼写错误):
for (int i = 0; i < numberOfSimultaneousRequests; i++)
priorityQueue.BeginReceive(TimeSpan.FromDays(30), state, callback);
并且回调看起来像这样:
private void ProcessMessage(IAsyncResult asyncResult)
{
try
{
Message msg = priorityQueue.EndReceive(asyncResult);
//Do something with the message
}
finally
{
priorityQueue.BeginDequeue(null, ProcessMessage);//start processing another one
}
答案 5 :(得分:1)
只是尝试某种类似的方式,tpl似乎能够在遇到物理问题时抛出某种线程安全异常,例如尝试在tpl foreach之外创建一个sqlconnection并在循环体内使用它 - 它抛出一个异常为了我。我在进入正文之前新建了一个队列,列举了一个字符串列表,看起来没问题,我的代码在i7 2500 8gb和本地msmq上使用1路消息在500毫秒内一直处理10000个项目
答案 6 :(得分:1)
我在名为CodeRonin的博客上找到了完整的解决方案。在我看来,这是整个互联网中唯一完整的例子。谢谢CodeRonin!
http://code-ronin.blogspot.de/2008/09/msmq-transactional-message-processing.html