死信队列

DeadLetter 示例演示如何处理和处理传递失败的消息。 它基于事务处理 MSMQ 绑定示例。 此示例使用 netMsmqBinding 绑定。 此服务是自承载控制台应用程序,通过它可以观察服务接收排队消息。

注释

本示例的设置过程和生成说明位于本主题末尾。

注释

本示例演示仅在 Windows Vista 上可用的每个应用程序死信队列。 该示例可以修改为在 Windows Server 2003 和 Windows XP 上使用 MSMQ 3.0 的默认系统全局队列。

在排队通信中,客户端使用队列与服务通信。 更确切地说,客户端将消息发送到队列。 服务从队列接收消息。 因此,服务与客户端不必同时运行,才能使用队列进行通信。

由于队列通信可能涉及一定数量的休眠状态,因此您可能希望为消息设置一个存活时间,以确保消息在超时时不会传递到应用程序。 此外,在某些情况下,应用程序必须通知消息是否传递失败。 在所有这些情况下,例如消息生存时间已过期或邮件传递失败时,消息将放入死信队列中。 然后,执行发送任务的应用程序可以读取死信队列中的消息并采取纠正措施(从不执行任何操作到纠正传递失败的原因),并重新发送消息。

NetMsmqBinding 绑定中的死信队列用下面的属性表示:

  • DeadLetterQueue 属性表示客户端需要的死信队列。 此枚举具有以下值:

  • None:客户端不需要死信队列。

  • System:系统死信队列用于存储死消息。 系统死信队列由计算机上运行的所有应用程序共享。

  • Custom:使用 CustomDeadLetterQueue 属性指定的自定义死信队列用于存储死消息。 此功能仅在 Windows Vista 上可用。 当应用程序必须使用自己的死信队列而不与同一计算机上运行的其他应用程序共享该死信队列时使用此功能。

  • CustomDeadLetterQueue 属性表示要用作死信队列的特定队列。 这仅在 Windows Vista 中可用。

在此示例中,客户端从事务范围内向服务发送一批消息,并为这些消息指定“生存时间”的任意低值(约 2 秒)。 客户端还指定一个自定义死信队列用于排队已过期的消息。

客户端应用程序可以读取死信队列中的消息,然后重试发送消息或更正导致原始消息放置在死信队列中并发送消息的错误。 在示例中,客户端显示错误消息。

服务协定如 IOrderProcessor以下示例代码所示。

[ServiceContract(Namespace="http://Microsoft.ServiceModel.Samples")]
public interface IOrderProcessor
{
    [OperationContract(IsOneWay = true)]
    void SubmitPurchaseOrder(PurchaseOrder po);
}

本示例中的服务代码是事务处理 MSMQ 绑定的代码。

与服务的通信发生在事务范围内。 服务从队列中读取消息,执行该操作,然后显示操作的结果。 应用程序还为死信消息创建死信队列。

//The service contract is defined in generatedClient.cs, generated from the service by the svcutil tool.

//Client implementation code.
class Client
{
    static void Main()
    {
        // Get MSMQ queue name from app settings in configuration
        string deadLetterQueueName = ConfigurationManager.AppSettings["deadLetterQueueName"];

        // Create the transacted MSMQ queue for storing dead message if necessary.
        if (!MessageQueue.Exists(deadLetterQueueName))
            MessageQueue.Create(deadLetterQueueName, true);

        // Create a proxy with given client endpoint configuration
        OrderProcessorClient client = new OrderProcessorClient("OrderProcessorEndpoint");

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

        Console.WriteLine();
        Console.WriteLine("Press <ENTER> to terminate client.");
        Console.ReadLine();
    }
}

客户端的配置指定了消息到达服务所需的较短时间。 如果消息无法在指定的持续时间内传输,消息将过期并移动到死信队列。

注释

客户端可以在指定时间内将消息传送到服务队列。 为确可以在操作中看到死信服务,应在启动服务之前运行客户端。 消息将超时并被传送到死信服务。

应用程序必须定义使用哪个队列作为其死信队列。 如果未指定队列,则使用默认系统范围事务性死信队列对死消息排队。 在本示例中,客户端应用程序指定其自己的应用程序死信队列。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <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>

死信消息服务从死信队列中读取消息。 死信消息服务实现 IOrderProcessor 协定。 然而,它的实现不是为了处理订单。 死信消息服务是客户端服务,没有处理订单的功能。

注释

死信队列是客户端队列,对于客户端队列管理器来说是本地队列。

死信消息服务实现可检查消息传递失败的原因并采取纠正措施。 消息失败的原因记录在两个枚举中,分别是 DeliveryFailureDeliveryStatus。 可以按照以下示例代码中所示,从MsmqMessageProperty中检索OperationContext

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

死信队列中的消息是被发送到处理消息的服务的消息。 因此,当死信消息服务从队列中读取消息时,Windows Communication Foundation (WCF) 通道层发现终结点不匹配,从而不传递该消息。 在这种情况下,消息是发给订单处理服务的,然而却被死信消息服务接收到了。 为接收发送至不同终结点的消息,需要在ServiceBehavior中指定一个地址筛选器以匹配任何地址。 这是成功处理从死信队列中读取的消息所必需的。

在此示例中,死信消息服务会重新发送消息(如果失败的原因是消息超时)。出于所有其他原因,它显示传递失败,如以下示例代码所示:

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

下面的示例演示死信消息的配置:

<?xml version="1.0" encoding="utf-8" ?>
<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>

在运行示例中,有 3 个可执行文件可以运行,以查看死信队列对每个应用程序的工作原理;客户端、服务和死信服务,该服务从每个应用程序的死信队列中读取,并将消息重新发送给服务。 所有这些应用程序都是控制台应用程序,均在控制台窗口中输出结果。

注释

由于队列正在使用中,因此客户端和服务不必同时启动和运行。 即使关闭客户端再启动服务,仍然可以接收到消息。 应启动该服务并将其关闭,以便可以创建队列。

运行客户端时,客户端会显示消息:

Press <ENTER> to terminate client.

客户端尝试发送消息,但由于短的超时,消息过期并在每个应用程序的死信队列中排队。

然后运行死信服务,该服务读取消息并显示错误代码,并将消息重新发送回服务。

The dead letter service is ready.
Press <ENTER> to terminate service.

Submitting purchase order did not succeed
Message Delivery Status: InDoubt
Message Delivery Failure: ReachQueueTimeout

Purchase order Time To Live expired
Trying to resend the message
Purchase order resent

服务启动后,读取重新发送的消息并进行处理。

The service is ready.
Press <ENTER> to terminate service.

Processing Purchase Order: 97897eff-f926-4057-a32b-af8fb11b9bf9
        Customer: somecustomer.com
        OrderDetails
                Order LineItem: 54 of Blue Widget @unit price: $29.99
                Order LineItem: 890 of Red Widget @unit price: $45.89
        Total cost of this order: $42461.56
        Order status: Pending

设置、生成和运行示例

  1. 确保已为 Windows Communication Foundation 示例 执行One-Time 安装过程。

  2. 如果先运行服务,则它将检查以确保队列存在。 如果队列不存在,服务将创建一个队列。 可以先运行服务来创建队列,也可以通过 MSMQ 队列管理器创建一个队列。 按照以下步骤在 Windows 2008 中创建队列。

    1. 在 Visual Studio 2012 中打开服务器管理器。

    2. 展开“功能”选项卡

    3. 右键单击 专用消息队列,然后选择 “新建”,专用队列

    4. 选中“事务性”框

    5. 输入 ServiceModelSamplesTransacted 作为新队列的名称。

  3. 若要生成解决方案的 C# 或 Visual Basic .NET 版本,请按照 生成 Windows Communication Foundation 示例中的说明进行操作。

  4. 若要在单计算机配置或跨计算机配置中运行示例,请相应地更改队列名称,请将 localhost 替换为计算机的全名,并按照 运行 Windows Communication Foundation 示例中的说明进行作。

在加入到工作组的计算机上运行此示例

  1. 如果计算机不是域的一部分,请将身份验证模式和保护级别设置为 None 关闭传输安全性,如以下示例配置中所示:

    <bindings>
        <netMsmqBinding>
            <binding name="TransactedBinding">
                <security mode="None"/>
            </binding>
        </netMsmqBinding>
    </bindings>
    

    通过设置终结点的属性确保终结点 bindingConfiguration 与绑定相关联。

  2. 在运行示例之前,请确保在 DeadLetterService、服务器和客户端上更改配置。

    注释

    设置 security modeNone 相当于将 MsmqAuthenticationModeMsmqProtectionLevelMessage 的安全性设置为 None

注释

默认情况下, netMsmqBinding 绑定传输会启用安全性。 两个属性 MsmqAuthenticationMode ,以及 MsmqProtectionLevel共同确定传输安全性的类型。 默认情况下,身份验证模式设置为 Windows ,保护级别设置为 Sign。 要使 MSMQ 提供身份验证和签名功能,它必须是域的一部分。 如果在不属于域的计算机上运行此示例,则会收到以下错误:“用户的内部消息队列证书不存在”。