Windows服务通过MSMQ进行通信 - 我需要服务总线吗?

时间:2011-10-08 11:24:20

标签: c# .net c#-4.0 msmq publish-subscribe

我遇到这个问题,其中系统包含推送要处理的消息的节点(Windows服务)以及其他用于提取消息和处理消息的消息。

这是通过在每次发送后维护循环队列列表和轮换队列来推送节点平衡队列之间的负载的方式设计的。因此,消息1将转到队列1,消息2转到队列2等。到目前为止,这部分一直很好用。

在消息拉出结束时,我们设计它以便以类似的方式检索消息 - 首先从队列1,然后从队列2等检索。理论上,每个拉节点位于不同的机器上,在实践中,到目前为止,它只收听一个队列。但是最近的一项要求使得我们在一台机器中有一个拉节点,它可以监听多个队列:一个通常非常繁忙并且填充了数百万条消息,一条通常只包含少量消息。

我们面临的问题是,我们最初构建拉节点的方式是从队列到队列,直到找到消息。如果它超时(比如说在一秒之后),那么它会移动到下一个队列。

这不再起作用,因为Q1(充满了数百万条消息)将被延迟大约一秒消息,因为从Q1开始每次拉动后我们都会向Q2询问消息(如果它不包含任何消息,我们将等待一秒钟)。

所以它是这样的:

Q1包含10条消息,Q2不包含

  • 拉节点请求来自Q1的消息
  • Q1立即返回消息
  • 拉节点请求来自Q1的消息
  • ------------等待一秒-------------(Q2为空,请求超时)
  • 拉节点请求来自Q1的消息
  • Q1立即返回消息
  • 拉节点请求来自Q1的消息
  • ------------等待一秒-------------(Q2为空,请求超时)

所以这显然是错误的。

我想我正在寻找最好的建筑解决方案。消息处理不需要尽可能实时,但需要健壮,不应丢失任何消息!

我想听听你对这个问题的看法。

提前感谢 雅尼斯

2 个答案:

答案 0 :(得分:1)

也许您可以在MessageQueue类中使用ReceiveCompleted事件?不需要轮询。

答案 1 :(得分:1)

我最终创建了一组线程 - 每个msmq需要处理一个线程。在构造函数中,我初始化这些线程:

Storages.ForEach(queue =>
        {
            Task task = Task.Factory.StartNew(() =>
            {
                LoggingManager.LogInfo("Starting a local thread to read in mime messages from queue " + queue.Name, this.GetType());
                while (true)
                {
                    WorkItem mime = queue.WaitAndRetrieve();
                    if (mime != null)
                    {
                        _Semaphore.WaitOne();
                        _LocalStorage.Enqueue(mime);

                        lock (_locker) Monitor.Pulse(_locker);

                        LoggingManager.LogDebug("Adding no. " + _LocalStorage.Count + " item in queue", this.GetType());
                    }
                }
            });
        });
  • _LocalStorage是一个线程安全的Queue实现(.NET 4.0中引入的ConcurrentQueue)

  • 信号量是一个计数信号量,用于控制_LocalStorage中的插入。 _LocalStorage基本上是接收消息的缓冲区,但我们不希望它在处理节点繁忙工作时变得太大。效果可能是我们检索了_LocalStorage中的所有msmq消息,但是正忙于处理它们中的5个左右。这在弹性方面都很糟糕(如果程序意外终止我们丢失了所有这些消息),并且在性能方面也是如此,因为将所有这些项目保存在内存中的内存消耗将是巨大的。因此,我们需要控制_LocalStorage缓冲区队列中保存的项目数。

  • 我们Pulse线程等待工作(见下文),通过执​​行简单的Monitor.Pulse将新项目添加到队列中

从队列中取出工作项的代码如下:

lock (_locker)
            if (_LocalStorage.Count == 0) 
                Monitor.Wait(_locker);

        WorkItem result;
        if (_LocalStorage.TryDequeue(out result))
        {
            _Semaphore.Release();
            return result;
        }

        return null;

我希望这可以帮助某人找出类似的问题。