배달 못 한 편지 큐를 사용하여 메시지 전송 오류 처리
대기 중인 메시지를 배달하지 못할 수 있습니다. 이러한 실패 메시지는 배달 못 한 편지 큐에 기록됩니다. 네트워크 오류, 삭제된 큐, 꽉 찬 큐, 인증 오류 또는 정시 배달 실패와 같은 이유로 인해 배달 실패가 발생할 수 있습니다.
대기 중인 메시지는 수신 응용 프로그램이 적절한 시간 내에 큐에서 메시지를 읽지 못하는 경우 오랫동안 큐에 남을 수 있습니다. 이러한 동작은 시간이 중요한 메시지에 적합하지 않을 수 있습니다. 시간이 중요한 메시지에는 대기 중인 바인딩에 설정되어 있는 TTL(Time to Live) 속성이 있으며, 이 속성은 메시지가 만료되기 전에 큐에 있을 수 있는 시간을 나타냅니다. 만료된 메시지는 배달 못한 편지 큐라는 특수 큐로 보내집니다. 또한 큐 할당량 초과 또는 인증 오류 등 다른 이유로 메시지가 배달 못 한 편지 큐에 배치될 수도 있습니다.
일반적으로 응용 프로그램에서는 배달 못 한 편지 큐에서 메시지를 읽는 보정 논리 및 실패 이유를 작성합니다. 보정 논리는 실패의 원인에 따라 달라집니다. 예를 들어, 인증 오류의 경우 메시지에 첨부된 인증서를 수정하여 메시지를 다시 보낼 수 있습니다. 대상 큐 할당량을 초과하여 배달에 실패한 경우 할당량 문제가 해결되었기를 기대하며 배달을 다시 시도할 수 있습니다.
대부분의 큐 시스템에는 해당 시스템에서 실패한 모든 메시지가 저장되는 시스템 차원의 배달 못 한 편지 큐가 있습니다. 메시지 큐(MSMQ)에서는 두 개의 시스템 차원의 배달 못 한 편지 큐를 제공합니다. 하나는 트랜잭션 큐에 배달하지 못한 메시지를 저장하는 시스템 차원의 배달 못 한 트랜잭션 큐이고 다른 하나는 비트랜잭션 큐에 배달하지 못한 메시지를 저장하는 시스템 차원의 배달 못 한 비트랜잭션 큐입니다. 두 클라이언트에서 서로 다른 두 개의 서비스에 메시지를 보내는 경우 WCF에 있는 서로 다른 큐는 보낼 동일한 MSMQ 서비스를 공유하므로 배달 못 한 시스템 큐에 유형이 다른 여러 메시지가 있을 수 있습니다. 이 방법이 항상 최상은 아닙니다. 예를 들어 보안과 같이 경우에 따라 한 클라이언트가 배달 못 한 편지 큐에서 다른 클라이언트의 메시지를 읽을 수 없도록 할 수도 있습니다. 또한 공유된 배달 못 한 편지 큐에서는 클라이언트가 큐를 탐색하여 보낸 메시지를 찾도록 합니다. 이런 경우 배달 못 한 편지 큐에 있는 메시지의 수에 따라 지나치게 많은 비용이 들 수 있습니다. 따라서 WCF의 경우 NetMsmqBinding, MsmqIntegrationBinding, 및 Windows Vista의 MSMQ는 응용 프로그램별 배달 못 한 편지 큐라고도 하는 사용자 지정 배달 못 한 편지 큐를 제공합니다.
사용자 지정 배달 못 한 편지 큐에서는 메시지를 보내는 동일한 MSMQ 서비스를 공유하는 클라이언트를 격리할 수 있습니다.
Windows Server 2003 및 Windows XP에서 WCF(Windows Communication Foundation)는 시스템 차원의 배달 못 한 편지 큐를 대기 중인 모든 클라이언트 응용 프로그램에 제공합니다. Windows Vista에서 WCF는 배달 못 한 편지 큐를 대기 중인 각 클라이언트 응용 프로그램에 제공합니다.
배달 못 한 편지 큐의 사용 지정
배달 못 한 편지 큐는 송신 응용 프로그램의 큐 관리자에 있습니다. 이 관리자는 만료되었거나 전송 또는 배달하지 못한 메시지를 보관합니다.
바인딩에는 다음과 같은 배달 못 한 편지 큐 속성이 있습니다.
배달 못 한 편지 큐에서 메시지 읽기
배달 못 한 편지 큐에서 메시지를 읽는 응용 프로그램은 다음과 같은 사소한 차이를 제외하면 응용 프로그램 큐에서 읽는 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 매개 변수와 함께 배달 못 한 편지 큐에서 메시지를 읽는 서비스에 추가해야 합니다.
배달 못 한 편지 큐에서 포이즌 메시지 처리
경우에 따라 배달 못 한 편지 큐에서 포이즌 메시지 처리가 가능합니다. 시스템 큐에서 하위 큐를 만들 수 없기 때문에 배달 못 한 시스템 큐에서 메시지를 읽는 경우 ReceiveErrorHandling을 Move로 설정할 수 없습니다. 사용자 지정 배달 못 한 편지 큐에서 메시지를 읽는 경우 하위 큐를 사용할 수 있으므로 Move는 포이즌 메시지의 유효한 처리 방식입니다.
ReceiveErrorHandling이 Reject로 설정된 경우 사용자 지정 배달 못 한 편지 큐에서 메시지를 읽으면 포이즌 메시지가 배달 못 한 시스템 큐에 저장됩니다. 배달 못 한 시스템 큐에서 메시지를 읽는 경우 메시지가 손실/삭제됩니다. MSMQ에서 배달 못 한 시스템 큐의 거부로 인해 메시지가 손실/삭제됩니다.
예제
다음 예제에서는 배달 못 한 편지 큐를 만드는 방법 및 이를 사용하여 만료된 메시지를 처리하는 방법을 보여 줍니다. 이 예제는 방법: 대기 중인 메시지와 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>