MSMQ 4.0 中的有害訊息處理

這個範例會示範如何在服務中執行有害訊息處理。這個範例是以交易 MSMQ 繫結範例為基礎。這個範例會使用 netMsmqBinding。這個服務是自我裝載的主控台應用程式,可讓您觀察接收佇列訊息的服務。

在佇列通訊中,用戶端會使用佇列與服務通訊。更精確地說,用戶端會傳送訊息至佇列。服務會接收來自佇列的訊息。因此,服務與用戶端不需同時執行,就能使用佇列通訊。

有害訊息是指會從佇列反覆讀取的訊息,此時讀取該訊息的服務無法處理訊息,因而終止訊息讀取時所位於的交易。此時,服務會重試訊息。如果訊息有問題,理論上這種情形會不斷發生。請注意,只有當您使用交易來讀取佇列並叫用服務作業時,才會發生這個情況。

根據 MSMQ 的版本,NetMsmqBinding 支援有害訊息的有限到完整偵測。訊息已偵測為有害之後,有多種方法可以處理此訊息。同樣地,根據 MSMQ 的版本,NetMsmqBinding 會支援完整處理有害訊息的有限處理功能。

這個範例會示範 Windows Server 2003 和 Windows XP 平台上提供的有限有害訊息處理功能,以及 Windows Vista 上提供的完整有害訊息處理功能。這兩個範例的目標都是要將有害訊息從佇列移出至其他佇列,以便有害訊息服務可以接著處理這些有害訊息。

MSMQ v4.0 有害訊息處理範例

在 Windows Vista 中,MSMQ 會提供能夠用來儲存有害訊息的有害訊息子佇列功能。這個範例會示範使用 Windows Vista 處理有害訊息的最佳作法。

在 Windows Vista 中,有害訊息偵測功能已經相當成熟。有三個屬性能夠協助偵測。ReceiveRetryCount 是從佇列重新讀取指定訊息、並接著分派至應用程式以便進行處理的次數。因為訊息無法分派至應用程式,或應用程式在服務作業中回復交易而使訊息放回佇列時,該訊息就會從佇列重新讀取。MaxRetryCycles 是將訊息移至重試佇列的次數。當達到 ReceiveRetryCount 時,訊息就會移至重試佇列。RetryCycleDelay 屬性是指時間延遲,在經過此段時間之後,訊息就會從重試佇列移回主要佇列。ReceiveRetryCount 會重設為 0。這時訊息會再試一次。如果讀取訊息的所有嘗試都失敗,該訊息就會被標記為有害。

一旦訊息標記為有害,該訊息就會根據 ReceiveErrorHandling 列舉中的設定加以處理。若要重新逐一查看可能的值:

  • Fault (預設):使接聽程式和服務主機發生錯誤。
  • Drop:捨棄訊息。
  • Move:將訊息移至有害訊息子佇列。這個值只能在 Windows Vista 上使用。
  • Reject:拒絕訊息,並將訊息傳回至傳送者之寄不出的信件佇列。這個值只能在 Windows Vista 上使用。

此範例會示範對有害訊息使用 Move 處置。Move 會導致訊息移至有害訊息子佇列。

服務合約為 IOrderProcessor,這會定義適合與佇列搭配使用的單向服務。

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

服務作業會顯示訊息,指出正在處理訂單。為了示範有害訊息功能,SubmitPurchaseOrder 服務作業會擲回例外狀況,以復原在隨機叫用服務時的交易。這樣會導致訊息必須放回佇列中。最後,訊息會標示為有害。組態會設定成將有害訊息移至有害訊息子佇列。

    // Service class that implements the service contract.
    // Added code to write output to the console window.
    public class OrderProcessorService : IOrderProcessor
    {
        static Random r = new Random(137);

        [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
        public void SubmitPurchaseOrder(PurchaseOrder po)
        {

            int randomNumber = r.Next(10);

            if (randomNumber % 2 == 0)
            {
                Orders.Add(po);
                Console.WriteLine("Processing {0} ", po);
            }
            else
            {
                Console.WriteLine("Aborting transaction, cannot process purchase order: " + po.PONumber);
                Console.WriteLine();
                throw new Exception("Cannot process purchase order: " + po.PONumber);
            }
        }


        public static void OnServiceFaulted(object sender, EventArgs e) 
        {
            Console.WriteLine("Service Faulted");
        }
        

        // Host the service within this EXE console application.
        public static void Main()
        {
            // Get MSMQ queue name from app settings in configuration.
            string queueName = ConfigurationManager.AppSettings["queueName"];

            // Create the transacted MSMQ queue if necessary.
            if (!System.Messaging.MessageQueue.Exists(queueName))
                System.Messaging.MessageQueue.Create(queueName, true);

            // Get the base address that is used to listen for WS-MetaDataExchange requests.
            // This is useful to generate a proxy for the client.
            string baseAddress = ConfigurationManager.AppSettings["baseAddress"];

            // Create a ServiceHost for the OrderProcessorService type.
            ServiceHost serviceHost = new ServiceHost(typeof(OrderProcessorService), new Uri(baseAddress));

            // Hook on to the service host faulted events.
            serviceHost.Faulted += new EventHandler(OnServiceFaulted);

            // Open the ServiceHostBase to create listeners and start listening for messages.
            serviceHost.Open();

            // The service can now be accessed.
            Console.WriteLine("The service is ready.");
            Console.WriteLine("Press <ENTER> to terminate service.");
            Console.WriteLine();
            Console.ReadLine();

            if(serviceHost.State != CommunicationState.Faulted) {
                serviceHost.Close();
            }

        }
    }

服務組態包括下列有害訊息屬性:receiveRetryCountmaxRetryCyclesretryCycleDelayreceiveErrorHandling,如下列組態檔所示。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <!-- Use appSetting to configure MSMQ queue name. -->
    <add key="queueName" value=".\private$\ServiceModelSamplesPoison" />
    <add key="baseAddress" value="https://localhost:8000/orderProcessor/poisonSample"/>
  </appSettings>
  <system.serviceModel>
    <services>
      <service 
              name="Microsoft.ServiceModel.Samples.OrderProcessorService">
        <!-- Define NetMsmqEndpoint -->
        <endpoint address="net.msmq://localhost/private/ServiceModelSamplesPoison"
                  binding="netMsmqBinding"
                  bindingConfiguration="PoisonBinding" 
                  contract="Microsoft.ServiceModel.Samples.IOrderProcessor" />
      </service>
    </services>

    <bindings>
      <netMsmqBinding>
        <binding name="PoisonBinding" 
                 receiveRetryCount="0"
                 maxRetryCycles="1"
                 retryCycleDelay="00:00:05"                      
                 receiveErrorHandling="Move">
        </binding>
      </netMsmqBinding>
    </bindings>
  </system.serviceModel>
</configuration>

處理有害訊息佇列中的訊息

有害訊息服務會讀取最終有害訊息佇列中的訊息,並處理這些訊息。

有害訊息佇列中的訊息是指定址到正在處理這些訊息之服務的訊息,這個服務與有害訊息服務端點可能不同。因此,當有害訊息服務是從佇列讀取訊息時,WCF 通道層會在端點中尋找不相符的項目,而且不會分派訊息。此時,該訊息是定址到訂單處理服務,但卻是由有害訊息服務接收。即使訊息是定址到其他端點,若要繼續接收訊息,我們就必須新增 ServiceBehavior,以便篩選比對準則要比對訊息定址到的任何服務端點時的所在位址。若要成功處理從有害訊息佇列中讀取的訊息,您就必須執行這項操作。

有害訊息服務實作本身與服務實作非常相似,它會實作合約並處理訂單。程式碼範例如下所示。

    // Service class that implements the service contract.
    // Added code to write output to the console window.
    [ServiceBehavior(AddressFilterMode=AddressFilterMode.Any)]
    public class OrderProcessorService : IOrderProcessor
    {
        [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
        public void SubmitPurchaseOrder(PurchaseOrder po)
        {
            Orders.Add(po);
            Console.WriteLine("Processing {0} ", po);
        }

        public static void OnServiceFaulted(object sender, EventArgs e) 
        {
            Console.WriteLine("Service Faulted...exiting app");
            Environment.Exit(1);
        }


        // Host the service within this EXE console application.
        public static void Main()
        {
   
            // Create a ServiceHost for the OrderProcessorService type.
            ServiceHost serviceHost = new ServiceHost(typeof(OrderProcessorService));

            // Hook on to the service host faulted events.
            serviceHost.Faulted += new EventHandler(OnServiceFaulted);
            
            serviceHost.Open();

            // The service can now be accessed.
            Console.WriteLine("The poison message service is ready.");
            Console.WriteLine("Press <ENTER> to terminate service.");
            Console.WriteLine();
            Console.ReadLine();

            // Close the ServiceHostBase to shutdown the service.
            if(serviceHost.State != CommunicationState.Faulted)
            {
                serviceHost.Close();
            }
        }

與從訂單佇列中讀取訊息的訂單處理服務不同,有害訊息服務會從有害子佇列中讀取訊息。有害佇列是主要佇列的子佇列,名為 "poison" 且由 MSMQ 自動產生。若要存取有害佇列,請提供後面加上 ";" 的主要佇列名稱和子佇列名稱 (在此範例中為 -"poison"),如下列範例組態所示。

Aa395218.note(zh-tw,VS.90).gif注意:
在 MSMQ v3.0 的範例中,有害佇列名稱不是子佇列,而是我們將訊息移至其中的佇列。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="Microsoft.ServiceModel.Samples.OrderProcessorService">
        <!-- Define NetMsmqEndpoint -->
        <endpoint address="net.msmq://localhost/private/ServiceModelSamplesPoison;poison"
                  binding="netMsmqBinding"
                  contract="Microsoft.ServiceModel.Samples.IOrderProcessor" >
        </endpoint>
      </service>
    </services>

  </system.serviceModel>
</configuration> 

當您執行範例時,用戶端、服務和有害訊息服務活動都會顯示在主控台視窗中。您可以查看來自用戶端的服務接收訊息。在每個主控台視窗中按下 ENTER 鍵,即可關閉服務。

服務會開始執行、處理訂單,並隨機終止處理。如果訊息指出其已處理訂單,您就可以再次執行用戶端來傳送其他訊息,直到您清楚該服務已確實終止訊息。根據設定的有害訊息設定,訊息會在移至最終有害佇列之前嘗試經過處理一次。

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

Processing Purchase Order: 0f063b71-93e0-42a1-aa3b-bca6c7a89546
        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

Processing Purchase Order: 5ef9a4fa-5a30-4175-b455-2fb1396095fa
        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

Aborting transaction, cannot process purchase order: 23e0b991-fbf9-4438-a0e2-20adf93a4f89

啟動有害訊息服務,即可從有害佇列讀取有害訊息。在這個範例中,有害訊息服務會讀取並處理訊息。您會發現已終止和已標記為有害的採購單會由有害訊息服務所讀取。

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

Processing Purchase Order: 23e0b991-fbf9-4438-a0e2-20adf93a4f89
        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 範例的單次安裝程序

  2. 若要建置方案的 C# 或 Visual Basic .NET 版本,請遵循建置 Windows Communication Foundation 範例中的指示。

  3. 若要在單一或跨電腦組態中執行本範例,請變更佇列名稱以反映實際主機名稱 (而非 localhost),並遵循執行 Windows Communication Foundation 範例中的指示。

根據預設,安全性會透過 netMsmqBinding 繫結傳輸啟用。MsmqAuthenticationModeMsmqProtectionLevel 這兩個屬性會共同決定傳輸安全性的類型。根據預設,驗證模式會設定為 Windows,保護層級則會設定為 Sign。若要 MSMQ 提供驗證和簽署功能,則 MSMQ 必須是網域的一部分。如果您在不屬於網域的電腦上執行這個範例,就會收到下列錯誤:「使用者的內部訊息佇列憑證不存在」。

若要在加入至工作群組的電腦上執行範例

  1. 如果您的電腦不是網域的一部分,請將驗證模式和保護層級設定為 None,以關閉傳輸安全性,如下面的範例組態所示:

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

    請透過設定端點的 bindingConfiguration 屬性,確定端點與繫結相關聯。

  2. 請務必先變更 PoisonMessageServer、伺服器和用戶端上的組態,再執行範例。

    Aa395218.note(zh-tw,VS.90).gif注意:
    security mode 設定為 None,相當於將 MsmqAuthenticationModeMsmqProtectionLevelMessage 安全性設定為 None

  3. 若要讓中繼資料交換正常運作,我們要向 http 繫結登錄 URL。這時需要在更高權限的命令視窗中執行服務。否則,就會發生類似下列的例外狀況:未處理的例外狀況: System.ServiceModel.AddressAccessDeniedException: HTTP 無法登錄 URL http://+:8000/ServiceModelSamples/service/。您的處理程序沒有足夠的存取權可存取此命名空間 (如需詳細資訊,請參閱 https://go.microsoft.com/fwlink/?LinkId=70353)。---> System.Net.HttpListenerException: 拒絕存取。

Send comments about this topic to Microsoft.
© 2007 Microsoft Corporation. All rights reserved.