Confessions of a Dangerous Mind

Brain.FlushBuffer()
posts - 176, comments - 234, trackbacks - 93

Inter-Process Communication Techniques: Message Queue

Riprendiamo lo scenario del post precedente: abbiamo un sistema di raccolta ordini formato da una applicazione web, un servizio che funge da "dispatcher" ed un sistema di terze parti che è fuori dal nostro controllo, ovvero non possiamo intervenire direttamente su di esso, bensì possiamo solamente usare delle primitive TCP/IP per accedervi.
La criticità del sistema consiste nella possibile congestione delle connessioni TCP/IP al sistema di terze parti, connessioni che risultano limitate da problematiche tecniche e di licensing.
Ovviamente le connessioni all'applicazione web sono fuori dal nostro controllo, nel senso che potrebbe connettersi un utente come mille.
L'utilizzo del servizio (broker) interposto tra l'applicazione web ed il sistema di terze parti rappresenta la soluzione del problema, in quanto esso permette di differire l'esecuzione delle funzionalità del sistema di terze parti, serializzando le richieste e comunicando in modo asincrono con l'utente dell'applicazione web.

Il meccanismo che può essere utilizzato per comunicare tra l'applicazione web ed il servizio di windows è MSMQ (Microsoft Message Queue).
MSMQ, integrato nel sistema operatvo, è un server per la gestione di code di messaggi. Esso consente di creare code, immagazzinarvi messaggi, ritornare messaggi di
acknowledge alle applicazioni che lo impiegano. Il tutto è perfettamente integrato in .net grazie al namespace System.Messaging.
Nel codice seguente si può vedere come venga configurato il codice per la creazione della coda e la ricezione dei messaggi all'interno del servizio.

   1: using System.Messaging;
   2:  
   3: private MessageQueue _ordersQueue;
   4:  
   5: public void Start()
   6: {
   7:     string _ordersQueuePath = @".\private$\Orders";
   8:     //Creo o inizializzo la coda di ricezione
   9:     if (MessageQueue.Exists(_ordersQueuePath))
  10:     {
  11:         _ordersQueue = new MessageQueue(_ordersQueuePath);                
  12:     }
  13:     else
  14:     {
  15:         _ordersQueue = MessageQueue.Create(_ordersQueuePath);                 
  16:     }            
  17:     _ordersQueue.Formatter = new XmlMessageFormatter(new Type[] { typeof(CodeSapiens.Business.Order) });
  18:     _ordersQueue.MessageReadPropertyFilter.CorrelationId = true;
  19:     //Bind del gestore dell'evento ReceiveCompleted
  20:     _ordersQueue.ReceiveCompleted += new ReceiveCompletedEventHandler(ReceiveCompletedHandler);
  21:     _ordersQueue.BeginReceive();
  22: }
  23:  
  24: private void ReceiveCompletedHandler(Object source, ReceiveCompletedEventArgs asyncResult)
  25: {
  26:     Message m = _ordersQueue.EndReceive(asyncResult.AsyncResult);
  27:     //=====================================
  28:     // Elaboro il messaggio...
  29:     //=====================================                
  30:     CodeSapiens.Business.Order _order = (CodeSapiens.Business.Order)m.Body;
  31:     OrderProcessor _processor = new OrderProcessor();
  32:     _processor.ProcessOrder(_order);
  33:     //Riavvio la ricezione
  34:     _ordersQueue.BeginReceive();
  35: }

L'oggetto OrderProcessor consentirà di elaborare, tramite il sistema di terze parti, l'ordine immagazzinato nella coda, proveniente dall'applicazione web.

La coda andrà configurata impostando Full Control per l'utente NETWORK SERVICE che fa girare il servizio w3wp.exe. Con l'utente NETWORK SERVICE si farà girare anche il servizio "Broker" in modo da uniformare i permessi di accesso alla coda.

Le classi "Order" e "OrderDetail" verranno utilizzate come corpo dei messaggi della coda:

   1: namespace CodeSapiens.Business
   2: {
   3:     [Serializable()]
   4:     class Order
   5:     {
   6:         public Order()
   7:         {    
   8:             this.Details = new List<OrderDetail>();
   9:         }
  10:         public Guid Id { get; set; }
  11:         public int CustomerId { get; set; }
  12:         public string Address { get; set; }
  13:         public string CustomerEMail { get; set; }
  14:         public DateTime OrderDate { get; set; }
  15:         public List<OrderDetail> Details { get; set; }
  16:     }
  17:  
  18:     [Serializable()]
  19:     class OrderDetail
  20:     {
  21:         public OrderDetail(string productId, decimal quantity, decimal price, decimal tax)
  22:         {
  23:             this.ProductId = productId;
  24:             this.Quantity = quantity;
  25:             this.Price = price;
  26:             this.Tax = tax;
  27:         }
  28:         public string ProductId { get; set; }
  29:         public decimal Quantity { get; set; }
  30:         public decimal Price { get; set; }
  31:         public decimal Tax { get; set; }
  32:     }
  33: }

Le Classi saranno serializzabili e dovranno essere immagazzinate nella nostra coda per essere smistate dal servizio. Vediamo dunque il codice utilizzato per indirizzare la coda dalla web application e per immagazzinarvi il messaggio per l'elaborazione differita.

   1: private void SendMessage()
   2: {
   3:     string _ordersQueuePath = @".\private$\Orders";
   4:  
   5:     MessageQueue _ordersQueue = new MessageQueue(_ordersQueuePath);
   6:  
   7:     //Creo l'oggetto Ordine da immagazzinare nella coda
   8:     //Verrà in seguito elaborato dal servizio
   9:     CodeSapiens.Business.Order _order = new CodeSapiens.Business.Order();
  10:     _order.Id = Guid.NewGuid();
  11:     _order.CustomerId = "1";
  12:     _order.Address = "One Microsoft Way";
  13:     _order.CustomerEMail="info@customer.net"
  14:     _order.OrderDate = DateTime.Parse(txtReserveFrom.Text);
  15:     _order.Details.Add(new OrderDetail("ABX12", 2, 20, 10));
  16:     _order.Details.Add(new OrderDetail("XYP51", 2, 20, 10));
  17:     _order.Details.Add(new OrderDetail("ZVB11", 3, 10, 20));
  18:  
  19:     Message _message = new Message();
  20:     _message.Body = _order;            
  21:     _ordersQueue.Send(_message);
  22: }

L'uso della coda ha delle implicazioni positive che possono essere identificate nei punti seguenti:

  • non c'è connessione diretta tra la web application e il database/sistema di terze parti;
  • la sicurezza della coda è controllata dalle ACL del sistema operativo, per cui possiamo decidere che in una coda un processo possa solo leggere o solo scrivere
  • Se, per qualche motivo, il servizio va offline, la coda funge da buffer e non è necessario bloccare la web application. Non appena il servizio ritorna on-line, esso elaborerà i messaggi immagazzinati nella coda durante il periodo di mancato funzionamento
  • non si incorre nel problema della "resource starvation", in quanto un numero di utenti concorrenti potenzialmente molto elevato non va ad attingere direttamente alla risorsa critica (la connessione al sistema di terze parti) bensì alla coda, che ai nostri fini può essere considerata a capacità infinita
  • è possibile implementare meccanismi di elaborazione del messaggio anche molto complessi (validazione ordine) senza irritare l'utente, che non è costretto ad attendere on line l'esecuzione di una pagina web
  • Utilizzando le proprietà di acknowledge dei messaggi inviati e ricevuti ed una cosa amministraiva, è possibile avere la certezza che la cosa di destinazione abbia ricevuto il messaggio inviato dalla nostra applicazione

Concludendo, l'uso di MessageQueue consente di aggiungere un grado di libertà che permette di accoppiare sistemi in modo "lasco"; ulteriori integrazioni saranno possibili in tempi successivi, proprio grazie alla libertà e alla robustezza messa a disposizione dall'infrastruttura MSMQ. Ovviamente sarà nostra cura aggiungere una gestione del logging degli errori e una gestione esaustiva delle eccezioni.

Print | posted on giovedì 13 novembre 2008 23:41 |

Feedback

Gravatar

# re: Inter-Process Communication Techniques: Message Queue

Anche noi usiamo pesantemente MSMQ nelle nostre applicazioni e ultiammente ne gestiamo l'accesso tramite WCF.
però per personale esperienza direi che bisogna prestare attenzione a 3 cose molto importanti:
1 - Se la coda è transazionale i messaggi vengono salvati sul filesystem quindi in caso di spegnimento del server rimangono, mentre se la coda non è transazionale i messaggi sono solo in memoria, il che rende tutto più veloce ma allo spegnimento ... ... puff niente messaggi.
2 - Fino a MSMQ3.0 (XP e Windows 2003) le code transazionali possono essere lette SOLO dal server che le ospita, quindi non è possibile bilanciare la lettura di una coda su più server. Comunque questo limite è stato tolto con MSMQ 4.0 (Vista, Windows Server 2008)
3 - La dimensuione massima di un singolo messaggio è di 4Mb
13/11/2008 23:56 | Ivan Fioravanti
Gravatar

# re: Inter-Process Communication Techniques: Message Queue

Grazie per le integrazioni, Ivan!
Ovviamente il post non aveva la pretesa di essere esaustivo; MSMQ è un "mondo" e come tale va esplorato. Un saluto!
14/11/2008 12:33 | info@codesapiens.net
Comments have been closed on this topic.

Powered by:
Powered By Subtext Powered By ASP.NET