MSMQ Receive() 方法超时

13

我之前的问题是MSMQ Slow Queue Reading,但是我已经有所进展,现在我认为我更清楚地知道了问题所在。

我的代码(实际上是我正在使用的开源库的一部分)看起来像这样:

queue.Receive(TimeSpan.FromSeconds(10), MessageQueueTransactionType.Automatic);

这里使用Messaging.MessageQueue.Receive函数,队列是一个MessageQueue。问题如下:
以上的代码行将在指定的超时时间(10秒)内调用。Receive(...)函数是一个阻塞函数,应该一直阻塞到队列中有消息到达,此时它将返回消息。如果在超时之前没有收到消息,则会在超时时返回。如果在调用函数时队列中有消息,则会立即返回该消息。
然而,发生的情况是Receive(...)函数被调用后看到队列中没有消息,因此等待新的消息进入。当有新消息到达(在超时之前),它没有检测到这个新消息并继续等待。最终达到超时时间,在这一点上,代码会继续并再次调用Receive(...),在那里它接收消息并处理它。
现在,这个问题只会在几天/几周后出现。我可以通过删除和重新创建队列来让它正常工作。它在不同的计算机和不同的队列上都会发生。因此,似乎某些东西正在逐渐积累,直到某个点打破了Receive(...)函数使用的触发/通知能力。
我已经检查了很多不同的事情,一切似乎正常,并且与正常工作的队列没有什么不同。磁盘空间充足(剩余13GB),RAM也足够(根据我的估计,1GB中大约有350MB可用)。我已经检查了注册表项,它们都与其他队列相同,性能监视器也没有显示任何异常。我还运行了TMQ工具,也没有发现明显的问题。
我在所有机器上都使用Windows XP,并安装了服务包3。我没有向队列发送大量消息,最多每2秒钟发送1条,但通常比这频率要低得多。消息也很小,远未达到4MB的限制。
我刚刚注意到的唯一一件事是C:\ WINDOWS \ system32 \ msmq \ storage中的p0000001.mq和r0000067.mq文件均为4,096KB,但其他未出现问题的计算机上也是这个大小。该问题并不会同时发生在计算机上的每个队列上,因为我可以在计算机上重现1个问题队列,而其他队列仍然会遇到问题。
我对MSMQ并不是很有经验,如果您发布了可能需要检查的内容,请说明如何检查或在哪里可以找到更多关于您所说的内容的详细信息。
目前的情况是:
  • 计算机A - 4个队列正常
  • 计算机B - 2个队列出现问题,1个队列正常
  • 计算机C - 2个队列出现问题
  • 计算机D - 1个队列正常
  • 计算机E - 2个队列正常

所以我有很多台计算机/队列需要进行比较和测试。


使用了哪个开源库?这是他们那边已知的问题吗? - call me Steve
这是NServiceBus(www.nservicebus.com)。似乎没有其他人遇到过这个问题(至少邮件列表上没有)。我的解释是基于所有者的描述。 "在处理完前一个消息(或在启动时),NServiceBus线程会查看队列以获取消息。查看的常规行为是阻塞,直到有消息到达,但NServiceBus通过超时限制了这一点,以便在任何时间点都可以进行优雅的关闭。"我粘贴的代码行就是所提到的“查看”。 - mike
1
嗯,我不是该领域的专家,但看起来queue.Receive的第二个参数在你的情况下可能不太合适,也许改为"MessageQueueTransactionType.None"(如果你不需要事务功能)会更好。 第二种解决方法(依我之见)是将超时功能移出此调用,即在此调用中仅等待0秒,并在代码的其他位置进行等待。 我理解这些选项可能不是理想的,因为你可能需要更改库或代码的内容。 祝你好运。 - call me Steve
很遗憾,我没有这个问题的答案,但是我也遇到了同样的行为。不过,我想补充一点信息。只有在我从vs调试我的应用程序时才会出现这种“减速”。如果我在没有调试的情况下运行应用程序,它就能正常运行。 - Brian
当您将超时时间设置为较低或较高(五秒或二十秒)时,是否会有任何变化?这是您可以测试的吗?最具体地说,队列失败所需的时间是否随着您使用的超时长度而变化? - SamuelWarren
显示剩余3条评论
4个回答

9

你没有使用事件处理程序监听队列的特殊原因吗?System.Messaging库允许你将处理程序附加到队列而不是每10秒循环接收(如果我正确理解了你的做法)。尝试像这样使用:

class MSMQListener
{
    public void StartListening(string queuePath)
    {
        MessageQueue msQueue = new MessageQueue(queuePath);
        msQueue.ReceiveCompleted += QueueMessageReceived;
        msQueue.BeginReceive();
    }

    private void QueueMessageReceived(object source, ReceiveCompletedEventArgs args)
    {
        MessageQueue msQueue = (MessageQueue)source;

        //once a message is received, stop receiving
        Message msMessage = null;
        msMessage = msQueue.EndReceive(args.AsyncResult);

        //do something with the message

        //begin receiving again
        msQueue.BeginReceive();
    }
}

主要原因是您无法从事务队列异步接收。这个限制可以追溯到本机API - acelent

4

我们也在使用NServiceBus,在我们的网络中遇到了类似的问题。

基本上,MSMQ使用UDP进行两阶段提交。在接收到消息后,必须进行确认。在没有确认之前,客户端无法接收该消息,因为接收事务尚未完成。

对于我们来说,这在不同的时间是由不同的原因引起的:

  • 一次,这是由于分布式事务协调器无法在机器之间通信,因为防火墙配置错误。
  • 另一次,我们正在使用没有经过sysprep克隆的虚拟机,这使得内部MSMQ id不唯一,并将其接收到一个机器,然后对另一个机器进行确认。最终,MSMQ会解决这些问题,但需要相当长的时间。

0

如果您想使用完全同步且没有事件的内容,则可以尝试此方法。

public object Receive(string path, int millisecondsTimeout)
{
    var mq = new System.Messaging.MessageQueue(path);
    var asyncResult = mq.BeginReceive();
    var handles = new System.Threading.WaitHandle[] { asyncResult.AsyncWaitHandle };
    var index = System.Threading.WaitHandle.WaitAny(handles, millisecondsTimeout);
    if (index == 258) // Timeout
    {
        mq.Close();
        return null;
    }
    var result = mq.EndReceive(asyncResult);
    return result;
}

0

试试这个

public Message Receive(TimeSpan timeout, Cursor cursor)

重载函数。

要获取MessageQueue的光标,请调用该队列的CreateCursor方法。

当您需要读取不在队列前面的消息时,例如同步或异步读取消息时,使用光标与Peek(TimeSpan,Cursor,PeekAction)和Receive(TimeSpan,Cursor)等方法一起使用。不需要使用光标仅读取队列中的第一条消息。

在事务中读取消息时,如果事务被中止,消息队列不会回滚光标移动。例如,假设有一个包含两条消息A1和A2的队列。如果您在事务中删除消息A1,则消息队列将光标移动到消息A2。但是,如果由于任何原因中止事务,则将消息A1重新插入队列,但光标仍指向消息A2。

要关闭光标,请调用Close。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接