Freigeben über


Behandlung nicht verarbeitbarer Nachrichten in MSMQ 3.0

In diesem Beispiel wird veranschaulicht, wie die Handhabung nicht verarbeitbarer Nachrichten in einem Dienst erfolgen soll. Dieses Beispiel basiert auf dem Beispiel Abgewickelte MSMQ-Bindung. In diesem Beispiel wird die Datei netMsmqBinding verwendet. Der Dienst ist eine selbst gehostete Konsolenanwendung, die es Ihnen ermöglicht, den Dienst beim Empfang von Nachrichten in der Warteschlange zu beobachten.

In einer Warteschlangenkommunikation kommuniziert der Client über eine Warteschlange mit dem Dienst. Genauer ausgedrückt bedeutet dies, dass der Client Nachrichten an eine Warteschlange sendet. Der Dienst empfängt Nachrichten aus der Warteschlange. Folglich müssen der Dienst und der Client nicht gleichzeitig ausgeführt werden, um über eine Warteschlange zu kommunizieren.

Eine nicht verarbeitbare Nachricht ist eine Nachricht, die wiederholt aus einer Warteschlange eingelesen wird, wenn der Dienst, der die Nachricht liest, diese nicht verarbeiten kann und daher die Transaktion abbricht, unter der die Nachricht gelesen wird. In solchen Fällen wird erneut versucht, die Nachricht zu verarbeiten. Dies kann theoretisch ewig so weitergehen, wenn ein Problem mit der Nachricht vorliegt. Beachten Sie, dass dieser Fall nur eintreten kann, wenn zum Lesen aus der Warteschlange und zum Aufrufen des Dienstvorgangs Transaktionen verwendet werden.

Je nach MSMQ-Version unterstützt NetMsmqBinding eingeschränkte bis vollständige Erkennung von nicht verarbeitbaren Nachrichten. Nachdem die Nachricht als nicht verarbeitbar erkannt wurde, kann sie auf verschiedene Weisen gehandhabt werden.

In diesem Beispiel sehen Sie die eingeschränkten Funktionen für die Handhabung nicht verarbeiteter Nachrichten auf den Plattformen Windows Server 2003 und Windows XP und die vollständigen Funktionen, die Windows Vista bietet. In beiden Beispielen besteht das Ziel darin, die nicht verarbeitbare Nachricht aus der Warteschlange in eine andere Warteschlange zu verschieben, die dann von einem Dienst für nicht verarbeitbare Nachrichten verarbeitet werden kann.

Beispiel zur Behandlung nicht verarbeitbarer Nachrichten in MSMQ v3.0 (Windows Server 2003 und Windows XP)

In MSMQ v3.0 gibt es keine Unterstützung für eine Unterwarteschlange für nicht verarbeitbare Nachrichten. Daher wird in diesem Beispiel eine Warteschlange erstellt, in der nicht verarbeitbare Nachrichten gespeichert werden sollen. Dies ist die bevorzugte Methode zum Umgang mit nicht verarbeitbaren Nachrichten in MSMQ v3.0, um Datenverluste zu verhindern.

In Windows Server 2003 und Windows XP beschränkt sich die Erkennung nicht verarbeitbarer Nachrichten auf das Konfigurieren einer einzigen Eigenschaft, ReceiveRetryCount. Die ReceiveRetryCount-Eigenschaft gibt an, wie oft die Nachricht aus der Warteschlange gelesen werden kann. Windows Communication Foundation (WCF) hält diese Zahl im Arbeitsspeicher. Wenn diese Zahl erreicht ist (was bedeutet, dass die Nachricht nicht verarbeitet werden kann), wird dazu übergegangen, die nicht verarbeitbare Nachricht zu behandeln. Die ReceiveErrorHandling-Enumerationseigenschaft in der Bindung stellt dazu mögliche Werte bereit. Diese Enumerationswerte lauten:

  • "Fault" (Standard): Versetzt den Listener und auch den Diensthost in den Fehlerzustand.
  • "Drop": Verwirft die Nachricht.
  • "Move": Verschiebt die Nachricht in die Unterwarteschlange für nicht verarbeitbare Nachrichten. Dieser Wert ist nur unter Windows Vista verfügbar.
  • "Reject": Lehnt die Nachricht ab, indem diese zurück an den Sender in dessen Warteschlange für unzustellbare Nachrichten gesendet wird. Dieser Wert ist nur unter Windows Vista verfügbar.

Bei Verwendung von MSMQ v3.0 sind nur "Fault" und "Drop" gültige Werte. Das Beispiel zeigt, wie nicht verarbeitbare Nachrichten mit MSMQ v3.0 behandelt werden. Dieses Beispiel kann auch in MSMQ v4.0 ausgeführt werden, wo es genau wie in MSMQ v3.0 funktioniert.

Der Dienstvertrag lautet IOrderProcessor und definiert einen unidirektionalen Dienst, der für die Verwendung mit Warteschlangen geeignet ist.

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

Der Dienstvorgang zeigt eine Nachricht an, die angibt, dass der Auftrag verarbeitet wird. Zur Demonstration der Funktionen für nicht verarbeitbare Nachrichten löst der SubmitPurchaseOrder-Dienst eine Ausnahme aus, um bei einem zufälligen Aufruf des Diensts für die Transaktion ein Rollback durchzuführen. Dadurch wird die Nachricht in die Warteschlange zurückgestellt. Schließlich wird die Nachricht als nicht verarbeitbar markiert. Die Konfiguration ist so festgelegt, dass nicht verarbeitbare Nachrichten als fehlgeschlagen gelten. Sobald die Nachricht als nicht verarbeitbar gilt, löst der in der Warteschlange befindliche Kanal eine MsmqPoisonMessageException aus, die die MessageLookupId der nicht verarbeitbaren Nachricht enthält und den ServiceHost mit einem Fehler versieht. Ein Fehlerhandler wird angefügt, um auf diese Ausnahme hin zu überwachen und die entsprechende Aktion auszuführen. Zum Hinzufügen des Fehlerhandlers wird ein PoisonErrorBehaviorAttribute erstellt und der OrderProcessorService mit diesem Verhalten als Anmerkung versehen.

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

        [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 StartThreadProc(object stateInfo)
        {
            StartService();
        }

        public static void StartService()
        {
            // Get MSMQ queue name from app settings in configuration
            QueueName = ConfigurationManager.AppSettings["queueName"];

            // Get MSMQ queue name for the final poison queue
            PoisonQueueName = ConfigurationManager.AppSettings["poisonQueueName"];

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

            // Create the transacted poison message MSMQ queue if necessary.
            if (!System.Messaging.MessageQueue.Exists(PoisonQueueName))
                System.Messaging.MessageQueue.Create(PoisonQueueName, 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();
            
        }


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

        // Host the service within this EXE console application.
        public static void Main()
        {

            OrderProcessorService.StartService();

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

        }
    }

Das PoisonErrorBehaviorAttribute installiert den PoisonErrorHandler. Der PoisonErrorHandler ist eine Implementierung des IErrorHandler. Der IErrorHandler wird jedes Mal aufgerufen, wenn im System eine Ausnahme ausgelöst wird. In dieser Implementierung wird nach der MsmqPoisonMessageException gesucht, die anzeigt, dass eine nicht verarbeitbare Nachricht gefunden wurde. Der MSMQ-Nachrichten-Suchbezeichner wird aus der Ausnahme abgerufen, und mithilfe der System.Messaging-API wird die nicht verarbeitbare Nachricht aus der Warteschlange empfangen und zur späteren Verarbeitung in eine spezielle Warteschlange für potenziell schädliche Nachrichten verschoben. Da die Nachricht noch unter der ServiceModel-Transaktion gesperrt sein kann, wird gewartet, ob der Lesevorgang erfolgreich war oder wiederholt werden muss. Sobald die Nachricht in eine andere Warteschlange verschoben ist, können die anderen in der Warteschlange befindlichen Nachrichten verarbeitet werden, ein neuer ServiceHost wird erstellt und zum Verarbeiten geöffnet. Das folgende Codebeispiel veranschaulicht dieses Verhalten:

    public class PoisonErrorHandler : IErrorHandler
    {
        static WaitCallback orderProcessingCallback = new WaitCallback(OrderProcessorService.StartThreadProc);

        public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
        {
            // no-op -we are not interested in this.
        }
        
        // Handle poison message exception by moving the offending message out of the way for regular processing to go on.
        public bool HandleError(Exception error)
        {
            MsmqPoisonMessageException poisonException = error as MsmqPoisonMessageException;
            if (null != poisonException)
            {
                long lookupId = poisonException.MessageLookupId;
                Console.WriteLine(" Poisoned message -message look up id = {0}", lookupId);

                // Get MSMQ queue name from app settings in configuration.

                System.Messaging.MessageQueue orderQueue = new System.Messaging.MessageQueue(OrderProcessorService.QueueName);
                System.Messaging.MessageQueue poisonMessageQueue = new System.Messaging.MessageQueue(OrderProcessorService.PoisonQueueName);
                System.Messaging.Message message = null;
                
                // Use a new transaction scope to remove the message from the main application queue and add it to the poison queue.
                // The poison message service processes messages from the poison queue.
                using(TransactionScope txScope= new TransactionScope(TransactionScopeOption.RequiresNew))
                {
                    int retryCount = 0;
                    while(retryCount < 3) 
                    {
                        retryCount++;
                    
                        try 
                        {
                            // Look up the poison message using the look up id.
                            message = orderQueue.ReceiveByLookupId(lookupId);
                            if(message != null) 
                            {
                                // Send the message to the poison message queue.
                                poisonMessageQueue.Send(message, System.Messaging.MessageQueueTransactionType.Automatic);
                                
                                // complete transaction scope
                                txScope.Complete();

                                Console.WriteLine("Moved poisoned message with look up id: {0} to poison queue: {1} ", lookupId, OrderProcessorService.PoisonQueueName);
                                break;
                            }
                        
                        }
                        catch(InvalidOperationException ) 
                        {
                            //Code for the case when the message may still not be available in the queue because of a race condition in transaction or 
                            //another node in the farm may actually have taken the message.
                            if (retryCount < 3) 
                            {
                                Console.WriteLine("Trying to move poison message but message is not available, sleeping for 10 seconds before retrying");
                                Thread.Sleep(TimeSpan.FromSeconds(10));
                            }
                            else 
                            {
                                Console.WriteLine("Giving up on trying to move the message");
                            }
                        }
                    
                    }
                }

                // Restart the service host.
                Console.WriteLine();
                Console.WriteLine("Restarting the service to process rest of the messages in the queue");
                Console.WriteLine("Press <ENTER> to stop the service");
                ThreadPool.QueueUserWorkItem(orderProcessingCallback);
                return true;
            }

            return false;
        }
    }

Die Dienstkonfiguration enthält die folgenden Eigenschaften für nicht verarbeitbare Nachrichten: receiveRetryCount und receiveErrorHandling, wie in der folgenden Konfigurationsdatei gezeigt. Die maxRetryCycles-Eigenschaft und die retryCycleDelay-Eigenschaft werden beim Ausführen unter Windows Server 2003 oder Windows XP ignoriert.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <!-- Use appSetting to configure the MSMQ queue name. -->
    <add key="queueName" value=".\private$\ServiceModelSamplesPoison" />
    <add key="poisonQueueName" value=".\private$\OrderProcessorPoisonQueue" />
    <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="Fault">
        </binding>
      </netMsmqBinding>
    </bindings>
  </system.serviceModel>
</configuration>

Verarbeiten von Nachrichten aus der Warteschlange für potenziell schädliche Nachrichten

Der Dienst für nicht verarbeitbare Nachrichten liest Nachrichten aus der endgültigen Warteschlange für potenziell schädliche Nachrichten und verarbeitet sie.

Nachrichten in der Warteschlange für potenziell schädliche Nachrichten sind Nachrichten, die an den Dienst adressiert sind, der die Nachricht verarbeitet, der aber ein anderer als der Dienstendpunkt für nicht verarbeitbare Nachrichten sein kann. Wenn der Dienst für nicht verarbeitete Nachrichten Nachrichten aus der Warteschlange liest, findet die WCF-Kanalebene die fehlende Übereinstimmung bei den Endpunkten und verteilt die Nachricht nicht. In diesem Fall ist die Nachricht an den Auftragsverarbeitungsdienst adressiert, wird jedoch vom Dienst für nicht verarbeitete Nachrichten empfangen. Damit die Nachricht empfangen wird, selbst wenn sie an einen anderen Endpunkt adressiert ist, muss ein ServiceBehavior zum Filtern von Adressen hinzugefügt werden, wobei das Übereinstimmungskriterium darin besteht, dass eine Übereinstimmung mit jedem Dienstendpunkt, an den die Nachricht adressiert ist, vorliegen soll. Dies ist erforderlich, um Nachrichten, die aus der Warteschlange für potenziell schädliche Nachrichten gelesen werden, erfolgreich zu verarbeiten.

Die Implementierung des Diensts für nicht verarbeitbare Nachrichten selbst weist große Ähnlichkeiten mit der Dienstimplementierung auf. Sie implementiert den Vertrag und verarbeitet die Aufträge. Der Beispielcode sieht wie folgt aus.

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

Im Gegensatz zum Auftragsverarbeitungsdienst, der Nachrichten aus der Auftragswarteschlange liest, liest der Dienst für nicht verarbeitbare Nachrichten seine Nachrichten aus der Unterwarteschlange für nicht verarbeitbare Nachrichten. Die Warteschlange für nicht verarbeitbare Nachrichten ist eine Unterwarteschlange der Hauptwarteschlange, heißt "poison" und wird automatisch von MSMQ generiert. Für den Zugriff darauf geben Sie den Namen der Hauptwarteschlange, gefolgt von einem Semikolon und dem Namen der Unterwarteschlange (in diesem Fall -"poison") an, wie in der folgenden Beispielkonfiguration gezeigt.

Tipp

Im Beispiel für MSMQ v3.0 handelt es sich beim Namen der Warteschlange für nicht verarbeitbare Nachrichten ("poison") nicht um eine Unterwarteschlange, sondern vielmehr um die Warteschlange, in die die Nachricht verschoben wurde.

<?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> 

Wenn Sie das Beispiel ausführen, werden die Client- und Dienstaktivitäten sowie die Aktivitäten des Diensts für nicht verarbeitbare Nachrichten in Konsolenfenstern angezeigt. Sie können sehen, wie der Dienst Nachrichten vom Client empfängt. Drücken Sie in den einzelnen Konsolenfenstern die EINGABETASTE, um die Dienste herunterzufahren.

Der Dienst beginnt mit der Ausführung, verarbeitet Aufträge und beginnt zu einem nach dem Zufallsprinzip ausgewählten Zeitpunkt, die Verarbeitung abzubrechen. Wenn die Nachricht angibt, dass der Auftrag verarbeitet wurde, können Sie den Client erneut ausführen, um eine weitere Nachricht zu senden, bis Sie feststellen, dass der Dienst die Verarbeitung einer Nachricht tatsächlich abgebrochen hat. Gemäß den konfigurierten Einstellungen für nicht verarbeitbare Nachrichten wird noch einmal versucht, die Nachricht zu verarbeiten, bevor sie in die endgültige Warteschlange für nicht verarbeitbare Nachrichten ("poison") verschoben wird.

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

Starten Sie den Dienst für nicht verarbeitbare Nachrichten, um die nicht verarbeitbare Nachricht aus der Warteschlange für potenziell schädliche Nachrichten zu lesen. In diesem Beispiel liest der Dienst für nicht verarbeitbare Nachrichten die Nachricht und verarbeitet sie. Sie können sehen, dass die Bestellung, die abgebrochen und als nicht verarbeitbar gekennzeichnet wurde, vom Dienst für nicht verarbeitbare Nachrichten gelesen wird.

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

So richten Sie das Beispiel ein, erstellen es und führen es aus

  1. Stellen Sie sicher, dass Sie die Beispiele zum einmaligen Setupverfahren für Windows Communication Foundation ausgeführt haben.

  2. Zum Erstellen der C#- oder Visual Basic .NET-Edition der Projektmappe befolgen Sie die unter Erstellen der Windows Communication Foundation-Beispiele aufgeführten Anweisungen.

  3. Wenn Sie das Beispiel in einer Konfiguration mit einem einzigen Computer oder computerübergreifend ausführen möchten, befolgen Sie die Anweisungen unter Durchführen der Windows Communication Foundation-Beispiele.

Standardmäßig wird mit der netMsmqBinding-Bindung die Transportsicherheit aktiviert. Der Typ der Transportsicherheit wird durch zwei Eigenschaften festgelegt: MsmqAuthenticationMode und MsmqProtectionLevel. Standardmäßig wird der Authentifizierungsmodus auf Windows festgelegt, und die Schutzebene wird auf Sign gesetzt. Damit MSMQ das Authentifizierungs- und Signierungsfeature bereitstellt, muss es Teil einer Domäne sein. Wenn Sie dieses Beispiel auf einem Computer ausführen, der nicht Teil einer Domäne ist, wird folgende Fehlermeldung ausgegeben: "Das interne Message Queuing-Zertifikat für diesen Benutzer ist nicht vorhanden".

So führen Sie das Beispiel auf einem Computer aus, der zu einer Arbeitsgruppe gehört

  1. Wenn Ihr Computer nicht zu einer Domäne gehört, deaktivieren Sie die Transportsicherheit, indem Sie den Authentifizierungsmodus und die Schutzebene auf None festlegen, wie in der folgenden Beispielkonfiguration gezeigt.

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

    Stellen Sie sicher, dass der Endpunkt der Bindung zugeordnet ist, indem Sie das bindingConfiguration-Attribut des Endpunkts festlegen.

  2. Ändern Sie die Konfiguration auf dem PoisonMessageServer, dem Server und dem Client, bevor Sie das Beispiel ausführen.

    Tipp

    Das Festlegen von security mode auf None entspricht dem Festlegen von MsmqAuthenticationMode, MsmqProtectionLevel und der Message-Sicherheit auf None.

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