共用方式為


MSMQ 4.0 中的有害訊息處理

MSMQ4 範例會示範如何在服務中執行有害訊息處理。 此範例是以 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:捨棄訊息。

  • 移動:將訊息移至有害訊息子佇列。 此值僅適用於 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="http://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」),如下列範例組態所示。

注意

在 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. 如果服務優先執行,它就會檢查以確定佇列存在。 如果佇列不存在,服務將建立一個佇列。 您可以先執行服務來建立佇列,也可以透過 MSMQ 佇列管理員建立佇列。 請依照下列步驟,在 Windows 2008 中建立佇列。

    1. 在 Visual Studio 2012 中開啟伺服器管理員。

    2. 展開 [功能] 索引標籤。

    3. 以滑鼠右鍵按一下 [私人訊息佇列],然後依序選取 [新增] 和 [私人佇列]

    4. 核取 [可異動] 方塊。

    5. 輸入 ServiceModelSamplesTransacted 作為新佇列的名稱。

  3. 若要建置方案的 C# 或 Visual Basic .NET 版本,請遵循 Building the Windows Communication Foundation Samples中的指示。

  4. 若要在單一或跨電腦組態中執行本範例,請變更佇列名稱以反映實際主機名稱 (而非 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、伺服器和用戶端上的組態,再執行範例。

    注意

    security mode 設定為 None,相當於將 MsmqAuthenticationModeMsmqProtectionLevelMessage 安全性設定為 None

  3. 若要讓中繼資料交換正常運作,我們要向 http 繫結登錄 URL。 這時需要在更高權限的命令視窗中執行服務。 否則,您會收到例外狀況,例如:Unhandled Exception: System.ServiceModel.AddressAccessDeniedException: HTTP could not register URL http://+:8000/ServiceModelSamples/service/. Your process does not have access rights to this namespace (see https://go.microsoft.com/fwlink/?LinkId=70353 for details). ---> System.Net.HttpListenerException: Access is denied