当有大量消息排队时,从MSMQ读取速度会变慢

时间:2011-06-27 10:22:02

标签: c# .net msmq message-queue

简介

我有一个基于SEDA的系统,并使用MSMQ在不同的应用程序/服务之间进行通信(事件触发)。

其中一个服务按文件获取消息,因此我有一个文件监听器,它读取文件内容并将其插入队列(或实际上是4个不同的队列,但这对第一个问题不是很重要)。

服务器是Windows Server 2008

第一个问题 - 阅读速度慢下来

我在另一端读取这些消息的应用程序通常每秒从队列中读取大约20条消息,但是当发布消息的服务开始排队数千条消息时,读取​​将关闭,并且读取应用程序仅读取2-每秒4条消息。当没有发布到队列时,读取的应用程序可以再次读取每秒最多20条消息。

阅读应用程序中的代码非常简单,使用C#开发,我使用System.Messaging中的Read(TimeSpan超时)函数。

问:为什么在队列中发布了大量消息时读取速度会变慢?

第二个问题 - TPS的限制

另一个问题是阅读本身。如果我使用1或5个线程从队列中读取,那么我每秒可以读取的消息数似乎没有区别。我也试过实现一个“循环解决方案”,其中邮政服务发布到一组随机的4个队列,并且读取的应用程序有一个线程监听这些队列中的每一个,但即使我仍然只有20个TPS从1个队列中读取1个线程,1个队列,4个线程或4个队列(每个队列一个线程)。

我知道线程中的处理大约需要50毫秒,所以如果当时只处理一条消息,那么20 TPS是非常正确的,但多线程的线索应该是并行处理消息而不是顺序消息。

服务器上有大约110个不同的队列。

问:为什么即使使用多线程和使用多个队列,我也不能从队列中收到超过20条消息?

这是今天运行的代码:

// There are 4 BackgroundWorkers running this function
void bw_DoWork(object sender, DoWorkEventArgs e) 
{
    using(var mq = new MessageQueue(".\\content"))
    {
        mq.Formatter = new BinaryMessageFormatter();

        // ShouldIRun is a bool set to false by OnStop()
        while(ShouldIRun)
        {
            try
            {
                using(var msg = mq.Receive(new TimeSpan(0,0,2))
                {
                    ProcessMessageBody(msg.Body); // This takes 50 ms to complete
                }
            }
            catch(MessageQueueException mqe)
            {
               // This occurs every time TimeSpan in Receive() is reached
               if(mqe.MessageQueueErrorCode == MessageQueueErrorCode.IOTimeout) 
                   continue;
            }
        }
    }

但即使有4个线程,似乎所有人都等待该功能再次进入“接收”点。我也尝试使用4个不同的队列(content1,content2,content3和content4),但我仍然每50毫秒处理1条消息。

这是否与Receive()中的TimeSpan有关,和/或是否可以省略它?

另一个问题是如果使用私有队列,in​​stad公共会解决什么问题吗?

2 个答案:

答案 0 :(得分:4)

效果问题。
您没有提及服务器上是否正在运行所有代码,或者您是否有客户端远程访问服务器上的队列。从速度上来说,我会假设后者 队列是交易的吗? 消息有多大?

如果要从队列中读取消息,则应用程序不会连接到队列本身。一切都在本地队列管理器和远程队列管理器之间进行。队列管理器是写入队列和从队列读取的唯一进程。因此,拥有多个队列或单个队列不一定会有任何不同。

因此,MSMQ队列管理器在某些时候将成为瓶颈,因为它可以同时完成很多工作。您的第一个问题显示了这一点 - 当您在队列管理器上放置高负载并将消息放入IN时,您获取消息OUT的能力会降低。例如,我建议查看性能监视器以查看MQSVC.EXE是否被最大化。

答案 1 :(得分:2)

你为什么要使用时间跨度? - 这是一件坏事,这就是原因。

在开发服务和队列时,您需要以安全的方式进行编程。队列中的每个项目都将生成一个新线程。使用timespan强制每个线程使用单个计时器事件线程。这些事件必须在事件线程中等待轮到他们。

规范是每个队列事件1个线程 - 这通常是您的System.Messaging.ReceiveCompletedEventArgs事件。另一个线程是你的onStart事件...

每秒20个线程或20个读取可能是正确的。通常在线程池时,您只能在.net中一次生成36个线程。

我的建议是删除计时器事件,让你的队列简单地处理数据。

做更像这样的事情;

namespace MessageService 
{ 

public partial class MessageService : ServiceBase 

{ 

    public MessageService() 

    { 

        InitializeComponent(); 

    } 



    private string MessageDirectory = ConfigurationManager.AppSettings["MessageDirectory"]; 

    private string MessageQueue = ConfigurationManager.AppSettings["MessageQueue"]; 



    private System.Messaging.MessageQueue messageQueue = null; 



    private ManualResetEvent manualResetEvent = new ManualResetEvent(true); 





    protected override void OnStart(string[] args) 

    { 

        // Create directories if needed 

        if (!System.IO.Directory.Exists(MessageDirectory)) 

            System.IO.Directory.CreateDirectory(MessageDirectory); 



        // Create new message queue instance 

        messageQueue = new System.Messaging.MessageQueue(MessageQueue); 



        try 

        {    

            // Set formatter to allow ASCII text 

            messageQueue.Formatter = new System.Messaging.ActiveXMessageFormatter(); 

            // Assign event handler when message is received 

            messageQueue.ReceiveCompleted += 

                new System.Messaging.ReceiveCompletedEventHandler(messageQueue_ReceiveCompleted); 

            // Start listening 



            messageQueue.BeginReceive(); 

        } 

        catch (Exception e) 

        { 



        } 

    } 



    protected override void OnStop() 

    { 

        //Make process synchronous before closing the queue 

        manualResetEvent.WaitOne(); 





        // Clean up 

        if (this.messageQueue != null) 

        { 

            this.messageQueue.Close(); 

            this.messageQueue = null; 

        } 

    } 



    public void messageQueue_ReceiveCompleted(object sender, System.Messaging.ReceiveCompletedEventArgs e) 

    { 

        manualResetEvent.Reset(); 

        System.Messaging.Message completeMessage = null; 

        System.IO.FileStream fileStream = null; 

        System.IO.StreamWriter streamWriter = null; 

        string fileName = null; 

        byte[] bytes = new byte[2500000]; 

        string xmlstr = string.Empty;                

            try 

            { 

                // Receive the message 

                completeMessage = this.messageQueue.EndReceive(e.AsyncResult);                    

                completeMessage.BodyStream.Read(bytes, 0, bytes.Length); 



                System.Text.ASCIIEncoding ascii = new System.Text.ASCIIEncoding(); 



                long len = completeMessage.BodyStream.Length; 

                int intlen = Convert.ToInt32(len);                   

                xmlstr = ascii.GetString(bytes, 0, intlen);                   

            } 

            catch (Exception ex0) 

            { 

                //Error converting message to string                    

            } 

        }