큐에 대기된 메시지는 배달에 실패할 수 있습니다. 이러한 실패한 메시지는 배달 못 한 편지 큐에 기록됩니다. 배달 실패는 네트워크 오류, 삭제된 큐, 전체 큐, 인증 실패 또는 정시에 배달 실패와 같은 이유로 인해 발생할 수 있습니다.
큐에 대기 중인 메시지는 수신 애플리케이션이 큐에서 적시에 읽지 않는 경우 오랫동안 큐에 남아 있을 수 있습니다. 이 동작은 시간에 민감한 메시지에 적합하지 않을 수 있습니다. 시간에 민감한 메시지에는 큐에 있는 바인딩에 TTL(Time to Live) 속성이 설정되어 있으며, 이는 메시지가 만료되기 전에 큐에 있을 수 있는 기간을 나타냅니다. 만료된 메시지는 "dead-letter queue"라는 특수 큐로 전송됩니다. 큐 할당량 초과나 인증 실패와 같은 다양한 이유로 인해 메시지를 데드레터 큐에 넣을 수도 있습니다.
일반적으로 애플리케이션은 죽은 편지 큐에서 메시지와 실패 원인을 읽기 위한 보상 로직을 작성합니다. 보정 논리는 오류의 원인에 따라 달라집니다. 예를 들어 인증 실패의 경우 메시지와 함께 연결된 인증서를 수정하고 메시지를 다시 전송할 수 있습니다. 대상 큐 할당량에 도달하여 배달에 실패한 경우 할당량 문제가 해결되기를 바라며 배달을 다시 시도하면 됩니다.
대부분의 큐 시스템에는 해당 시스템에서 실패한 모든 메시지가 저장되는 시스템 전체 데드 레터 큐가 있습니다. MSMQ(메시지 큐)는 트랜잭션 큐에 배달하지 못한 메시지를 저장하는 트랜잭션 시스템 전체 실패 메시지 큐와 비 트랜잭션 큐에 배달하지 못한 메시지를 저장하는 비 트랜잭션 시스템 전체 실패 메시지 큐 두 가지 시스템 전체 실패 메시지 큐를 제공합니다. 두 클라이언트가 서로 다른 두 서비스로 메시지를 보내고 있고, 따라서 WCF의 서로 다른 큐가 동일한 MSMQ 서비스를 통해 메시지를 전송하는 경우, 시스템 데드레터 큐에 메시지가 혼합되는 상황이 발생할 수 있습니다. 항상 최적은 아닙니다. 여러 경우(예: 보안) 한 클라이언트가 죽은 편지함에서 다른 클라이언트의 메시지를 읽는 것을 원하지 않을 수 있습니다. 공유된 죽은 편지 큐를 사용하면, 클라이언트가 자신이 보낸 메시지를 찾기 위해 큐를 탐색해야 합니다. 죽은 편지 큐에 있는 메시지의 수에 따라 이는 엄청난 비용이 들 수 있습니다. 따라서 Windows Vista의 WCFNetMsmqBinding
MsmqIntegrationBinding,
및 MSMQ는 사용자 지정된 배달 못 한 편지 큐(애플리케이션별 배달 못 한 편지 큐라고도 함)를 지원합니다.
사용자 지정 데드 레터 큐는 메시지를 보내기 위해 동일한 MSMQ 서비스를 공유하는 클라이언트 간에 격리를 제공합니다.
Windows Server 2003 및 Windows XP에서 WCF(Windows Communication Foundation)는 모든 대기 중인 클라이언트 애플리케이션을 위한 시스템 전반의 배달 못 한 편지 큐를 제공합니다. Windows Vista에서 WCF는 각각의 클라이언트 애플리케이션을 위한 비활성 큐를 제공합니다.
Dead-Letter 큐 사용 설정
데드 레터 큐는 보내는 애플리케이션의 큐 관리자에 있습니다. 만료되었거나 전송 또는 배달에 실패한 메시지를 저장합니다.
바인딩에는 다음과 같은 배달 못 한 편지 큐 속성이 있습니다.
Dead-Letter 큐에서 메시지 읽기
배달 못 한 편지 큐에서 메시지를 읽는 애플리케이션은 다음과 같은 사소한 차이점을 제외하고 애플리케이션 큐에서 읽는 WCF 서비스와 유사합니다.
시스템 트랜잭션 배달 못 한 편지 큐에서 메시지를 읽으려면 URI(Uniform Resource Identifier)가 net.msmq://localhost/system$;DeadXact 형식이어야 합니다.
시스템 비트랜잭션 데드레터 큐에서 메시지를 읽으려면, URI는 다음과 같은 형식이어야 합니다: net.msmq://localhost/system$;DeadLetter.
사용자 지정 배달 못 한 편지 큐에서 메시지를 읽으려면 URI는 net.msmq://localhost/private/<custom-dlq-name> 형식이어야 합니다. 여기서 custom-dlq-name 은 사용자 지정 배달 못 한 편지 큐의 이름입니다.
큐를 처리하는 방법에 대한 자세한 내용은 서비스 엔드포인트 및 큐 주소 지정을 참조하세요.
수신기의 WCF 스택은 서비스가 대기 중인 주소와 메시지의 주소를 일치시킵니다. 주소가 일치하면 메시지가 디스패치됩니다. 그렇지 않으면 메시지가 디스패치되지 않습니다. 배달 못 한 편지 큐의 메시지는 일반적으로 배달 못 한 편지 큐 서비스가 아닌 서비스로 지정되어 있으므로, 배달 못 한 편지 큐에서 읽을 때 문제가 발생할 수 있습니다. 따라서 배달 못 한 편지 큐에서 읽는 서비스는 스택에 큐의 모든 메시지를 수신자와 상관없이 일치시킬 주소 필터 ServiceBehavior
를 설치해야 합니다. 특히 죽은 편지 큐에서 메시지를 읽는 서비스에 ServiceBehavior
매개 변수를 사용하여 Any를 추가해야 합니다.
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
다음은 비활성 메시지 큐 서비스 구성 파일에 대한 코드입니다.