共用方式為


使用寄不出的信件佇列來處理訊息傳輸失敗

佇列訊息可能會傳遞失敗。這些失敗的訊息都會記錄在寄不出的信件佇列中。造成傳遞失敗的原因可能是網路失敗、佇列已刪除、佇列已滿、驗證失敗,或是未能準時傳遞。

如果接收應用程式未及時讀取佇列中的訊息,佇列的訊息可能會長時間停留於佇列中。這項行為對於時間緊急的訊息可能會不適當。時間緊急訊息的佇列繫結會設定「訊息存留時間」(TTL) 屬性,此屬性會指示該訊息在過期前可於佇列中停留多久。過期訊息會傳送至稱為「寄不出的信件佇列」的特殊佇列。訊息也可能會因為其他原因而放入寄不出的信件佇列中,例如超過佇列配額或發生驗證失敗。

一般而言,應用程式都會撰寫補償邏輯來讀取寄不出的信件佇列中的訊息和失敗原因。補償邏輯會依據失敗原因而有不同。例如,在驗證失敗的情況下,您可以修正附加在訊息中的憑證,並且重新傳送訊息。如果是因為到達目標佇列配額而導致傳遞失敗,您可以在解決配額問題之後重新嘗試傳遞。

大多數佇列系統都有整個系統的寄不出的信件佇列,而該系統的所有失敗訊息都會儲存於其中。訊息佇列 (MSMQ) 提供兩種整個系統的寄不出的信件佇列,一個是整個系統的交易式寄不出的信件佇列,其中會儲存無法傳遞到交易式佇列的訊息,另一個是整個系統的非交易式寄不出的信件佇列,其中會儲存無法傳遞到非交易式佇列的訊息。如果有兩個用戶端要對兩個不同服務傳送訊息,而使得 WCF 中的不同佇列要共用相同的 MSMQ 服務來進行傳送,這時系統的寄不出的信件佇列中就可能會出現混合的訊息。這種處理不一定是最佳做法。在幾種情況下 (例如,考量安全性的情況下),您可能不想讓某個用戶端從寄不出的信件佇列中讀取到另一個用戶端的訊息。共用的寄不出的信件佇列也會要求用戶端瀏覽整個佇列來尋找自己所傳送的訊息,而根據寄不出的信件佇列中的訊息數目,這樣可能會極為耗費資源。因此,在 WCF 中,Windows Vista 上的 NetMsmqBindingMsmqIntegrationBinding, 及 MSMQ 會提供自訂的寄不出的信件佇列 (有時候稱為應用程式特定的寄不出的信件佇列)。

自訂的寄不出的信件佇列會隔離共用相同 MSMQ 服務以傳送訊息的用戶端。

在 Windows Server 2003 和 Windows XP 上,Windows Communication Foundation (WCF) 會提供所有佇列用戶端應用程式適用的整個系統的寄不出的信件佇列。在 Windows Vista 上,WCF 會針對每個佇列用戶端應用程式提供寄不出的信件佇列。

指定寄不出的信件佇列的使用方式

寄不出的信件佇列是位於傳送應用程式的佇列管理員中。它會儲存已過期的訊息或是無法傳輸或傳遞的訊息。

繫結具有下列寄不出的信件佇列屬性:

從寄不出的信件佇列讀取訊息

從寄不出的信件佇列讀取訊息的應用程式與從應用程式佇列讀取訊息的 WCF 服務很類似,除了下列微小差異之處:

  • 為了從系統的交易式寄不出的信件佇列讀取訊息,統一資源識別元 (URI) 必須採用 net.msmq://localhost/system$;DeadXact 的格式。

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

  • 為了從自訂的寄不出的信件佇列讀取訊息,URI 必須採用 form:net.msmq://localhost/private/<custom-dlq-name> 的格式,其中 custom-dlq-name 是自訂的寄不出的信件佇列的名稱。

如需詳細資訊如何定位佇列的詳細資訊,請參閱服務端點與佇列定址

接收者上的 WCF 堆疊會比對服務正在接聽的位址和該訊息上的位址。如果這些位址相符,便發送該訊息,否則,就不發送該訊息。這種做法在從寄不出的信件佇列讀取訊息時會發生問題,因為寄不出的信件佇列中的訊息通常是定位傳送給該服務,而不定位傳送給寄不出的信件佇列服務。因此,從寄不出的信件佇列讀取的服務必須安裝位址篩選 ServiceBehavior,這個行為會指示堆疊比對佇列中的所有訊息,而不管收訊者為何。具體來說,您必須在從寄不出的信件佇列讀取訊息的服務中新增含有 Any 參數的 ServiceBehavior

從寄不出的信件佇列處理有害訊息

在一些條件前提下,寄不出的信件佇列會提供有害訊息處理的功能。由於您無法從系統佇列建立子佇列,所以在從系統的寄不出的信件佇列讀取時,ReceiveErrorHandling 無法設定為 Move。請注意,如果是從自訂的寄不出的信件佇列讀取,您就可建立子佇列,因此,Move 會是有效處置有害訊息的方式。

ReceiveErrorHandling 設定為 Reject,從自訂的寄不出的信件佇列讀取時,有害訊息將會放到系統的寄不出的信件佇列中。如果是從系統的寄不出的信件佇列讀取,該訊息就會被捨棄 (清除)。在 MSMQ 中,由系統的寄不出的信件佇列傳回 Reject 時,便會捨棄 (清除) 訊息。

範例

下列範例會示範如何建立寄不出的信件佇列,以及如何使用該佇列來處理過期的訊息。這個範例是以 HOW TO:與 WCF 端點交換佇列訊息中的範例為基礎。下列範例會示範如何撰寫訂單處理服務的用戶端程式碼,該服務會分別針對每個應用程式使用寄不出的信件佇列。這個範例也會示範如何處理寄不出的信件佇列中的訊息。

下列是用戶端的程式碼,此用戶端會針對每個應用程式指定寄不出的信件佇列。

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();
        }
    }
}

下列是用戶端組態檔的程式碼。

<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>

下列是服務的程式碼,此服務會處理寄不出的信件佇列中的訊息。

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();
            }
        }
    }
}

下列是寄不出的信件佇列服務組態檔的程式碼。

<!-- 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>

另請參閱

工作

HOW TO:與 WCF 端點交換佇列訊息

概念

佇列概觀
有害訊息處理