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 à 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 WCF NetMsmqBinding, 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.
Sous 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 en file d'attente. Sous Windows Vista, WCF fournit une file d'attente de lettres mortes pour chaque application cliente 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 le sujet suivant la manière d'adresser des files d'attente, consultez Points de terminaison de service et adressage de files 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é. Cet exemple est basé sur l'exemple donné dans Comment : é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.
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();
}
}
}
Le code ci-dessous est destiné au fichier de configuration client.
<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>
Le code ci-dessous est destiné à un service qui traite les messages stockés dans une file d'attente de lettres mortes.
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();
}
}
}
}
Le code ci-dessous est destiné au fichier de configuration du service de file d'attente de lettres mortes.
<!-- 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>
Voir aussi
Tâches
Comment : échanger des messages en file d'attente avec des points de terminaison WCF
Concepts
Vue d'ensemble des files d'attente
Gestion des messages incohérents