共用方式為


使用 Dead-Letter 佇列來處理訊息傳輸失敗

佇列訊息可能會失敗傳遞。 這些失敗的訊息會記錄在死信佇列中。 失敗的傳遞可能是因為網路失敗、已刪除的佇列、完整佇列、驗證失敗或無法準時傳遞等原因所造成。

如果接收應用程式未及時從佇列讀取它們,佇列中的訊息可能會長時間留在佇列中。 此行為可能不適用於時間敏感性訊息。 時間敏感的訊息在佇列系結中設定了存留時間(TTL)屬性,指出訊息在過期之前可以在佇列中的時間長度。 過期的訊息會傳送至稱為死信佇列的特殊佇列。 出於其他原因,訊息也可以被放入死信佇列,例如超過佇列配額或因驗證失敗。

一般而言,應用程式會撰寫補償邏輯,從死信佇列中讀取訊息及失敗原因。 補償邏輯取決於失敗的原因。 例如,在驗證失敗的情況下,您可以更正附加訊息的憑證,然後重新傳送訊息。 如果傳遞失敗是因為達到了目標佇列的配額,您可以重新嘗試傳遞,希望配額問題已經得到解決。

大部分的佇列系統都設有系統範圍的死信佇列,用來儲存所有來自該系統的失敗訊息。 消息佇列 (MSMQ) 提供兩個系統範圍的死信佇列:一個交易式死信佇列,用於儲存無法傳遞至交易佇列的訊息;以及一個非交易式死信佇列,儲存未能傳遞至非交易佇列的訊息。 如果兩個用戶端將訊息傳送至兩個不同的服務,而 WCF 中的不同佇列因此共用相同的 MSMQ 服務來傳送,那麼系統死信佇列中可能會有混合的訊息。 這不一定是最佳。 在某些情況下(例如安全考量),您可能不希望某個用戶端從死信佇列中讀取另一個用戶端的訊息。 共用的未送達訊息佇列也需要使用者瀏覽整個佇列,以尋找他們傳送的訊息;由於未送達訊息佇列中的訊息數量,這過程可能會相當耗費成本。 因此,在 Windows Vista 上,WCFNetMsmqBindingMsmqIntegrationBinding, 和 MSMQ 提供自定義寄不出的信件佇列(有時稱為應用程式特定的寄不出的信件佇列)。

自定義寄不出的信件佇列會在共用相同 MSMQ 服務來傳送訊息的客戶端之間提供隔離。

在 Windows Server 2003 和 Windows XP 上,Windows Communication Foundation (WCF) 為所有佇列用戶端應用程式提供全系統死信佇列。 在 Windows Vista 上,WCF 會為每個用戶端佇列應用程式提供死信佇列。

指定 Dead-Letter 佇列的使用

死信佇列位於傳送應用程式的佇列管理員中。 它會儲存已過期或傳輸失敗的訊息。

綁定具有下列死信佇列屬性:

從 Dead-Letter 佇列讀取訊息

從死信佇列讀取訊息的應用程式類似於從應用程式佇列讀取的 WCF 服務,但有以下細微的差異:

  • 若要從交易死信佇列讀取訊息,統一資源識別碼 (URI) 格式必須是:net.msmq://localhost/system$;DeadXact。

  • 若要從系統非交易式寄不出的信件佇列讀取訊息,URI 的格式必須是:net.msmq://localhost/system$;DeadLetter。

  • 若要從自定義寄不出的信件佇列讀取訊息,URI 必須是 form:net.msmq://localhost/private/<custom-dlq-name,其中 > 是自定義寄不出的信件佇列的名稱。

如需如何尋址佇列的詳細資訊,請參閱 服務端點和佇列尋址

接收者上的 WCF 堆疊會比對服務正在接聽的位址與訊息上的位址。 如果位址相符,則會分派訊息;如果不是,則不會分派訊息。 這可能會導致從死信佇列讀取時發生問題,因為死信佇列中的訊息通常會尋址至服務,而不是死信佇列服務。 因此,從死信佇列讀取的服務必須安裝一個地址篩選器 ServiceBehavior ,以指示堆疊匹配佇列中所有與收件人無關的訊息。 具體來說,您必須將ServiceBehaviorAny參數加入至從寄不出的信件佇列讀取訊息的服務。

來自 Dead-Letter 佇列的有害訊息處理

毒消息處理可在死信佇列上使用,但須符合某些條件。 由於您無法從系統佇列建立子佇列,因此從系統死信佇列讀取時,ReceiveErrorHandling 無法設定為 Move。 請注意,如果您要從自訂死信佇列讀取,則可以有子佇列,因此Move是有害訊息的有效處置方式。

ReceiveErrorHandling 設定為 Reject 時,從自定義死信佇列讀取時,毒性訊息會放在系統死信佇列中。 如果從系統死信佇列讀取,則會捨棄並清除訊息。 MSMQ 系統的死信佇列會捨棄(清除)被拒絕的訊息。

範例

下列範例示範如何建立死信佇列,以及如何使用它來處理過期訊息。 此範例是以 如何:使用 WCF 端點交換佇列訊息中的範例為基礎。 下列範例示範如何撰寫供使用死信佇列的訂單處理服務所用的客戶端代碼。 此範例也會示範如何處理寄不出的信件佇列中的訊息。

以下是用戶端的程式代碼,為每個應用程式指定死信佇列。

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

以下是用戶端組態檔的程序代碼。

以下是處理死信佇列訊息的服務程式碼。

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

以下是死信佇列服務配置檔案的程式碼。

另請參閱