Compartir a través de


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 Queue Server (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 consiguiente diferentes colas en WCF están compartiendo el mismo servicio de MSMQ para realizar el envío, es posible tener 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 consiguiente, en WCFNetMsmqBinding, MsmqIntegrationBinding, y MSMQ en Windows Vista proporcione una cola de mensajes no enviados personalizada (a veces se denomina una 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 para 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 la aplicación, salvo por las diferencias menores siguientes:

  • 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 Para obtener más información sobre saber cómo direccionar las colas, vea Extremos de servicio y direccionamiento de la cola.

La pila WCF en el receptor compara 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 en Cómo: Intercambiar mensajes en cola con extremos de 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.

Imports System
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
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();
        }
    }
}

El código siguiente muestra el archivo de configuración del cliente.

<configuration>
  <!-- Change "localhost" in the endpoint address to the machine name where the queue resides
       Change "localhost" in the customDeadLetterQueue attribute in the binding element to the 
       machine name where the client application executes 
   -->
  
  <appSettings>
    <!-- use appSetting to configure MSMQ Dead Letter queue name -->
    <add key="deadLetterQueueName" value=".\private$\ServiceModelSamplesOrdersAppDLQ"/>
  </appSettings>

  <system.serviceModel>
    <client>
      <!-- Define NetMsmqEndpoint -->
      <endpoint name="OrderProcessorEndpoint"
                address="net.msmq://localhost/private/ServiceModelSamplesDeadLetter" 
                binding="netMsmqBinding" 
                bindingConfiguration="PerAppDLQBinding" 
                contract="IOrderProcessor" />
    </client>

    <bindings>
      <netMsmqBinding>
        <binding name="PerAppDLQBinding"
                 deadLetterQueue="Custom"
                 customDeadLetterQueue="net.msmq://localhost/private/ServiceModelSamplesOrdersAppDLQ" 
                 timeToLive="00:00:02"/>
      </netMsmqBinding>
    </bindings>
  </system.serviceModel>
</configuration>

A continuación, se muestra el código para un servicio que procesa los mensajes de una cola de mensajes no enviados.

Imports System
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
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();
            }
        }
    }
}

A continuación, se muestra el código para el archivo de configuración del servicio de cola de mensajes no enviados.

<!-- Change the endpoint address to reflect your machine name.
     Place this code in the app.config for the Dead Letter Queue service -->
<configuration>
  <system.serviceModel>
    <services>
      <service 
          name="Microsoft.ServiceModel.Samples.PurchaseOrderDLQService">
        <!-- Define NetMsmqEndpoint in this case, DLQ end point to read messages-->
        <endpoint address="net.msmq://localhost/private/ServiceModelSamplesOrdersAppDLQ"
                  binding="netMsmqBinding"
                  bindingConfiguration="DefaultBinding" 
                  contract="Microsoft.ServiceModel.Samples.IOrderProcessor" />
      </service>
    </services>

    <client>
      <!-- Define NetMsmqEndpoint -->
      <endpoint name="OrderProcessorEndpoint"
                 address="net.msmq://localhost/private/ServiceModelSamplesDeadLetter" 
                 binding="netMsmqBinding" 
                 bindingConfiguration="SystemDLQBinding" 
                 contract="IOrderProcessor" />
    </client>

    <bindings>
      <netMsmqBinding>
        <binding name="DefaultBinding" />
        <binding name="SystemDLQBinding"
                 deadLetterQueue="System"/>
      </netMsmqBinding>
    </bindings>
  </system.serviceModel>
</configuration>

Vea también

Tareas

Cómo: Intercambiar mensajes en cola con extremos de WCF

Conceptos

Información general de colas
Control de mensajes dudosos