使用寄不出的信件佇列來處理訊息傳輸失敗
佇列訊息可能會傳遞失敗。 這些失敗的訊息都會記錄在寄不出的信件佇列中。 造成傳遞失敗的原因可能是網路失敗、佇列已刪除、佇列已滿、驗證失敗,或是未能準時傳遞。
如果接收應用程式未及時讀取佇列中的訊息,佇列的訊息可能會長時間停留於佇列中。 這項行為對於時間緊急的訊息可能會不適當。 時間緊急訊息的佇列繫結會設定「訊息存留時間」(TTL) 屬性,此屬性會指示該訊息在過期前可於佇列中停留多久。 過期訊息會傳送至稱為「寄不出的信件佇列」的特殊佇列。 訊息也可能會因為其他原因而放入寄不出的信件佇列中,例如超過佇列配額或發生驗證失敗。
一般而言,應用程式都會撰寫補償邏輯來讀取寄不出的信件佇列中的訊息和失敗原因。 補償邏輯會依據失敗原因而有不同。 例如,在驗證失敗的情況下,您可以修正附加在訊息中的憑證,並且重新傳送訊息。 如果是因為到達目標佇列配額而導致傳遞失敗,您可以在解決配額問題之後重新嘗試傳遞。
大多數佇列系統都有整個系統的寄不出的信件佇列,而該系統的所有失敗訊息都會儲存於其中。 訊息佇列 (MSMQ) 提供兩種整個系統的寄不出的信件佇列,一個是整個系統的異動式寄不出的信件佇列,其中會儲存無法傳遞到異動式佇列的訊息,另一個是整個系統的非異動式寄不出的信件佇列,其中會儲存無法傳遞到非異動式佇列的訊息。 如果有兩個用戶端要對兩個不同服務傳送訊息,而使得 WCF 中的不同佇列共用相同的 MSMQ 服務來傳送,則在系統無效信件佇列中可能會出現混合的訊息。 這種處理不一定是最佳做法。 在幾種情況下 (例如,考量安全性的情況下),您可能不想讓某個用戶端從寄不出的信件佇列中讀取到另一個用戶端的訊息。 共用的寄不出的信件佇列也會要求用戶端瀏覽整個佇列來尋找自己所傳送的訊息,而根據寄不出的信件佇列中的訊息數目,這樣可能會極為耗費資源。 因此,在 WCF NetMsmqBinding
中,Windows Vista 上的 MsmqIntegrationBinding,
和 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
,這個行為會指示堆疊比對佇列中的所有訊息,而不管收訊者為何。 具體來說,您必須在從寄不出的信件佇列讀取訊息的服務中新增含有 ServiceBehavior
參數的 Any。
從寄不出的信件佇列處理有害訊息
在一些條件前提下,寄不出的信件佇列會提供有害訊息處理的功能。 由於您無法從系統佇列建立子佇列,所以在從系統的寄不出的信件佇列讀取時,ReceiveErrorHandling
無法設定為 Move
。 請注意,如果是從自訂的寄不出的信件佇列讀取,您就可建立子佇列,因此,Move
會是有效處置有害訊息的方式。
當 ReceiveErrorHandling
設定為 Reject
,從自訂的寄不出的信件佇列讀取時,有害訊息將會放到系統的寄不出的信件佇列中。 如果是從系統的寄不出的信件佇列讀取,該訊息就會被捨棄 (清除)。 在 MSMQ 中,由系統的寄不出的信件佇列傳回 Reject 時,便會捨棄 (清除) 訊息。
範例
下列範例會示範如何建立寄不出的信件佇列,以及如何使用該佇列來處理過期的訊息。 此範例是以操作說明:與 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 ", 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
下列是寄不出的信件佇列服務組態檔的程式碼。