Het gebruik van Dead-Letter wachtrijen voor het afhandelen van fouten bij het overdragen van berichten
Berichten in de wachtrij kunnen niet worden afgeleverd. Deze mislukte berichten worden vastgelegd in een doodletterwachtrij. De mislukte levering kan worden veroorzaakt door redenen zoals netwerkfouten, een verwijderde wachtrij, een volledige wachtrij, verificatiefout of een fout bij het leveren op tijd.
Berichten in de wachtrij kunnen lange tijd in de wachtrij blijven als de ontvangende toepassing deze niet tijdig uit de wachtrij leest. Dit gedrag is mogelijk niet geschikt voor tijdgevoelige berichten. Tijdgevoelige berichten hebben een TTL-eigenschap (Time to Live) ingesteld in de binding in de wachtrij. Dit geeft aan hoe lang de berichten in de wachtrij kunnen staan voordat ze moeten verlopen. Verlopen berichten worden verzonden naar een speciale wachtrij, de wachtrij met dode letters. Berichten kunnen ook om andere redenen in een dead-letter wachtrij worden geplaatst, zoals het overschrijden van een wachtrijquotum of vanwege een fout in de verificatie.
Over het algemeen schrijven toepassingen compensatielogica om berichten uit de dead-letter queue te lezen en de redenen voor fouten te begrijpen. De compensatielogica is afhankelijk van de oorzaak van de fout. In het geval van een verificatiefout kunt u bijvoorbeeld het certificaat corrigeren dat is gekoppeld aan het bericht en het bericht opnieuw verzenden. Als de levering is mislukt omdat het quotum voor de doelwachtrij is bereikt, kunt u de levering opnieuw instellen in de hoop dat het quotumprobleem is opgelost.
De meeste wachtrijsystemen hebben een systeembrede wachtrij met dode letters waarin alle mislukte berichten van dat systeem worden opgeslagen. Message Queuing (MSMQ) biedt twee system-wide dead-letter wachtrijen: een transactionele system-wide dead-letter wachtrij waarin berichten worden opgeslagen die niet in de transactionele wachtrij zijn afgeleverd, en een niet-transactionele system-wide dead-letter wachtrij die berichten opslaat die niet in de niet-transactionele wachtrij zijn afgeleverd. Als twee clients berichten verzenden naar twee verschillende services en daarom verschillende wachtrijen in WCF dezelfde MSMQ-service delen om te verzenden, is het mogelijk dat er een mix van berichten in de dode-letterwachtrij van het systeem terechtkomt. Dit is niet altijd optimaal. In verschillende gevallen (beveiliging, bijvoorbeeld), wilt u mogelijk niet dat één client de berichten van een andere client leest uit een wachtrij met dode letters. Voor een gedeelde wachtrij met niet-bezorgde berichten moeten clients ook door de wachtrij zoeken om een bericht te vinden dat ze hebben verzonden, wat extreem kostbaar kan zijn op basis van het aantal berichten in de wachtrij met niet-bezorgde berichten. Daarom bieden MsmqIntegrationBinding,
en MSMQ op Windows Vista in WCF-NetMsmqBinding
een aangepaste wachtrij met dode letters (ook wel een toepassingsspecifieke wachtrij voor dode letters genoemd).
De aangepaste wachtrij met dode letters biedt isolatie tussen clients die dezelfde MSMQ-service delen om berichten te verzenden.
Op Windows Server 2003 en Windows XP biedt Windows Communication Foundation (WCF) een systeembrede wachtrij met dode letters voor alle clienttoepassingen in de wachtrij. Op Windows Vista biedt WCF een wachtrij met dode letters voor elke clienttoepassing in de wachtrij.
Het gebruik van de Dead-Letter-wachtrij opgeven
Een wachtrij met dode letters bevindt zich in de wachtrijbeheerder van de verzendende toepassing. Er worden berichten opgeslagen die zijn verlopen of die niet zijn overgedragen of geleverd.
De binding heeft de volgende wachtrijeigenschappen voor onbestelbare letters:
Berichten lezen uit de Dead-Letter wachtrij
Een toepassing die berichten uit een wachtrij met dode letters leest, is vergelijkbaar met een WCF-service die uit een toepassingswachtrij leest, met uitzondering van de volgende kleine verschillen:
Als u berichten wilt lezen uit een wachtrij voor transactionele dode letters, moet de URI (Uniform Resource Identifier) van het formulier zijn: net.msmq://localhost/system$; DeadXact.
Als u berichten wilt lezen uit een niet-transactionele wachtrij met dode letters, moet de URI van het formulier zijn: net.msmq://localhost/system$; DeadLetter.
Als u berichten wilt lezen uit een aangepaste wachtrij met dode letters, moet de URI van het formulier:net.msmq://localhost/private/<custom-dlq-name> waarbij custom-dlq-name de naam is van de aangepaste wachtrij met dode letters.
Zie Service-eindpunten en Wachtrijadresseringvoor meer informatie over het adresseren van wachtrijen.
De WCF-stack op de ontvanger zorgt ervoor dat de adressen waarop de service luistert overeenkomen met het adres in het bericht. Als de adressen overeenkomen, wordt het bericht verzonden; zo niet, dan wordt het bericht niet verzonden. Dit kan problemen veroorzaken bij het lezen vanuit de wachtrij met dode letters, omdat berichten in de wachtrij met dode letters meestal worden geadresseerd aan de service en niet aan de service voor wachtrijen met dode letters. Daarom moet de service die uit de wachtrij met dode letters leest, een adresfilter installeren ServiceBehavior
waarmee de stack wordt geïnstrueerd om alle berichten in de wachtrij onafhankelijk van de geadresseerde te vinden. U moet met name een ServiceBehavior
met de parameter Any toevoegen aan de service voor het lezen van berichten uit de wachtrij met dode letters.
Afhandeling van gifberichten uit de Dead-Letter-wachtrij
De verwerking van gifberichten is beschikbaar in wachtrijen met onbestelbare berichten, met een aantal voorwaarden. Omdat u geen subwachtrijen kunt maken op basis van systeemwachtrijen, kunt u bij het lezen van de systeem-dead-letter-wachtrij ReceiveErrorHandling
niet instellen op Move
. Houd er rekening mee dat als u leest uit een aangepaste wachtrij met dode letters, subwachtrijen kunt hebben en daarom Move
een geldige verwijdering is voor het gifbericht.
Wanneer ReceiveErrorHandling
is ingesteld op Reject
, wordt bij het lezen uit de aangepaste wachtrij voor dode letters het gifbericht in de wachtrij met dode letters geplaatst. Als het bericht wordt gelezen uit de wachtrij met dode letters van het systeem, wordt het bericht verwijderd (leeggemaakt). Een afwijzing uit een wachtrij met dode letters in MSMQ laat het bericht vallen (opschonen).
Voorbeeld
In het volgende voorbeeld ziet u hoe u een wachtrij met dode letters maakt en hoe u deze kunt gebruiken om verlopen berichten te verwerken. Het voorbeeld is gebaseerd op het voorbeeld in How to: Exchange Queued Messages with WCF Endpoints. In het volgende voorbeeld ziet u hoe u de clientcode schrijft voor de orderverwerkingsservice die gebruikmaakt van een dead-letter wachtrij voor elke toepassing. In het voorbeeld ziet u ook hoe u berichten verwerkt uit de dead-letter queue.
Hier volgt code voor een client die een wachtrij met dode letters voor elke toepassing opgeeft.
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
Hier volgt code voor het clientconfiguratiebestand.
Hier volgt een code voor een service die berichten verwerkt uit een dead-letter queue.
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 ");
MsmqMessageProperty mqProp = OperationContext.Current.IncomingMessageProperties[MsmqMessageProperty.Name] as MsmqMessageProperty;
Console.WriteLine($"Message Delivery Status: {mqProp.DeliveryStatus} ");
Console.WriteLine($"Message Delivery Failure: {mqProp.DeliveryFailure}");
Console.WriteLine();
}
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
public void SubmitPurchaseOrder(PurchaseOrder po)
{
Console.WriteLine($"Submitting purchase order did not succeed ");
MsmqMessageProperty mqProp = OperationContext.Current.IncomingMessageProperties[MsmqMessageProperty.Name] as MsmqMessageProperty;
Console.WriteLine($"Message Delivery Status: {mqProp.DeliveryStatus} ");
Console.WriteLine($"Message Delivery Failure: {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
Hier volgt de code voor het configuratiebestand van de dead-letter queue service.