Utilizando filas de mensagens mortas para manuseio de transferência de mensagens com falha
Mensagens na fila podem falhar na entrega. Essas mensagens com falha são registradas em uma fila de mensagens mortas. A entrega com falha pode ser causada por motivos como falhas de rede, uma fila excluída, uma fila completa, uma falha de autenticação ou uma falha na entrega a tempo.
As mensagens na fila poderão permanecer na fila por muito tempo se o aplicativo de recebimento não as ler da fila em tempo hábil. Esse comportamento pode não ser apropriado para mensagens com limite de tempo. As mensagens com limite de tempo têm uma propriedade TTL (Time to Live) definida na associação que está na fila, o que indica quanto tempo as mensagens podem ficar na fila antes de expirarem. As mensagens vencidas são enviadas para uma fila especial chamada de fila de mensagens mortas. As mensagens também podem ser colocadas em uma fila de mensagens mortas por outros motivos, como exceder uma cota de fila ou passar por uma falha de autenticação.
Em geral, os aplicativos gravam lógica de compensação para ler mensagens da fila de mensagens mortas e os motivos de falha. A lógica de compensação depende da causa da falha. Por exemplo, no caso de falha de autenticação, você pode corrigir o certificado anexado com a mensagem e reenviar a mensagem. Se a entrega falhou porque a cota da fila de destino foi atingida, você pode tentar novamente a entrega na esperança de que o problema de cota tenha sido resolvido.
A maioria dos sistemas de enfileiramento tem uma fila de mensagens mortas em todo o sistema em que todas as mensagens com falha desse sistema são armazenadas. O MSMQ (Enfileiramento de Mensagens) fornece duas filas de mensagens mortas em todo o sistema: uma fila de mensagens mortas em todo o sistema transacional que armazena mensagens que falharam na entrega para a fila transacional e uma fila de mensagens mortas não transacionais em todo o sistema que armazena mensagens que falharam na entrega para a fila não transacional. Se dois clientes estiverem enviando mensagens para dois serviços diferentes e, portanto, filas diferentes no WCF estiverem compartilhando o mesmo serviço MSMQ a ser enviado, será possível ter uma combinação de mensagens na fila de mensagens mortas do sistema. Isso nem sempre é o ideal. Em vários casos (segurança, por exemplo), talvez você não seja aconselhável que um cliente leia as mensagens de outro cliente de uma fila de mensagens mortas. Uma fila de mensagens mortas compartilhada também exige que os clientes naveguem pela fila para encontrar uma mensagem que eles enviaram, o que pode ser proibitivamente caro com base no número de mensagens na fila de mensagens mortas. Portanto, no WCFNetMsmqBinding
, no MsmqIntegrationBinding,
e no MSMQ no Windows Vista, forneça uma fila de mensagens mortas personalizada (às vezes chamada de fila de mensagens mortas específica do aplicativo).
A fila de mensagens mortas personalizadas fornece isolamento entre clientes que compartilham o mesmo serviço MSMQ para enviar mensagens.
No Windows Server 2003 e no Windows XP, o WCF (Windows Communication Foundation) fornece uma fila de mensagens mortas em todo o sistema para todos os aplicativos cliente na fila. No Windows Vista, o WCF fornece uma fila de mensagens mortas para cada aplicativo cliente na fila.
Especificar o uso da fila de mensagens mortas
Uma fila de mensagens mortas está no gerenciador de filas do aplicativo de envio. Ele armazena mensagens que expiraram ou que falharam na transferência ou entrega.
A associação tem as seguintes propriedades de fila de mensagens mortas:
Ler mensagens da fila de mensagens mortas
Um aplicativo que lê mensagens de uma fila de mensagens mortas é semelhante a um serviço WCF que lê de uma fila de aplicativos, exceto pelas seguintes pequenas diferenças:
Para ler mensagens de uma fila de mensagens mortas transacionais do sistema, o URI (Uniform Resource Identifier) deve ser do formulário: net.msmq://localhost/system$;DeadXact.
Para ler mensagens de uma fila de mensagens mortas não transacionais do sistema, o URI deve ser do formulário: net.msmq://localhost/system$;DeadLetter.
Para ler mensagens de uma fila de mensagens mortas personalizadas, o URI deve ser do formulário: net.msmq://localhost/private/<custom-dlq-name> onde custom-dlq-name é o nome da fila de mensagens mortas personalizada.
Para obter mais informações sobre como lidar com filas, consulte Pontos de extremidade de serviço e endereçamento de fila.
A pilha WCF no receptor corresponde aos endereços em que o serviço está escutando com o endereço na mensagem. Se os endereços corresponderem, a mensagem será enviada; se não, a mensagem não será enviada. Isso pode causar problemas ao ler da fila de mensagens mortas, pois as mensagens na fila de mensagens mortas normalmente são endereçadas ao serviço e não ao serviço de fila de mensagens mortas. Portanto, a leitura do serviço da fila de mensagens mortas deve instalar um filtro ServiceBehavior
de endereço que instrua a pilha a corresponder a todas as mensagens na fila sem importar o destinatário. Especificamente, você deve adicionar um ServiceBehavior
com o parâmetro Any ao serviço que lê mensagens da fila de mensagens mortas.
Tratamento de mensagens suspeitas da fila de mensagens mortas
O tratamento de mensagens suspeitas está disponível em filas de mensagens mortas, com algumas condições. Como você não pode criar sub-filas a partir de filas do sistema, ao ler da fila de mensagens mortas do sistema, o ReceiveErrorHandling
não pode ser definido como Move
. Observe que, se você estiver lendo de uma fila de mensagens mortas personalizada, poderá ter sub-filas e, portanto, Move
será uma disposição válida para a mensagem suspeita.
Quando ReceiveErrorHandling
está definido como Reject
, ao ler da fila de mensagens mortas personalizadas, a mensagem suspeita é colocada na fila de mensagens mortas do sistema. Se estiver lendo da fila de mensagens mortas do sistema, a mensagem será descartada (limpa). Uma rejeição de uma fila de mensagens mortas do sistema no MSMQ remove (limpa) a mensagem.
Exemplo
O exemplo a seguir mostra como criar uma fila de mensagens mortas e como usá-la para processar mensagens expiradas. O exemplo tem base no exemplo em Como fazer intercâmbio mensagens na fila com pontos de extremidade do WCF. O exemplo a seguir mostra como gravar o código do cliente no serviço de processamento de pedidos que usa uma fila de mensagens mortas para cada aplicativo. O exemplo também mostra como processar mensagens da fila de mensagens mortas.
Veja a seguir o código de um cliente que especifica uma fila de mensagens mortas para cada aplicativo.
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
Veja a seguir o código do arquivo de configuração do cliente.
Veja a seguir o código para um serviço que processa mensagens de uma fila de mensagens mortas.
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
Veja a seguir o código do arquivo de configuração do serviço de fila de mensagens mortas.