Sdílet prostřednictvím


Zpracování chyb přenosu zpráv pomocí front nedoručených zpráv

Doručení zpráv ve frontě může selhat. Tyto zprávy, které selhaly, se zaznamenávají do fronty nedoručených zpráv. Neúspěšné doručení může být způsobené chybami sítě, odstraněnou frontou, úplnou frontou, selháním ověřování nebo selháním doručení včas.

Zprávy ve frontě můžou zůstat ve frontě dlouhou dobu, pokud je přijímající aplikace nečte z fronty včas. Toto chování nemusí být vhodné pro zprávy citlivé na čas. Zprávy citlivé na čas mají vlastnost TTL (Time to Live) nastavenou ve frontě vazby, která označuje, jak dlouho mohou být zprávy ve frontě před vypršením jejich platnosti. Zprávy s vypršenou platností se odesílají do speciální fronty označované jako fronta nedoručených zpráv. Zprávy mohou být také vloženy do fronty nedoručených zpráv z jiných důvodů, například překročení kvóty fronty nebo kvůli selhání ověřování.

Obecně platí, že aplikace zapisuje logiku kompenzace pro čtení zpráv z fronty nedoručených zpráv a důvodů selhání. Logika kompenzace závisí na příčině selhání. V případě selhání ověřování můžete například opravit certifikát připojený ke zprávě a zprávu odeslat znovu. Pokud se doručení nezdařilo, protože došlo k dosažení kvóty cílové fronty, můžete doručení obnovit v naději, že došlo k vyřešení problému s kvótou.

Většina systémů front má frontu nedoručených zpráv v celém systému, ve které jsou uloženy všechny zprávy, které selhaly. Služba Řízení front zpráv (MSMQ) poskytuje dvě fronty nedoručených zpráv v celém systému: transakční frontu nedoručených zpráv v celém systému, která ukládá zprávy, které se nepodařilo doručit do transakční fronty, a frontu nedoručených zpráv v celém systému, která ukládá zprávy, které se nepodařilo doručit do neaktuální fronty. Pokud dva klienti odesílají zprávy do dvou různých služeb, a proto různé fronty v WCF sdílejí stejnou službu MSMQ k odeslání, je možné mít v systémové frontě nedoručených zpráv kombinaci zpráv. To není vždy optimální. V několika případech (například zabezpečení) možná nebudete chtít, aby jeden klient přečetl zprávy jiného klienta z fronty nedoručených zpráv. Sdílená fronta nedoručených zpráv také vyžaduje, aby klienti procházeli frontou, aby našli zprávu, kterou odeslali, což může být na základě počtu zpráv ve frontě nedoručených zpráv zakázané. Proto ve WCFNetMsmqBindingMsmqIntegrationBinding, a MSMQ v systému Windows Vista poskytují vlastní frontu nedoručených zpráv (někdy označovanou jako fronta nedoručených zpráv specifická pro aplikaci).

Vlastní fronta nedoručených zpráv poskytuje izolaci mezi klienty, kteří sdílejí stejnou službu MSMQ pro odesílání zpráv.

V systémech Windows Server 2003 a Windows XP poskytuje Windows Communication Foundation (WCF) frontu nedoručených zpráv pro všechny klientské aplikace ve frontě systému. V systému Windows Vista wcf poskytuje frontu nedoručených zpráv pro každou klientskou aplikaci ve frontě.

Určení použití fronty nedoručených zpráv

Fronta nedoručených zpráv je ve správci fronty odesílající aplikace. Ukládá zprávy, jejichž platnost vypršela nebo které selhaly při přenosu nebo doručení.

Vazba má následující vlastnosti fronty nedoručených zpráv:

Čtení zpráv z fronty nedoručených zpráv

Aplikace, která čte zprávy z fronty nedoručených zpráv, se podobá službě WCF, která čte z fronty aplikace s výjimkou následujících menších rozdílů:

  • Chcete-li číst zprávy z fronty nedoručených zpráv systému, musí být identifikátor URI (Uniform Resource Identifier) formuláře: net.msmq://localhost/system$; DeadXact.

  • Chcete-li číst zprávy z fronty neaktuálních nedoručených zpráv systému, musí být identifikátor URI formuláře: net.msmq://localhost/system$; DeadLetter.

  • Pokud chcete číst zprávy z vlastní fronty nedoručených zpráv, musí být identifikátor URI formuláře:net.msmq://localhost/private/<custom-dlq-name>, kde custom-dlq-name je název vlastní fronty nedoručených zpráv.

Další informace o adresování front najdete v tématu Koncové body služby a adresování front.

Zásobník WCF na příjemci odpovídá adresě, na které služba naslouchá s adresou ve zprávě. Pokud se adresy shodují, zpráva se odešle; pokud ne, zpráva se neodesílají. To může způsobit problémy při čtení z fronty nedoručených zpráv, protože zprávy ve frontě nedoručených zpráv jsou obvykle adresovány službě, nikoli službě fronty nedoručených zpráv. Služba, která čte z fronty nedoručených zpráv, proto musí nainstalovat filtr ServiceBehavior adres, který dává zásobníku pokyn, aby odpovídal všem zprávám ve frontě nezávisle na adresátovi. Konkrétně musíte do služby přidat ServiceBehavior parametr s parametrem Any , který čte zprávy z fronty nedoručených zpráv.

Zpracování zpráv z fronty nedoručených zpráv

Zpracování jedovatých zpráv je k dispozici ve frontách nedoručených zpráv s určitými podmínkami. Vzhledem k tomu, že nelze vytvořit dílčí fronty ze systémových front, nelze při čtení z fronty ReceiveErrorHandling nedoručených zpráv systému nastavit hodnotu Move. Všimněte si, že pokud čtete z vlastní fronty nedoručených zpráv, můžete mít dílčí fronty, a proto Move je platná dispozice pro zprávu o jedu.

Pokud ReceiveErrorHandling je nastavena hodnota Reject, při čtení z vlastní fronty nedoručených zpráv, je jed zpráva vložena do fronty systémových nedoručených zpráv. Při čtení z fronty systémových nedoručených zpráv se zpráva zahodí (vyprázdní). Odmítnutí fronty nedoručených zpráv systému v MSMQ zahodí (vyprázdnění) zprávy.

Příklad

Následující příklad ukazuje, jak vytvořit frontu nedoručených zpráv a jak ji použít ke zpracování zpráv s vypršenou platností. Příklad vychází z příkladu v části Postupy: Výměna zpráv zařazených do fronty s koncovými body WCF. Následující příklad ukazuje, jak napsat kód klienta do služby zpracování objednávek, která používá frontu nedoručených zpráv pro každou aplikaci. Příklad také ukazuje, jak zpracovávat zprávy z fronty nedoručených zpráv.

Následuje kód klienta, který určuje frontu nedoručených zpráv pro každou aplikaci.

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

Následuje kód konfiguračního souboru klienta.

Následuje kód pro službu zpracovávající zprávy z fronty nedoručených zpráv.

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

Následuje kód konfiguračního souboru služby fronty nedoručených zpráv.

Viz také