等待Service Bus OnMessage处理的消息完成

时间:2013-08-23 08:50:25

标签: azure azureservicebus

我正在使用Azure Service Bus SubscriptionClient.OnMessage方法;配置为同时处理最多5条消息。

在代码中,我需要等待所有消息完成处理才能继续(正确关闭Azure Worker角色)。我该怎么做?

SubscriptionClient.Close()会阻塞,直到所有消息都完成处理?

2 个答案:

答案 0 :(得分:6)

在SubscriptionClient或QueueClient上调用Close不会阻止。据我所知,Calling Close会立即关闭实体。我只是使用Windows Azure SDK 2.0附带的Worker Role With Service Bus Queue项目模板快速测试。我在消息进程操作中添加了一个线程休眠几秒钟,然后在它运行时关闭该角色。我看到在消息处理线程睡眠时调用了Close方法,但它肯定没有等待for消息处理完成,角色简单关闭了。

为了妥善处理这个问题,您需要执行我们在处理处理消息的任何工作者角色(Service Bus,Azure存储队列或其他任何内容)时所做的相同操作:跟踪正在处理的内容并在完成后关闭。有几种方法可以解决这个问题,但由于涉及多个线程,所有这些方法都是手动的并且在这种情况下变得混乱。

考虑到OnMessage的工作方式,您需要在操作中添加一些内容,以查看角色是否已被告知关闭,如果是,则不进行任何处理。问题是,当执行OnMessage操作时,它已经有了一条消息。您可能需要放弃该消息但不退出OnMessage操作,否则如果队列中有消息,它将继续收到消息。你不能简单地放弃消息并让执行离开动作,因为那时系统将被传递另一条消息(可能是同一条消息),并且这样做的几个线程可能导致消息得到太多的出队计数并且死了字母。此外,您无法在SubscriptionClient或QueueClient上调用Close,这将在内部停止接收循环,因为一旦您调用close,任何未完成的消息处理将在调用.Complete,.Abandon等时抛出异常消息,因为消息实体现在已关闭。这意味着您无法轻松停止传入的消息。

这里的主要问题是因为您正在使用OnMessage并通过在OnMessageOptions上设置MaxConcurrentCalls来设置并发消息处理,这意味着启动和管理线程的代码隐藏在QueueClient和SubscriptionClient中并且您不需要&# 39;对此有控制权。您没有办法减少线程数,或单独停止线程等。您需要创建一种方法将OnMessage操作线程置于他们意识到系统的状态被告知要关闭然后完成他们的消息而不退出操作,以便不再为他们分配新消息。这意味着您可能还需要将MessageOptions设置为不使用自动完成功能,并在OnMessage操作中手动调用complete。

必须完成所有这些操作可能会严重降低使用OnMessage帮助程序的实际好处。在幕后OnMessage只是设置一个循环调用接收默认超时和将消息处理到另一个线程来执行操作(松散描述)。所以你使用OnMessage方法所得到的就是不必自己编写那个处理程序,但是你遇到的问题是因为你没有自己编写那个处理程序而你没有控制权超过那些线程。第二十二条军规。如果你真的需要优雅地停止,你可能想要离开OnMessage方法,用线程编写你自己的Receive循环,并在主循环中停止接收新消息并等待所有工人结束。

一个选项,特别是如果消息是幂等的(这意味着不止一次地处理它们会产生相同的结果......无论如何你应该注意)然后如果它们在中间处理中停止,它们将简单地重新出现在队列中稍后由另一个实例处理。如果工作本身不是资源密集型而且操作是幂等的,那么这实际上可以是一种选择。与实例可能由于硬件故障或其他问题而失败时没有什么不同。当然,它没有优雅或优雅,但它确实消除了我所提到的所有复杂性,并且仍然是由于其他故障而无论如何都会发生的事情。

请注意,在告知实例关闭时会调用OnStop。你可以延迟5分钟直到布料关闭它,所以如果你的消息需要超过五分钟来处理它,如果你试图优雅地关闭它,真的很重要,一些将在处理期间被切断。

答案 1 :(得分:2)

您可以调整OnMessageAsync以等待处理消息以完成,并阻止开始处理的新消息:

以下是实施:

_subscriptionClient.OnMessageAsync(async message =>
        {
            if (_stopRequested)
            {
                // Block processing of new messages. We want to wait for old messages to complete and exit.
                await Task.Delay(_waitForExecutionCompletionTimeout);                   
            }
            else
            {
                try
                {
                    // Track executing messages
                    _activeTaskCollection[message.MessageId] = message;

                    await messageHandler(message);
                    await message.CompleteAsync();
                }
                catch (Exception e)
                {
                    // handle error by disposing or doing nothing to force a retry
                }
                finally
                {
                    BrokeredMessage savedMessage;

                    if (!_activeTaskCollection.TryRemove(message.MessageId, out savedMessage))
                    {
                        _logger.LogWarning("Attempt to remove message id {0} failed.", savedMessage.MessageId);
                    }
                }
            }

        }, onMessageOptions);

还有等待完成的Stop的实现:

  public async Task Stop()
    {
        _stopRequested = true;
        DateTime startWaitTime = DateTime.UtcNow;

        while (DateTime.UtcNow - startWaitTime < _waitForExecutionCompletionTimeout && _activeTaskCollection.Count > 0)
        {
            await Task.Delay(_waitForExecutionCompletionSleepBetweenIterations);
        }

        await _subscriptionClient.CloseAsync();
        }

请注意,_activeTaskCollection是一个ConcurrentDictionary(我们也可以使用带有互锁的计数器来计算正在进行的消息数,但是使用字典可以让您在发生错误时轻松调查发生的事情。