Utilisation de files d'attente de lettres mortes pour gérer des défaillances de transfert de messages

La remise de messages en file d'attente peut échouer. Les messages qui ont échoué sont enregistrés dans une file d'attente de lettres mortes. L'échec de la remise peut être dû à des défaillances du réseau, une file d'attente supprimée, une file d'attente saturée, un échec d'authentification ou un retard de remise.

Les messages en file d'attente peuvent rester longtemps dans la file d'attente si l'application de réception ne les lit pas en temps voulu. Ce comportement peut s'avérer inapproprié pour des messages dépendants de l'heure. Les messages dépendants de l'heure ont une propriété de durée de vie définie dans la liaison en file d'attente, qui indique combien de temps les messages peuvent demeurer dans la file d'attente avant d'expirer. Les messages ayant expiré sont envoyés dans une file d'attente spéciale appelée file d'attente de lettres mortes. Les messages peuvent également être placés dans une file d'attente de lettres mortes pour d'autres raisons, telles que le dépassement d'un quota de file d'attente ou un échec d'authentification.

En général, les applications écrivent une logique de compensation pour lire les messages de la file d'attente de lettres mortes et les raisons de l'échec. La logique de compensation dépend de la cause de la défaillance. Par exemple, dans le cas d'un échec d'authentification, vous pouvez corriger le certificat joint au message et renvoyer le message. Si la remise a échoué parce que le quota de la file d'attente cible a été atteint, vous pouvez tenter de nouveau la remise dans l'espoir que le problème de quota a été résolu.

La plupart des systèmes de mise en file d'attente possèdent une file d'attente de lettres mortes à l'échelle du système qui stocke tous les messages ayant échoué issus de ce système. Message Queuing (MSMQ) fournit deux files d’attente de lettres mortes à l’échelle du système : une file d’attente de lettres mortes transactionnelle à l’échelle du système, qui stocke les messages dont la remise dans la file d’attente transactionnelle a échoué et une file d’attente de lettres mortes non transactionnelle à l’échelle du système, qui stocke les messages dont la remise dans la file d’attente non transactionnelle a échoué. Si deux clients envoient des messages à deux services différents et si, par conséquent, des files d’attente différentes dans WCF partagent le même service MSMQ pour l’envoi, il est possible d’avoir une combinaison de messages dans la file d’attente de lettres mortes du système. Cela n'est pas toujours optimal. Dans certains cas (pour des raisons de sécurité, par exemple), vous ne souhaiterez pas qu'un client lise les messages d'un autre client à partir d'une file d'attente de lettres mortes. Dans une file d'attente de lettres mortes partagée, les clients doivent parcourir la file d'attente pour rechercher un message qu'ils ont envoyé, ce qui peut représenter un coût prohibitif en fonction du nombre de messages dans la file d'attente de lettres mortes. Par conséquent, dans WCFNetMsmqBinding, MsmqIntegrationBinding, et MSMQ sous Windows Vista fournissent une file d’attente de lettres mortes personnalisée (parfois appelée file d’attente de lettres mortes spécifique à l’application).

La file d'attente de lettres mortes personnalisée assure l'isolement entre les clients qui partagent le même service MSMQ pour envoyer des messages.

Sur Windows Server 2003 et Windows XP, Windows Communication Foundation (WCF) fournit une file d’attente de lettres mortes à l’échelle du système pour toutes les applications clientes mises en file d’attente. Sur Windows Vista, WCF fournit une file d’attente de lettres mortes pour chaque application cliente mise en file d’attente.

Spécification de l'utilisation de la file d'attente de lettres mortes

Une file d'attente de lettres mortes est dans le gestionnaire de files d'attente de l'application émettrice. Elle stocke les messages qui ont expiré ou dont la remise ou le transfert a échoué.

La liaison a les propriétés de file d’attente de lettres mortes suivantes :

Lecture des messages stockés dans la file d'attente de lettres mortes

Une application qui lit des messages stockés dans une file d’attente de lettres mortes est semblable à un service WCF qui lit à partir d’une file d’attente d’application, mais présente les différences mineures suivantes :

  • Pour lire des messages à partir d’une file d’attente de lettres mortes transactionnelle système, l’URI (Uniform Resource Identifier) doit être de la forme : net.msmq://localhost/system$;DeadXact.

  • Pour lire des messages à partir d'une file d'attente de lettres mortes non transactionnelle système, l'URI doit être de la forme : net.msmq://localhost/system$;DeadLetter.

  • Pour lire des messages à partir d’une file d’attente de lettres mortes personnalisée, l’URI doit être de la forme : net.msmq://localhost/private/<nom-falm-personnalisée> où nom-falm-personnalisée correspond au nom de la file d’attente de lettres mortes personnalisée.

Pour plus d’informations sur l’adresse des files d’attente, consultez Points de terminaison de service et adressage de file d’attente.

La pile WCF sur le récepteur met en correspondance les adresses où le service est à l’écoute et l’adresse sur le message. Si les adresses correspondent, le message est distribué ; dans le cas contraire, le message n'est pas distribué. Cela peut provoquer des problèmes lors de la lecture à partir de la file d'attente de lettres mortes, parce que les messages dans la file d'attente de lettres mortes sont adressés en général au service et pas le service de file d'attente de lettres mortes. Par conséquent, le service qui lit à partir de la file d'attente de lettres mortes doit installer un filtre d'adresse ServiceBehavior qui indique à la pile de mettre en correspondance tous les messages de la file d'attente indépendamment du destinataire. En particulier, vous devez ajouter un filtre ServiceBehavior avec le paramètre Any au service qui lit des messages à partir de la file d'attente de lettres mortes.

Gestion des messages incohérents à partir de la file d'attente de lettres mortes

La gestion des messages incohérents est disponible sur les files d'attente de lettres mortes, mais présente certaines conditions. Comme vous ne pouvez pas créer de sous-files d'attente à partir des files d'attente système, lorsque vous lisez à partir de la file d'attente de lettres mortes système, il n'est pas possible d'affecter à ReceiveErrorHandling la valeur Move. Notez que si vous lisez à partir d'une file d'attente de lettres mortes personnalisée, vous pouvez avoir des sous-files d'attente et, par conséquent, Move est une disposition valide pour le message incohérent.

Lorsque ReceiveErrorHandling a la valeur Reject, lors de la lecture à partir de la file d'attente de lettres mortes personnalisée, le message incohérent est placé dans la file d'attente de lettres mortes système. Lors de la lecture à partir de la file d'attente de lettres mortes système, le message est déposé (purgé). Le rejet d’une file d’attente de lettres mortes système dans MSMQ dépose (purge) le message.

Exemple

L'exemple ci-dessous montre comment créer une file d'attente de lettres mortes et comment l'utiliser pour traiter les messages ayant expiré. L’exemple est basé sur l’exemple dans Guide pratique pour échanger des messages en file d’attente avec des points de terminaison WCF. L'exemple ci-dessous montre comment écrire le code client pour le service de traitement des commandes qui utilise une file d'attente de lettres mortes pour chaque application. Cet exemple montre également comment traiter les messages stockés dans la file d'attente de lettres mortes.

Le code ci-dessous correspond à un client qui spécifie une file d'attente de lettres mortes pour chaque application.

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

Le code ci-dessous est destiné au fichier de configuration client.

Le code ci-dessous est destiné à un service qui traite les messages stockés dans une file d'attente de lettres mortes.

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

Le code ci-dessous est destiné au fichier de configuration du service de file d'attente de lettres mortes.

Voir aussi