我有一个在azure中运行的辅助角色。
此worker处理一个存在大量整数的队列。对于每个整数,我必须进行相当长的处理(根据整数从1秒到10分钟)。
由于这非常耗时,我想并行处理这些处理。不幸的是,当我使用400个整数的队列进行测试时,我的并行化似乎效率不高。
这是我的实施:
public class WorkerRole : RoleEntryPoint {
private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
private readonly ManualResetEvent runCompleteEvent = new ManualResetEvent(false);
private readonly Manager _manager = Manager.Instance;
private static readonly LogManager logger = LogManager.Instance;
public override void Run() {
logger.Info("Worker is running");
try {
this.RunAsync(this.cancellationTokenSource.Token).Wait();
}
catch (Exception e) {
logger.Error(e, 0, "Error Run Worker: " + e);
}
finally {
this.runCompleteEvent.Set();
}
}
public override bool OnStart() {
bool result = base.OnStart();
logger.Info("Worker has been started");
return result;
}
public override void OnStop() {
logger.Info("Worker is stopping");
this.cancellationTokenSource.Cancel();
this.runCompleteEvent.WaitOne();
base.OnStop();
logger.Info("Worker has stopped");
}
private async Task RunAsync(CancellationToken cancellationToken) {
while (!cancellationToken.IsCancellationRequested) {
try {
_manager.ProcessQueue();
}
catch (Exception e) {
logger.Error(e, 0, "Error RunAsync Worker: " + e);
}
}
await Task.Delay(1000, cancellationToken);
}
}
}
ProcessQueue的实现:
public void ProcessQueue() {
try {
_queue.FetchAttributes();
int? cachedMessageCount = _queue.ApproximateMessageCount;
if (cachedMessageCount != null && cachedMessageCount > 0) {
var listEntries = new List<CloudQueueMessage>();
listEntries.AddRange(_queue.GetMessages(MAX_ENTRIES));
Parallel.ForEach(listEntries, ProcessEntry);
}
}
catch (Exception e) {
logger.Error(e, 0, "Error ProcessQueue: " + e);
}
}
和ProcessEntry
private void ProcessEntry(CloudQueueMessage entry) {
try {
int id = Convert.ToInt32(entry.AsString);
Service.GetData(id);
_queue.DeleteMessage(entry);
}
catch (Exception e) {
_queueError.AddMessage(entry);
_queue.DeleteMessage(entry);
logger.Error(e, 0, "Error ProcessEntry: " + e);
}
}
在ProcessQueue函数中,我尝试使用不同的MAX_ENTRIES值:first = 20然后= 2。 MAX_ENTRIES = 20似乎更慢,但无论MAX_ENTRIES的值是多少,它似乎都很慢。
我的虚拟机是A2媒体。
我真的不知道我是否正确地进行了并行化;也许问题来自工人本身(这可能很难并行)。
答案 0 :(得分:2)
您还没有提到您正在使用哪种Azure消息队列技术,但是对于我想要并行处理多个消息的任务,我倾向于在服务总线队列上使用消息泵模式和订阅,利用Service Bus队列和订阅客户端上可用的OnMessage()方法:
来自MSDN:
调用OnMessage()时,客户端启动内部消息泵 不断轮询队列或订阅。这个消息泵 由发出Receive()调用的无限循环组成。如果是电话 超时,它会发出下一个Receive()调用。
此模式允许您使用委托(或在我的首选案例中使用匿名函数)处理WaWorkerHost进程上单独线程上的Brokered Message实例的接收。实际上,为了提高吞吐量级别,您可以指定消息泵应提供的线程数,从而允许您并行地从队列接收和处理2,4,8个消息。您还可以告诉消息泵在委托成功处理完消息后自动将消息标记为已完成。线程计数和自动完成指令都在重载方法的 OnMessageOptions 参数中传递。
public override void Run()
{
var onMessageOptions = new OnMessageOptions()
{
AutoComplete = true, // Message-Pump will call Complete on messages after the callback has completed processing.
MaxConcurrentCalls = 2 // Max number of threads the Message-Pump can spawn to process messages.
};
sbQueueClient.OnMessage((brokeredMessage) =>
{
// Process the Brokered Message Instance here
}, onMessageOptions);
RunAsync(_cancellationTokenSource.Token).Wait();
}
如果需要,您仍然可以利用RunAsync()方法在主Worker Role线程上执行其他任务。
最后,我还建议您将工作者角色实例扩展到最少2个(用于容错和冗余)以提高整体吞吐量。从我在此模式的多个生产部署中看到的情况来看,OnMessage()在多个Worker角色实例运行时表现完美。
答案 1 :(得分:1)
这里要考虑的一些事情:
您的个人任务是否为CPU密集型?如果是这样,并行性可能无济于事。但是,如果他们大多等待数据处理任务由其他资源处理,那么并行化是一个好主意。
如果并行化是一个好主意,请考虑不使用Parallel.ForEach进行队列处理。 Parallel.Foreach有两个问题阻止你做到最优:
代码将等到所有启动线程完成处理后再继续。因此,如果您有5个线程需要10秒,1个线程需要10分钟,则Parallel.Foreach的总处理时间将为10分钟。
即使您假设所有线程将同时开始处理,Parallel.Foreach也不会以这种方式工作。它查看服务器上的核心数量和其他参数,通常只会启动它认为可以处理的线程数,而不必过多了解这些线程中的内容。因此,如果你有很多非CPU绑定线程可以/同时启动而不会导致CPU过度使用,那么默认行为将不太可能以最佳方式运行它们。
如何以最佳方式执行此操作: 我确信那里有很多解决方案,但作为参考,我们在CloudMonix中构建它的方式(必须启动数百个独立线程并尽可能快地完成它们)是使用ThreadPool.QueueUserWorkItem并手动保持正在运行的线程数。
基本上,我们使用Thread-safe集合来跟踪由ThreadPool.QueueUserWorkItem启动的正在运行的线程。线程完成后,从该集合中删除它们。队列监视循环不支持在该集合中执行逻辑。如果处理集合未达到您发现最佳的限制,则队列监视逻辑将从队列中获取消息。如果集合中有空间,它会尝试从队列中拾取更多消息,将它们添加到集合中并通过ThreadPool.QueueUserWorkItem启动它们。当处理完成时,它会启动一个清除集合中线程的委托。
希望这有帮助并且有意义