Использование очередей недоставленных сообщений для обработки сбоев при передаче сообщенийUsing Dead-Letter Queues to Handle Message Transfer Failures

Сообщения в очереди могут вызвать сбой доставки.Queued messages can fail delivery. Сообщения, во время доставки которых произошел сбой, записываются в очередь недоставленных сообщений.These failed messages are recorded in a dead-letter queue. Сбой доставки может быть вызван такими причинами, как сбои сети, удаленная очередь, заполнение очереди, сбой проверки подлинности или сбой доставки по времени.The failed delivery can be caused by reasons such as network failures, a deleted queue, a full queue, authentication failure, or a failure to deliver on time.

Сообщения могут длительное время оставаться в очереди, если принимающее приложение не считывает их из очереди за отведенное время.Queued messages can remain in the queue for a long time if the receiving application does not read them from the queue in a timely fashion. Такое поведение может не быть свойственно чувствительным к времени сообщениям.This behavior may not be appropriate for time-sensitive messages. Критичные к времени сообщения имеют свойство срок жизни (TTL), заданное в привязке с очередью, которое указывает, насколько долго сообщения могут находиться в очереди до истечения срока действия.Time-sensitive messages have a Time to Live (TTL) property set in the queued binding, which indicates how long the messages can be in the queue before they must expire. Просроченные сообщения отправляются в специальную очередь, называемую очередью недоставленных сообщений.Expired messages are sent to a special queue called the dead-letter queue. Сообщения могут быть помещены в очередь недоставленных сообщений и по другим причинам, таким как превышение квоты очереди или сбой проверки подлинности.Messages can also be put in a dead-letter queue for other reasons, such as exceeding a queue quota or because of authentication failure.

Обычно для прочтения сообщений из очереди недоставленных сообщений и причин сбоя приложения записывают компенсирующую логику.Generally, applications write compensation logic to read messages from the dead-letter queue and failure reasons. Компенсирующая логика зависит от причины сбоя.The compensation logic depends on the cause of the failure. Например, в случае сбоя проверки подлинности можно исправить сертификат, прикрепленный к сообщению, и отправить сообщение повторно.For example, in the case of authentication failure, you can correct the certificate attached with the message and resend the message. Если сбой доставки произошел по причине достижения квоты целевой очереди, можно повторить попытку отправки в надежде, что проблема с квотой была устранена.If delivery failed because the target queue quota was reached, you can reattempt delivery in the hope that the quota problem was resolved.

Большинство систем организации очереди имеют общесистемную очередь недоставленных сообщений, в которой хранятся все сообщения данной системы, во время доставки которых произошел сбой.Most queuing systems have a system-wide dead-letter queue where all failed messages from that system are stored. Очередь сообщений (MSMQ) имеет две общесистемные очереди недоставленных сообщений: общесистемная очередь недоставленных транзакционных сообщений, в которой хранятся сообщения, по причине сбоя не доставленные в транзакционную очередь, и общесистемная очередь недоставленных нетранзакционных сообщений, в которой хранятся сообщения, по причине сбоя не доставленные в нетранзакционную очередь.Message Queuing (MSMQ) provides two system-wide dead-letter queues: a transactional system-wide dead-letter queue that stores messages that failed delivery to the transactional queue and a non-transactional system-wide dead-letter queue that stores messages that failed delivery to the non-transactional queue. Если два клиента отправляют сообщения двум разным службам, и, следовательно, разные очереди в WCF используют одну и ту же службу MSMQ для отправки, то в системную очередь недоставленных сообщений можно поместить сообщения.If two clients are sending messages to two different services, and therefore different queues in WCF are sharing the same MSMQ service to send, then it is possible to have a mix of messages in the system dead-letter queue. Это не всегда оптимально.This is not always optimal. В некоторых случаях (по соображениям безопасности, например), может быть нежелательно, чтобы один клиент прочел сообщения другого клиента из очереди недоставленных сообщений.In several cases (security, for example), you may not want one client to read another client's messages from a dead-letter queue. При использовании общей очереди недоставленных сообщений клиенты также вынуждены просматривать всю очередь, чтобы найти отправленное ими сообщение, что, в зависимости от количества сообщений в очереди недоставленных сообщений, может оказаться чрезмерно дорого.A shared dead-letter queue also requires clients to browse through the queue to find a message that they sent, which can be prohibitively expensive based on the number of messages in the dead-letter queue. Таким образом, в NetMsmqBinding WCF MsmqIntegrationBinding, и MSMQ в Windows Vista предусмотрена пользовательская очередь недоставленных сообщений (иногда она называется очередью недоставленных сообщений конкретного приложения).Therefore, in WCFNetMsmqBinding, MsmqIntegrationBinding, and MSMQ on Windows Vista provide a custom dead-letter queue (sometimes referred to as an application-specific dead-letter queue).

Пользовательская очередь недоставленных сообщений обеспечивает изоляцию между клиентами, использующими для отправки сообщений одну службу MSMQ.The custom dead-letter queue provides isolation between clients that share the same MSMQ service to send messages.

В Windows Server 2003 и Windows XP Windows Communication Foundation (WCF) предоставляет очередь недоставленных сообщений на уровне системы для всех клиентских приложений, помещенных в очередь.On Windows Server 2003 and Windows XP, Windows Communication Foundation (WCF) provides a system-wide dead-letter queue for all queued client applications. В Windows Vista WCF предоставляет очередь недоставленных сообщений для каждого клиентского приложения, находящихся в очереди.On Windows Vista, WCF provides a dead-letter queue for each queued client application.

Указание использования очереди недоставленных сообщенийSpecifying Use of the Dead-Letter Queue

Очередь недоставленных сообщений находится в диспетчере очереди отправляющего приложения.A dead-letter queue is in the queue manager of the sending application. В диспетчере очереди хранятся сообщения с истекшим сроком или сообщения, во время передачи или доставки которых произошел сбой.It stores messages that have expired or that have failed transfer or delivery.

Привязка имеет следующие свойства очереди недоставленных писем.The binding has the following dead-letter queue properties:

Чтение сообщений из очереди недоставленных сообщенийReading Messages from the Dead-Letter Queue

Приложение, считывающее сообщения из очереди недоставленных сообщений, похоже на службу WCF, которая считывает данные из очереди приложений, за исключением следующих незначительных отличий:An application that reads messages out of a dead-letter queue is similar to a WCF service that reads from an application queue, except for the following minor differences:

  • Для чтения сообщений из системной очереди недоставленных транзакционных сообщений универсальный код ресурса (URI) должен иметь вид net.msmq://localhost/system$;DeadXact.To read messages from a system transactional dead-letter queue, the Uniform Resource Identifier (URI) must be of the form: net.msmq://localhost/system$;DeadXact.

  • Для чтения сообщений из системной очереди недоставленных нетранзакционных сообщений код URI должен иметь вид net.msmq://localhost/system$;DeadLetter.To read messages from a system non-transactional dead-letter queue, the URI must be of the form: net.msmq://localhost/system$;DeadLetter.

  • Чтобы считать сообщения из пользовательской очереди недоставленных сообщений, URI должен иметь вид: net. msmq://ЛОКАЛХОСТ/привате/ <custom-dlq-name> , где Custom-очередь dlq-name — это имя пользовательской очереди недоставленных сообщений.To read messages from a custom dead-letter queue, the URI must be of the form:net.msmq://localhost/private/<custom-dlq-name> where custom-dlq-name is the name of the custom dead-letter queue.

Дополнительные сведения об устранении очередей см. в статье конечные точки службы и адресация очередей.For more information about how to address queues, see Service Endpoints and Queue Addressing.

Стек WCF на получателе сопоставляет адреса, прослушиваемые службой, с адресом в сообщении.The WCF stack on the receiver matches addresses that the service is listening on with the address on the message. Если адреса совпадают, сообщение отправляется, если нет - не отправляется.If the addresses match, the message is dispatched; if not, the message is not dispatched. Это может создать проблемы при чтении сообщений из очереди недоставленных сообщений, поскольку сообщения в очереди недоставленных сообщений обычно адресованы не службе и не службе очереди отправленных сообщений.This can cause problems when reading from the dead-letter queue, because messages in the dead-letter queue are typically addressed to the service and not the dead-letter queue service. Поэтому для чтения службой сообщений из очереди недоставленных сообщений необходима установка фильтра по адресу ServiceBehavior, которая дает команду стеку считать совпадающими все сообщения в очереди независимо от адресата.Therefore, the service reading from the dead-letter queue must install an address filter ServiceBehavior that instructs the stack to match all messages in the queue independently of the addressee. В частности, нужно добавить ServiceBehavior при помощи параметра Anyв службу, считывающую сообщения из очереди недоставленных сообщений.Specifically, you must add a ServiceBehavior with the Any parameter to the service reading messages from the dead-letter queue.

Обработка подозрительных сообщений из очереди недоставленных сообщенийPoison Message Handling from the Dead-Letter Queue

При некоторых условиях в очередях недоставленных сообщений возможна обработка подозрительных сообщений.Poison message handling is available on dead-letter queues, with some conditions. Поскольку создать вложенные очереди из системных очередей нельзя, ReceiveErrorHandling при прочтении из системной очереди недоставленных сообщений не может быть установлен в значение Move.Because you cannot create sub-queues from system queues, when reading from the system dead-letter queue, the ReceiveErrorHandling cannot be set to Move. Обратите внимание, что при чтении сообщений из пользовательской очереди недоставленных сообщений могут использоваться вложенные очереди, и, таким образом, Move является подходящим средством удаления подозрительного сообщения.Note that if you are reading from a custom dead-letter queue, you can have sub-queues and, therefore, Move is a valid disposition for the poison message.

Если ReceiveErrorHandling установлен в значение Reject, подозрительные сообщения при чтении из пользовательской очереди недоставленных сообщений помещаются в системную очередь недоставленных сообщений.When ReceiveErrorHandling is set to Reject, when reading from the custom dead letter queue, the poison message is put in the system dead-letter queue. При чтении из системной очереди недоставленных сообщений сообщение отбрасывается (удаляется).If reading from the system dead-letter queue, the message is dropped (purged). При возврате из системной очереди недоставленных сообщений в MSMQ сообщение отбрасывается (удаляется).A reject from a system dead-letter queue in MSMQ drops (purges) the message.

ПримерExample

В приведенном ниже примере показано, как создавать очередь недоставленных сообщений и как использовать ее для обработки просроченных сообщений.The following example shows how to create a dead-letter queue and how to use it to process expired messages. Этот пример основан на примере в процедуре обмена сообщениями в очереди с конечными точками WCF.The example is based on the example in How to: Exchange Queued Messages with WCF Endpoints. В приведенном ниже примере показано, как писать клиентский код для службы обработки заказов, в которой используется очередь недоставленных сообщений для каждого приложения.The following example shows how to write the client code to the order processing service that uses a dead-letter queue for each application. В примере также показано, как обрабатывать сообщения из очереди недоставленных сообщений.The example also shows how to process messages from the dead-letter queue.

В следующем примере показан код для клиента, задающего очередь недоставленных сообщений для каждого приложения.The following is code for a client that specifies a dead-letter queue for each 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

В следующем примере показан код для файла конфигурации клиента.The following is code for the client configuration file.

В следующем примере показан код для службы обработки сообщений из очереди недоставленных сообщений.The following is code for a service processing messages from a 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 ", 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

В следующем примере показан код для файла конфигурации службы очереди недоставленных сообщений.The following is code for the dead-letter queue service configuration file.

См. такжеSee also