Monday, December 24, 2007

Asynchronous Web Services with MSMQ

Learn how to overcome a common scalability limitation of Web Services.
Based on an article by Josh Lane

Many of the first Web Services you create are likely to be synchronous, where processing for each service invocation is handled at the time of the invocation. However, Web Services of this sort can have scalability limitations. You can overcome these issues by using Microsoft Message Queue (MSMQ) with Web Services.

Imagine you've decided to implement a help-desk-request logging system for a large IT department as a Web Service. This web service defines a single public SubmitRequest() method. The user request is processed immediately, regardless of current server load or database availability. This can limit scalability and robustness.

Public Function SubmitRequest(ByVal request As HelpDeskRequest)
Try
Dim oldWriter as New OldSystemWriter
oldWriter.SubmitRequest(request)
dim newWriter as New NewSystemWriter
newWriter.SubmitRequest(request)
ContextUtil.SetComplete()
Catch ex As Exception
EventLog.WriterEntry("HelpDeskRequestProcessor", _
ex.Message,EventLogEntryType.Error )
ContextUtil.SetAbort()
End Try

You can solve scalability problems by changing the web service from a synchronous request model to an asynchronous request model, introducing an intermediate component into the request-logging architecture that can take requests rapidly and guarantee their eventual delivery.

Such an asynchronous system can be built using MSMQ, a middleware subsystem that comes pre-installed on all Windows 2000 and upwards. It provides a means of storing messages of arbitrary content for forwarding or retrieval in operating-system-level structures called queues. MSMQ queues are Distributed Transaction Coordinator (DTC)-aware resource managers, meaning that send and receive operations against a queue can operate within a COM+ transaction. You can access the full complement of MSMQ services from the System.Messaging API in .NET.

First, you need to build a component that's triggered when the request message queue on the server receives a help request. This component removes the message from the queue and performs the processing.

The trigger calls this method each time a new message arrives in the
target queue. Note that the queue path is passed in as a parameter. Also
note that all work is performed inside a COM+ transaction, so the MSMQ
receive operation and both SQL Server write operations are treated as
a single atomic operation.


Public Sub ProcessMessage(ByVal path As String) _
Implements IMsgHandler.ProcessMessage
Dim mq As MessageQueue
Dim m As Message
Dim oldWriter as IRequestWriter
Dim newWriter as IRequestWriter
Try
mq = New MessageQueue( path )
m = mq.Receive()
m.Formatter = New XmlMessageFormatter( _
New Type() { GetType( _
VSM_ShareTypes.HelpDeskRequest ) } )
oldWriter = New OldSystemRequestWriter()
oldWriter.WriteRequest( m.Body )
newWriter
= New NewSystemRequestWriter()
newWriter.WriteRequest( m.Body )
ContextUtil.SetComplete()
Catch ex As Exception
EventLog.WriterEntry("VSM_RequestHandler", _
ex.Message,EventLogEntryType.Error )
ContextUtil.SetAbort()
Finally
mq.Dispose()
End Try
End Sub

Create an MSMQ Trigger Rule and register the COM+ components using regsvcs.exe. Trigger rules allow your components to be invoked automatically as a result of messages arriving at the target queue. You can use MSMQ triggers to process queue messages instead of implementing a traditional queue listenener; be sure you understand the usage semantics of each option before choosing one.

New Rule (set up in the Computer Management Console):
Invoke COM component
Component ProgID: VSM_MessageHandler.HelpDeskRequestHandler
Method Name: ProcessRequest

Here the public web service is responsible only for receiving incoming requests and writing them to the target request queue. This simple, fast operation minimizes the work performed by the web server and allows you to maximize precious web server resources and minimize client response times. Note the use of a local transaction instead of a COM+ transaction (which would be overkill since it involves only a single resource manager - the queue).


Public Function SubmitOrder( ByVal order As HelpDeskRequest)
Dim mqt As New MessageQueueTransaction()
Dim mq As MessageQueue
Try
mq = New MessageQueue(m_QueuePath)
mqt.Begin()
mq.Send(order)
mqt.Commit()
Catch ex As Exception
EventLog.WriteEntry( "VSM_RequestTaker", _
ex.Message, EventLogEntryType.Error )
mqt.Abort()
Finally
mq.Dispose()
End Try
End Function

Also see Using MSMQ message triggers (PDF file)

No comments: