Utilización de las colas de mensajes no enviados para administrar los errores en la transferencia de mensajes
Los mensajes en cola pueden producir un error en la entrega. Estos mensajes que no se han podido entregar se graban en una cola de mensajes no enviados. Los errores en la entrega pueden deberse a motivos como errores de la red, una cola eliminada, una cola completa, error de autenticación o un error para entregar a tiempo.
Los mensajes en cola pueden permanecer en la cola durante mucho tiempo si la aplicación receptora no los lee puntualmente de la cola. Este comportamiento puede no ser adecuado para los mensajes dependientes del tiempo. Los mensajes dependientes del tiempo tienen un conjunto de propiedades de período de vida (TTL) en el enlace en cola, que indica cuánto tiempo los mensajes pueden estar en la cola antes de que deban expirar. Los mensajes caducados se envían a la cola especial llamada cola de mensajes no enviados. Los mensajes también se pueden colocar en una cola de mensajes no enviados por otras razones, como superar una cuota de la cola o debido a un error de autenticación.
Generalmente, las aplicaciones escriben lógica de compensación para leer los mensajes de la cola de mensajes no enviados y de los motivos de error. La lógica de compensación depende de la causa del error. Por ejemplo, en el caso de error de autenticación, puede corregir el certificado adjunto al mensaje y reenviar el mensaje. Si se produjo un error en la entrega porque se alcanzó la cuota de la cola de destino, puede reintentar la entrega con la esperanza de que se haya resuelto el problema de la cuota.
La mayoría de sistemas de puesta en cola tienen una cola de mensajes no enviados para todo el sistema donde se almacenan todos los mensajes erróneos de ese sistema. Message Queuing (MSMQ) proporciona dos colas de mensajes no enviados para todo el sistema: una cola de mensajes no enviados transaccional para todo el sistema que almacena mensajes que produjeron un error en la entrega a la cola transaccional y una cola de mensajes no enviados no transaccional para todo el sistema que almacena mensajes que produjeron un error en la entrega a la cola no transaccional. Si dos clientes están enviando mensajes a dos servicios diferentes y, por tanto, diferentes colas en WCF están compartiendo el mismo servicio de MSMQ para realizar el envío, es posible que se tenga una mezcla de mensajes en la cola de mensajes no enviados del sistema. Esto es no siempre óptimo. En muchos casos (seguridad, por ejemplo), quizás no desee que un cliente lea los mensajes de otro cliente de una cola de mensajes no enviados. Una cola de mensajes no enviados compartida también requiere que los clientes naveguen por la cola para buscar un mensaje que enviaron, lo que puede resultar prohibitivamente caro en función del número de mensajes en la cola de mensajes no enviados. Por tanto, en WCF NetMsmqBinding
, MsmqIntegrationBinding,
y MSMQ en Windows Vista proporcione una cola de mensajes no enviados personalizada (a veces denominada cola de mensajes no enviados específica de la aplicación).
La cola de mensajes no enviados personalizada proporciona aislamiento entre los clientes que comparten el mismo servicio de MSMQ para enviar los mensajes.
En Windows Server 2003 y Windows XP, Windows Communication Foundation (WCF) proporciona una cola de mensajes no enviados de todo el sistema para todas las aplicaciones cliente en cola. En Windows Vista, WCF proporciona una cola de mensajes no enviados para cada aplicación cliente en cola.
Especificar el uso de la cola de mensajes no enviados
Una cola de mensajes no enviados está en el administrador de cola de la aplicación emisora. Almacena mensajes que han expirado o que han producido un error en la transferencia o entrega.
El enlace tiene las propiedades de cola de mensajes no enviados siguientes:
Leer mensajes de la cola de mensajes no enviados
Una aplicación que lee mensajes de una cola de mensajes no enviados es similar a un servicio WCF que lee de una cola de aplicación, excepto por las siguientes diferencias menores:
Para leer mensajes de una cola de mensajes no enviados transaccional de un sistema, el identificador URI (Uniform Resource Identifier) debe tener el formato siguiente: net.msmq://localhost/system$;DeadXact.
Para leer mensajes de una cola de mensajes no enviados no transaccional de un sistema, el URI debe tener el formato siguiente: net.msmq://localhost/system$;DeadLetter.
Para leer los mensajes de una cola de mensajes no enviados personalizada, el URI debe tener el formato siguiente: net.msmq://localhost/private/<custom-dlq-name>, donde custom-dlq-name es el nombre de la cola de mensajes no enviados personalizada.
Para obtener más información sobre cómo tratar las colas, consulte Puntos de conexión de servicio y direccionamiento de colas.
La pila WCF del receptor coincide con las direcciones de las que el servicio está realizando escuchas con la dirección del mensaje. Si las direcciones coinciden, se envía el mensaje; si no, no se envía el mensaje. Esto puede producir problemas al leer de la cola de mensajes no enviados, porque los mensajes en la cola de mensajes no enviados normalmente se direccionan al servicio y no al servicio de la cola de mensajes no enviados. Por consiguiente, el servicio que lee de la cola de mensajes no enviados debe instalar un filtro de direcciones ServiceBehavior
que indique a la pila que haga coincidir todos los mensajes de la cola independientemente del destinatario. Específicamente, debe agregar ServiceBehavior
con el parámetro Any al servicio que lee los mensajes de la cola de mensajes no enviados.
Control de mensajes dudosos de la cola de mensajes no enviados
El control de mensajes dudosos está disponible en las colas de mensajes no enviados, con algunas condiciones. Dado que no puede crear subcolas a partir de las colas del sistema, al leer de la cola de mensajes no enviados del sistema, ReceiveErrorHandling
no puede estar establecido en Move
. Tenga en cuenta que si está leyendo de una cola de mensajes no enviados personalizada, puede tener subcolas y, por consiguiente, Move
es una disposición válida para el mensaje dudoso.
Cuando ReceiveErrorHandling
está establecido en Reject
, al leer de la cola de mensajes no enviados personalizada, el mensaje dudoso se coloca en la cola de mensajes no enviados del sistema. Si se está leyendo de la cola de mensajes no enviados del sistema, el mensaje se quita (se purga). Un rechazo de una cola de mensajes no enviados del sistema en MSMQ quita (purga) el mensaje.
Ejemplo
El ejemplo siguiente muestra cómo crear una cola de mensajes no enviados y cómo utilizarla para procesar los mensajes caducados. El ejemplo se basa en el ejemplo de Procedimiento para intercambiar mensajes en cola con puntos de conexión WCF. El ejemplo siguiente muestra cómo escribir el código de cliente en el servicio de procesamiento de orden que utiliza una cola de mensajes no enviados para cada aplicación. El ejemplo también muestra cómo procesar los mensajes de la cola de mensajes no enviados.
A continuación, se muestra el código para un cliente que especifica una cola de mensajes no enviados para cada aplicación.
using System;
using System.ServiceModel.Channels;
using System.Configuration;
//using System.Messaging;
using System.ServiceModel;
using System.Transactions;
namespace Microsoft.ServiceModel.Samples
{
//The service contract is defined in generatedProxy.cs, generated from the service by the svcutil tool.
//Client implementation code.
class Client
{
static void Main()
{
// Get MSMQ queue name from appsettings in configuration.
string deadLetterQueueName = ConfigurationManager.AppSettings["deadLetterQueueName"];
// Create the transacted MSMQ queue for storing dead message if necessary.
if (!System.Messaging.MessageQueue.Exists(deadLetterQueueName))
System.Messaging.MessageQueue.Create(deadLetterQueueName, true);
OrderProcessorClient client = new OrderProcessorClient("OrderProcessorEndpoint");
try
{
// Create the purchase order.
PurchaseOrder po = new PurchaseOrder();
po.CustomerId = "somecustomer.com";
po.PONumber = Guid.NewGuid().ToString();
PurchaseOrderLineItem lineItem1 = new PurchaseOrderLineItem();
lineItem1.ProductId = "Blue Widget";
lineItem1.Quantity = 54;
lineItem1.UnitCost = 29.99F;
PurchaseOrderLineItem lineItem2 = new PurchaseOrderLineItem();
lineItem2.ProductId = "Red Widget";
lineItem2.Quantity = 890;
lineItem2.UnitCost = 45.89F;
po.orderLineItems = new PurchaseOrderLineItem[2];
po.orderLineItems[0] = lineItem1;
po.orderLineItems[1] = lineItem2;
//Create a transaction scope.
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
{
// Make a queued call to submit the purchase order.
client.SubmitPurchaseOrder(po);
// Complete the transaction.
scope.Complete();
}
client.Close();
}
catch(TimeoutException timeout)
{
Console.WriteLine(timeout.Message);
client.Abort();
}
catch(CommunicationException conexcp)
{
Console.WriteLine(conexcp.Message);
client.Abort();
}
Console.WriteLine();
Console.WriteLine("Press <ENTER> to terminate client.");
Console.ReadLine();
}
}
}
Imports System.ServiceModel.Channels
Imports System.Configuration
'using System.Messaging;
Imports System.ServiceModel
Imports System.Transactions
Namespace Microsoft.ServiceModel.Samples
'The service contract is defined in generatedProxy.cs, generated from the service by the svcutil tool.
'Client implementation code.
Friend Class Client
Shared Sub Main()
' Get MSMQ queue name from appsettings in configuration.
Dim deadLetterQueueName As String = ConfigurationManager.AppSettings("deadLetterQueueName")
' Create the transacted MSMQ queue for storing dead message if necessary.
If (Not System.Messaging.MessageQueue.Exists(deadLetterQueueName)) Then
System.Messaging.MessageQueue.Create(deadLetterQueueName, True)
End If
Dim client As New OrderProcessorClient("OrderProcessorEndpoint")
Try
' Create the purchase order.
Dim po As New PurchaseOrder()
po.CustomerId = "somecustomer.com"
po.PONumber = Guid.NewGuid().ToString()
Dim lineItem1 As New PurchaseOrderLineItem()
lineItem1.ProductId = "Blue Widget"
lineItem1.Quantity = 54
lineItem1.UnitCost = 29.99F
Dim lineItem2 As New PurchaseOrderLineItem()
lineItem2.ProductId = "Red Widget"
lineItem2.Quantity = 890
lineItem2.UnitCost = 45.89F
po.orderLineItems = New PurchaseOrderLineItem(1) {}
po.orderLineItems(0) = lineItem1
po.orderLineItems(1) = lineItem2
'Create a transaction scope.
Using scope As New TransactionScope(TransactionScopeOption.Required)
' Make a queued call to submit the purchase order.
client.SubmitPurchaseOrder(po)
' Complete the transaction.
scope.Complete()
End Using
client.Close()
Catch timeout As TimeoutException
Console.WriteLine(timeout.Message)
client.Abort()
Catch conexcp As CommunicationException
Console.WriteLine(conexcp.Message)
client.Abort()
End Try
Console.WriteLine()
Console.WriteLine("Press <ENTER> to terminate client.")
Console.ReadLine()
End Sub
End Class
End Namespace
El código siguiente muestra el archivo de configuración del cliente.
A continuación, se muestra el código para un servicio que procesa los mensajes de una cola de mensajes no enviados.
using System;
using System.ServiceModel.Description;
using System.Configuration;
using System.Messaging;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Transactions;
namespace Microsoft.ServiceModel.Samples
{
// Define a service contract.
[ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples")]
public interface IOrderProcessor
{
[OperationContract(IsOneWay = true)]
void SubmitPurchaseOrder(PurchaseOrder po);
}
// Service class that implements the service contract.
// Added code to write output to the console window
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Single, AddressFilterMode = AddressFilterMode.Any)]
public class PurchaseOrderDLQService : IOrderProcessor
{
OrderProcessorClient orderProcessorService;
public PurchaseOrderDLQService()
{
orderProcessorService = new OrderProcessorClient("OrderProcessorEndpoint");
}
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
public void SimpleSubmitPurchaseOrder(PurchaseOrder po)
{
Console.WriteLine("Submitting purchase order did not succeed ", po);
MsmqMessageProperty mqProp = OperationContext.Current.IncomingMessageProperties[MsmqMessageProperty.Name] as MsmqMessageProperty;
Console.WriteLine("Message Delivery Status: {0} ", mqProp.DeliveryStatus);
Console.WriteLine("Message Delivery Failure: {0}", mqProp.DeliveryFailure);
Console.WriteLine();
}
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
public void SubmitPurchaseOrder(PurchaseOrder po)
{
Console.WriteLine("Submitting purchase order did not succeed ", po);
MsmqMessageProperty mqProp = OperationContext.Current.IncomingMessageProperties[MsmqMessageProperty.Name] as MsmqMessageProperty;
Console.WriteLine("Message Delivery Status: {0} ", mqProp.DeliveryStatus);
Console.WriteLine("Message Delivery Failure: {0}", mqProp.DeliveryFailure);
Console.WriteLine();
// Resend the message if timed out.
if (mqProp.DeliveryFailure == DeliveryFailure.ReachQueueTimeout ||
mqProp.DeliveryFailure == DeliveryFailure.ReceiveTimeout)
{
// Re-send.
Console.WriteLine("Purchase order Time To Live expired");
Console.WriteLine("Trying to resend the message");
// Reuse the same transaction used to read the message from dlq to enqueue the message to the application queue.
orderProcessorService.SubmitPurchaseOrder(po);
Console.WriteLine("Purchase order resent");
}
}
// Host the service within this EXE console application.
public static void Main()
{
// Create a ServiceHost for the PurchaseOrderDLQService type.
using (ServiceHost serviceHost = new ServiceHost(typeof(PurchaseOrderDLQService)))
{
// Open the ServiceHostBase to create listeners and start listening for messages.
serviceHost.Open();
// The service can now be accessed.
Console.WriteLine("The dead letter service is ready.");
Console.WriteLine("Press <ENTER> to terminate service.");
Console.WriteLine();
Console.ReadLine();
// Close the ServiceHostBase to shutdown the service.
serviceHost.Close();
}
}
}
}
Imports System.ServiceModel.Description
Imports System.Configuration
Imports System.Messaging
Imports System.ServiceModel
Imports System.ServiceModel.Channels
Imports System.Transactions
Namespace Microsoft.ServiceModel.Samples
' Define a service contract.
<ServiceContract(Namespace:="http://Microsoft.ServiceModel.Samples")> _
Public Interface IOrderProcessor
<OperationContract(IsOneWay:=True)> _
Sub SubmitPurchaseOrder(ByVal po As PurchaseOrder)
End Interface
' Service class that implements the service contract.
' Added code to write output to the console window
<ServiceBehavior(InstanceContextMode:=InstanceContextMode.Single, ConcurrencyMode:=ConcurrencyMode.Single, AddressFilterMode:=AddressFilterMode.Any)> _
Public Class PurchaseOrderDLQService
Implements IOrderProcessor
Private orderProcessorService As OrderProcessorClient
Public Sub New()
orderProcessorService = New OrderProcessorClient("OrderProcessorEndpoint")
End Sub
<OperationBehavior(TransactionScopeRequired:=True, TransactionAutoComplete:=True)> _
Public Sub SimpleSubmitPurchaseOrder(ByVal po As PurchaseOrder)
Console.WriteLine("Submitting purchase order did not succeed ", po)
Dim mqProp As MsmqMessageProperty = TryCast(OperationContext.Current.IncomingMessageProperties(MsmqMessageProperty.Name), MsmqMessageProperty)
Console.WriteLine("Message Delivery Status: {0} ", mqProp.DeliveryStatus)
Console.WriteLine("Message Delivery Failure: {0}", mqProp.DeliveryFailure)
Console.WriteLine()
End Sub
<OperationBehavior(TransactionScopeRequired:=True, TransactionAutoComplete:=True)> _
Public Sub SubmitPurchaseOrder(ByVal po As PurchaseOrder) Implements IOrderProcessor.SubmitPurchaseOrder
Console.WriteLine("Submitting purchase order did not succeed ", po)
Dim mqProp As MsmqMessageProperty = TryCast(OperationContext.Current.IncomingMessageProperties(MsmqMessageProperty.Name), MsmqMessageProperty)
Console.WriteLine("Message Delivery Status: {0} ", mqProp.DeliveryStatus)
Console.WriteLine("Message Delivery Failure: {0}", mqProp.DeliveryFailure)
Console.WriteLine()
' Resend the message if timed out.
If mqProp.DeliveryFailure = DeliveryFailure.ReachQueueTimeout OrElse mqProp.DeliveryFailure = DeliveryFailure.ReceiveTimeout Then
' Re-send.
Console.WriteLine("Purchase order Time To Live expired")
Console.WriteLine("Trying to resend the message")
' Reuse the same transaction used to read the message from dlq to enqueue the message to the application queue.
orderProcessorService.SubmitPurchaseOrder(po)
Console.WriteLine("Purchase order resent")
End If
End Sub
' Host the service within this EXE console application.
Public Shared Sub Main()
' Create a ServiceHost for the PurchaseOrderDLQService type.
Using serviceHost As New ServiceHost(GetType(PurchaseOrderDLQService))
' Open the ServiceHostBase to create listeners and start listening for messages.
serviceHost.Open()
' The service can now be accessed.
Console.WriteLine("The dead letter service is ready.")
Console.WriteLine("Press <ENTER> to terminate service.")
Console.WriteLine()
Console.ReadLine()
' Close the ServiceHostBase to shutdown the service.
serviceHost.Close()
End Using
End Sub
End Class
End Namespace
A continuación, se muestra el código para el archivo de configuración del servicio de cola de mensajes no enviados.