Pages

MSMQ over HTTP and Load Balancer - Part 2

In Part 1 we saw how to configure MSMQ to work with HTTP protocol. In this post we will send messages to remote queue and also check if our message did really reach the queue. Consider there are two servers, one where application resides and other where queue resides. We will call them Server1 and Server 2. To be specific, this post talks about remote non-transactional private queues.


Let's recall few things here again:
  1. Private queues cannot be created remotely (through code). They need to be created manually on the server.
  2. You cannot check for the existence of remote queues.
  3. Messages cannot be received over HTTP protocol. They can only be sent.
So create a new private queue 'test-queue' on Server2 and use the code below to send a message.
 // FormatName:DIRECT=http://Server2-name-or-ip/msmq/private$/test-queue  
 static readonly string destinationQueuePath = ConfigurationManager.AppSettings["DestinationQueuePath"] as string;  
   
 // Prepare message  
 var message = new Message();  
 message.Body = "Test message sent over HTTP";
 message.Label = string.Format("Message_{0}", DateTime.Now); 
   
 // Send message  
 var destQueue = new MessageQueue(destinationQueuePath);  
 destQueue.Send(message);  
Look at the format used in destinationQueuePath. The key FormatName is case sensitive, whereas the key DIRECT is not. You must specify a path with FormatName included, else it will not work. You can read more about direct format names here. It is important you understand them!

If everything worked fine, you should see a message posted to Server2 (using Server Explorer).


However it is equally likely that a message does not reach the queue or worse it reaches but is discarded or refused by MSMQ due to insufficient permissions, for example, or worst that the destination queue itself does not exist.

How to track message delivery?

Answer is using Administration Queues, note that this is just one of the ways of doing this. There are Dead-Letter queues and Journal queues too, however, I feel administration queues are easiest to work with. Administration queue is the queue where acknowledgments for your messages are received.

Do the following on Server1:
  1. Create a private queue 'test-queue-admin' (this will act as an administration queue).
  2. Add a mapping file (you can name it anything) in C:\Windows\System32\msmq\Mapping\, and define a redirection for your acknowledgments in the file so that it looks like below:
     <redirections xmlns="msmq-queue-redirections.xml">  
       <redirection>  
          <from>http://Server1-name-or-ip:8080/MSMQ/private$/test-queue-admin</from>  
          <to>http://localhost:8080/MSMQ/private$/test-queue-admin</to>   
       </redirection>  
     </redirections>  
    
It is very surprising why you don't have to add such mapping on Server2, however, if you don't add it on Server1 acknowledgments are not received. I had put considerable time to get this working, and I am happy that you don't have to! Is it because of port specification in path? I don't know. I have not found an answer to this yet. Honestly, I did not search.

Important: You must restart Message Queuing service if you add or update mapping files.

Now, just set this queue as admin queue and type of acknowledgments to receive for the message.
 // FormatName:Direct=http://Server1-name-or-ip:8080/msmq/private$/test-queue-admin  
 message.AdministrationQueue = new MessageQueue(adminQueueRemotePath);  
 message.AcknowledgeType = AcknowledgeTypes.FullReachQueue | AcknowledgeTypes.FullReceive;  
And read the acknowledgment message like this:
 // .\private$\test-queue-admin  
 var adminQueue = new MessageQueue(adminQueueLocalPath);  
 Message ack = adminQueue.ReceiveByCorrelationId(message.Id, new TimeSpan(0, 0, 2));  
Once you receive (in this case ReceiveByCorrelationId), it will be deleted from the admin queue. So be sure to check acknowledgement details, if required, using server explorer before your code does that.

What if remote queue doesn't exist?

Remember (?), it is not possible to check for the existence of the remove private queue. If you call a MessageQueue.Exists method on remote private queue you will get an exception. The workaround is to call Peek method on the queue by specifying a timeout period to read the message. You cannot use HTTP here because it is only for sending message, and not for peeking or receiving.
 var destQueue = new MessageQueue("FormatName:DIRECT=OS:Server2\\private$\\test-queue");  
   
 try  
 {  
   destQueue.Peek(new TimeSpan(0, 0, 2));  
   // destination queue exists  
 }  
 catch (MessageQueueException ex)  
 {  
   if(ex.MessageQueueErrorCode == MessageQueueErrorCode.QueueNotFound)  
   {  
    // destination queue does not exist  
   }  
 }  

Note that since this check will most likely be performed (although async way is possible) in sync, it will block the thread. Therefore be careful while specifying timeout value. Even 2 seconds is high.

Make it work with Load Balancer

For this you just have to add a mapping file (as described earlier) on respective servers which will redirect from load balancer address to physical address of the server. So if there are now two servers Server2-1 and Server2-2 where 'test-queue' is hosted and there is a load balancer, you will have to add a mapping file on each server with respective server-name-or-ip in to node.

--

No comments:

Post a Comment