Partilhar via


Usando filas de mensagens mortas para lidar com falhas de transferência de mensagens

As mensagens em fila podem falhar na entrega. Essas mensagens com falha são registradas em uma fila de mensagens mortas. A falha na entrega pode ser causada por motivos como falhas de rede, uma fila excluída, uma fila completa, falha de autenticação ou uma falha na entrega no prazo.

As mensagens em fila podem permanecer na fila por muito tempo se o aplicativo de recebimento não as ler da fila em tempo hábil. Esse comportamento pode não ser apropriado para mensagens sensíveis ao tempo. As mensagens sensíveis ao tempo têm uma propriedade TTL (Time to Live) definida na associação em fila, que indica quanto tempo as mensagens podem ficar na fila antes de expirarem. As mensagens expiradas são enviadas para uma fila especial chamada fila de cartas mortas. As mensagens também podem ser colocadas em uma fila de mensagens mortas por outros motivos, como exceder uma cota de fila ou devido a falha de autenticação.

Geralmente, os aplicativos gravam a lógica de compensação para ler mensagens da fila de mensagens mortas e motivos de falha. A lógica de compensação depende da causa da falha. Por exemplo, no caso de falha de autenticação, você pode corrigir o certificado anexado com a mensagem e reenviar a mensagem. Se a entrega falhou porque a cota da fila de destino foi atingida, você pode tentar novamente a entrega na esperança de que o problema da cota tenha sido resolvido.

A maioria dos sistemas de fila tem uma fila de mensagens mortas em todo o sistema onde todas as mensagens com falha desse sistema são armazenadas. O serviço de enfileiramento de mensagens (MSMQ) fornece duas filas de mensagens mortas em todo o sistema: uma fila de mensagens mortas transacional em todo o sistema que armazena mensagens que falharam na entrega para a fila transacional e uma fila de mensagens mortas não transacional em todo o sistema que armazena mensagens que falharam na entrega para a fila não transacional. Se dois clientes estão enviando mensagens para dois serviços diferentes e, portanto, filas diferentes no WCF estão compartilhando o mesmo serviço MSMQ para enviar, então é possível ter uma mistura de mensagens na fila de mensagens mortas do sistema. Isto nem sempre é o ideal. Em vários casos (segurança, por exemplo), você pode não querer que um cliente leia as mensagens de outro cliente de uma fila de mensagens mortas. Uma fila de mensagens mortas compartilhadas também exige que os clientes naveguem pela fila para encontrar uma mensagem que enviaram, o que pode ser proibitivamente caro com base no número de mensagens na fila de mensagens mortas. Portanto, no WCFNetMsmqBindingMsmqIntegrationBinding, e no MSMQ no Windows Vista fornecem uma fila de mensagens mortas personalizada (às vezes chamada de fila de cartas mortas específica do aplicativo).

A fila de mensagens mortas personalizada fornece isolamento entre clientes que compartilham o mesmo serviço MSMQ para enviar mensagens.

No Windows Server 2003 e no Windows XP, o Windows Communication Foundation (WCF) fornece uma fila de mensagens mortas em todo o sistema para todos os aplicativos cliente enfileirados. No Windows Vista, o WCF fornece uma fila de mensagens mortas para cada aplicativo cliente enfileirado.

Especificando o uso da fila de mensagens mortas

Uma fila de mensagens mortas está no gerenciador de filas do aplicativo de envio. Ele armazena mensagens que expiraram ou que falharam na transferência ou entrega.

A associação tem as seguintes propriedades de fila de letra morta:

Lendo mensagens da fila de mensagens mortas

Um aplicativo que lê mensagens de uma fila de mensagens mortas é semelhante a um serviço WCF que lê de uma fila de aplicativos, exceto pelas seguintes diferenças menores:

  • Para ler mensagens de uma fila de letras mortas transacionais do sistema, o URI (Uniform Resource Identifier) deve ter o formato: net.msmq://localhost/system$; DeadXact.

  • Para ler mensagens de uma fila de letras mortas não transacionais do sistema, o URI deve ter o formato: net.msmq://localhost/system$; Carta morta.

  • Para ler mensagens de uma fila de cartas mortas personalizada, o URI deve ser do formato:net.msmq://localhost/private/<custom-dlq-name>, onde custom-dlq-name é o nome da fila de letras mortas personalizada.

Para obter mais informações sobre como lidar com filas, consulte Pontos de extremidade de serviço e endereçamento de filas.

A pilha WCF no recetor corresponde aos endereços que o serviço está escutando com o endereço na mensagem. Se os endereços corresponderem, a mensagem é enviada; caso contrário, a mensagem não é enviada. Isso pode causar problemas ao ler a partir da fila de mensagens mortas, porque as mensagens na fila de mensagens mortas geralmente são endereçadas ao serviço e não ao serviço de fila de mensagens mortas. Portanto, a leitura do serviço da fila de mensagens mortas deve instalar um filtro ServiceBehavior de endereço que instrua a pilha a corresponder a todas as mensagens na fila, independentemente do destinatário. Especificamente, você deve adicionar um ServiceBehavior com o Any parâmetro para o serviço de leitura de mensagens da fila de mensagens mortas.

Tratamento de mensagens suspeitas da fila de mensagens mortas

O tratamento de mensagens suspeitas está disponível em filas de mensagens mortas, com algumas condições. Como não é possível criar subfilas a partir de filas do sistema, ao ler a partir da fila de letras mortas do sistema, o ReceiveErrorHandling não pode ser definido como Move. Observe que, se você estiver lendo a partir de uma fila de mensagens mortas personalizada, poderá ter subfilas e, portanto, Move é uma disposição válida para a mensagem suspeita.

Quando ReceiveErrorHandling é definido como Reject, ao ler a partir da fila de letras mortas personalizada, a mensagem suspeita é colocada na fila de letras mortas do sistema. Se estiver lendo a fila de mensagens mortas do sistema, a mensagem será descartada (limpa). Uma rejeição de uma fila de mensagens mortas do sistema no MSMQ descarta (limpa) a mensagem.

Exemplo

O exemplo a seguir mostra como criar uma fila de mensagens mortas e como usá-la para processar mensagens expiradas. O exemplo é baseado no exemplo em Como: Mensagens em fila do Exchange com pontos de extremidade WCF. O exemplo a seguir mostra como gravar o código do cliente no serviço de processamento de pedidos que usa uma fila de mensagens mortas para cada aplicativo. O exemplo também mostra como processar mensagens da fila de mensagens mortas.

A seguir está o código para um cliente que especifica uma fila de letras mortas para cada aplicativo.

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

A seguir está o código para o arquivo de configuração do cliente.

A seguir está o código para um serviço que processa mensagens de uma fila de mensagens mortas.

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 seguir está o código para o arquivo de configuração do serviço de fila de letra morta.

Consulte também