基于任务的编程与应用程序的多个实例具有相同的效率



我创建了一个windows服务,它使用来自RabbitMq的消息。消耗是以异步方式完成的,消息被推送到服务。

即使只是在同一台服务器上创建更多的服务实例,它也能很好地横向扩展,并获得近似线性的性能提升。由于我们对吞吐量有一个明确的目标,我发现通过在windows服务中简单地创建n个任务来控制扩展比部署n个服务实例更简单。

重写windows服务以简单地启动n个任务,每个任务都运行"MQ消耗"代码,因此本质上做windows服务的多个实例所做的事情,不会产生相同的性能,实际上根本没有性能增益,实际上似乎在代码执行上有更大的开销!。

我确实知道存在日程安排问题,但底线问题是。是否可以使用任务并获得与启动应用程序的多个实例"大致"相同的性能,如果是,实现会是什么样子,因为我当前的设置扩展性很差。

一些代码片段:

这是基本的任务创建,每个任务都有一个taskwrapper实例,它基本上是消费和处理消息的整个业务逻辑的抽象:

public void Start()
{
for (var i = 0; i < _levelOfConcurrency; i++)
{
var task = Task.Factory.StartNew(() => new TaskWrapper().TaskRun(_cancelationTokenSource.Token,_sleeptime),
TaskCreationOptions.LongRunning);
_tasks[i] = task;
Console.WriteLine("Task created {0}", i);
}
}
public void Stop()
{
_cancelationTokenSource.Cancel();
Task.WaitAll(_tasks);
}

这是每个任务在taskwrapper类的运行循环中基本上都有的内容:

public void TaskRun(CancellationToken cancellationToken,TimeSpan sleeptime)
{
_semaphoreDataController.Start(); // Process messages async, fully selfcontained/threaded
//Keep alive and occasionally check if cancelation is requested
while (!cancellationToken.IsCancellationRequested)
{
Thread.Sleep(sleeptime);
}
Dispose();
}

我希望有人能启发我:-D

问题的核心:

它几乎不取决于你的编程风格和程序本身。

使用一个多线程进程可以获得与使用多个实例相同的性能。但正如我所说,这取决于你的服务。我建议你阅读这本书的在线版本:用.net进行并行编程。它让我对PLinq、任务和线程大开眼界。

但最终,它都在CPU上运行,进程间通信比线程间通信更复杂。

因此,进一步的研究已经确定了代码中的主要限制因素。

我使用与RabbitMQ通信的方式是通过Burrow.NET,它将为应用程序创建一个专用通道来与队列通信。

这意味着,即使有数千个并行任务正在运行,它们也只能从一个通道的队列中消耗,从而限制了服务的速度。

拥有多个服务实例,为RabbitMQ提供n个通道,从而获得线性性能增益。

DedicatedPublishingChannel作为其名称仅用于发布消息,因此我认为它不会造成消耗消息的速度问题。此外,尽管队列中只有1个通道可供订阅,但与处理这些消息的时间相比,将消息传递到应用程序的速度总是非常快。我已经制作了一个应用程序,可以将数百万条消息(Tweet-json)从队列迁移到MongoDB,而这始终是向数据库写入数据的瓶颈。

Burrow.NET处理任务管理,为您并行处理消息:

ITunnel tunnel;
// Create tunnel
tunnel.Subscribe(new SubscriptionOption<Message>
{
BatchSize = 10, // Number of threads you want to process those messages
MessageHandler = message => 
{
// Process your message here
},
QueuePrefetchSize = 10, // Should be double the message processing speed
SubscriptionName = "Anything"
});
// Message will be acked automatically after finish

在我看来,我只会使用尽可能有限的IO资源。我遇到了一个问题,我的应用程序创建了太多的通道,占用了服务器的内存。如果应用程序创建许多不必要的连接,情况会更糟,这就是为什么Burrow.NET将帮助您尽可能减少连接/通道。

如果你认为你的消息处理速度足够快,你可以用一个高数字设置预取大小,并用BatchSize(又名线程数)进行调整。例如,如果我的应用程序可以以5毫秒/秒的速度处理来自队列的消息,我会保持QueuePrefetchSize=10。

如果您将上面的方法与auto-ack一起使用,您就不必担心不会对消息进行ack,因为它会阻止新消息的到来。例如,如果QueuePrefetchSize=10,则10个线程将并行处理10条消息。你决定使用隧道。SubscribeAsync异步确认消息,但由于某些原因(未处理的异常:D可能是),其中4条消息未得到确认,这相当于QueuePrefetchSize为6。因此,只有6条消息可以同时处理,并且速度会适当降低。所以请尝试上面的方法,看看效果如何。

最新更新