Der WCF-Dienst kann unter Last langsam nach oben skaliert werden.

Dieser Artikel hilft Ihnen bei der Behebung des Fehlers, der auftritt, wenn Windows Communication Foundation (WCF)-Dienst unter Last langsam nach oben skaliert werden kann.

Ursprüngliche Produktversion:   Windows Communication Foundation
Ursprüngliche KB-Nummer:   2538826

Problembeschreibung

Wenn der WCF-Dienst eine Reihe von Anforderungen empfängt, wird der standardmäßige .NET E/A Completion Port (IOCP)-Threadpool möglicherweise nicht so schnell wie gewünscht skaliert, und die WCF-Antwortzeit wird dadurch erhöht. Je nach Ausführungszeit und Anzahl der empfangenen Anforderungen können Sie feststellen, dass die WCF-Ausführungszeit für jede empfangene Anforderung linear um ca. 500 ms erhöht wird, bis der Prozess genügend IOCP-Threads erstellt hat, um die Anforderungen zu warten oder die eingehende Last aufrechtzuerhalten. Das Problem ist in Diensten mit längeren Ausführungszeiten offensichtlicher. Das Problem der Skalierbarkeit des IOCP-Threadpools wird in der Regel beim ersten Laden des Prozesses nicht beobachtet.

Ursache

Drei Variablen, die sich auf die WCF-Dienstfähigkeit auswirken, können mit nahezu der gleichen Rate wie eingehende Anforderungen skaliert werden.

  1. WCF-Einschränkung

  2. .NET CLR-Wert Threadpool.GetMinThreads

  3. .NET CLR IOCP thread pool bug where IOCP threads are no longer created in a pattern corresponding to the incoming request volume prior to the Threadpool.GetMinThreads throttling value.

In diesem Artikel wird beschrieben, wie Sie das Problem mit dem .NET IOCP-Threadpool Nr. 3 beheben. Wenn aufgrund der WCF-Drosselung oder des GetMinThreads Werts Drosselungsprobleme auftreten, werden diese Einschränkungen von dieser Lösung nicht vermieden. Weitere Informationen finden Sie unten im Abschnitt " Weitere Informationen ", um Anleitungen zur Identifizierung Ihres Szenarios zu erhalten. Der IoCP-Threaderstellungsfehler sollte in der nächsten Version nach 4.0 des .NET Framework behoben werden. Dieses Skalierbarkeitsproblem ist im .NET CLR Worker-Threadpool nicht vorhanden.

Lösung

Durch Verschieben der WCF-Dienstausführung in einen anderen Threadpool entsteht möglicherweise ein geringer Mehraufwand bei der Implementierung dieser Lösung. Die Leistungsergebnisse variieren je nach WCF-Dienst. Testen Sie jeden WCF-Dienst auf einzelne Ergebnisse.

Hinweis

Wenden Sie diese Lösung an, wenn Sie einen WCF-Listener verwenden, der den eingehenden Thread beim Warten auf den WCF-Dienstcode nicht blockiert.

WCF-Listener Empfohlene Lösung
HTTP-Synchronisierungsmodul (Standard in 3.x) – wird im integrierten Anwendungspool verwendet Wechseln Sie zum Async-Handler, und wenden Sie dann die Lösung in diesem Artikel an, oder verwenden Sie alternativ einen privaten Threadpool. (siehe Links nach dieser Tabelle)
HTTP Async-Modul (Standard in 4.x) – wird im integrierten Anwendungspool verwendet Wenden Sie die Codelösung in diesem Artikel an.
ISAPI – wird im Anwendungspool im klassischen Modus verwendet Wenden Sie den privaten Threadpool an. (siehe Links nach dieser Tabelle)
tcp.Net Wenden Sie die Codelösung in diesem Artikel an.

Wenn Sie die Lösung in diesem Artikel nach der obigen Tabelle nicht anwenden können, finden Sie ein Beispiel für die Verwendung eines privaten Threadpools in einem MSDN-Artikel: Grundlagen: Synchronisierungskontexte in WCF.

Schritte zum Implementieren dieser Lösung, die den WCF-Dienst im .NET CLR Worker-Threadpool ausführt:

  1. WCF-Drosselungsschwellenwerte sollten hoch genug sein, um das erwartete Burst-Volume innerhalb akzeptabler Antwortzeiten zu verarbeiten.

  2. Wenn Sie einen der .NET CLR-Standardthreadpools Worker oder IOCP für Ihren WCF-Dienst verwenden, müssen Sie die minimale Threadanzahl (Wert, bei dem die Threaderstellungsdrosselung beginnt) für eine Zahl sicherstellen, die sie voraussichtlich gleichzeitig ausführen.

  3. Implementieren Sie den folgenden Code in Ihrem Dienst, der dann den WCF-Dienst im .NET CLR Worker-Threadpool ausführt.

    Diese Klasse wird verwendet, um die Ausführung in den .NET CLR Worker-Threadpool zu verschieben.

    public class WorkerThreadPoolSynchronizer : SynchronizationContext
    {
        public override void Post(SendOrPostCallback d, object state)
        {
         // WCF almost always uses Post
            ThreadPool.QueueUserWorkItem(new WaitCallback(d), state);
        }
    
        public override void Send(SendOrPostCallback d, object state)
        {
         // Only the peer channel in WCF uses Send
            d(state);
        }
    }
    

    Als Nächstes müssen wir eine benutzerdefinierte Attributklasse erstellen.

    [AttributeUsage(AttributeTargets.Class)]
    public class WorkerThreadPoolBehaviorAttribute : Attribute, IContractBehavior
    {
        private static WorkerThreadPoolSynchronizer synchronizer = new WorkerThreadPoolSynchronizer();
    
        void IContractBehavior.AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {
        }
    
        void IContractBehavior.ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
        }
    
        void IContractBehavior.ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
        {
        dispatchRuntime.SynchronizationContext = synchronizer;
        }
    
        void IContractBehavior.Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
        {
        }
    }
    

    Wenden Sie nun das benutzerdefinierte Attribut auf Ihren WCF-Dienst an. Beispiel:

    [WorkerThreadPoolBehavior]
    public class Service1 : IService1
    {
        public string GetData(int value)
        {
            int iSleepSec = (value * 1000);
            System.Threading.Thread.Sleep(iSleepSec);
            return string.Format("You slept for: {0} seconds", value);
        }
    }
    

Weitere Informationen

WCF verwendet den .NET CLR IOCP-Threadpool zum Ausführen des WCF-Dienstcodes. Das Problem tritt auf, wenn der .NET CLR-IOCP-Threadpool in einen Zustand wechselt, in dem er threads nicht schnell genug erstellen kann, um sofort eine Reihe von Anforderungen zu verarbeiten. Die Antwortzeit nimmt unerwartet zu, wenn neue Threads mit einer Rate von 1 pro 500 ms erstellt werden.

Das Problem wird möglicherweise offensichtlicher, wenn Der WCF-Dienst eine Technologie verwendet, die auch den .NET CLR IOCP-Threadpool verwendet. Beispielsweise nutzt der Windows Server AppFabric Cache Client diesen Threadpool in geringem Umfang.

Wenn Sie die oben beschriebenen WCF-Einschränkungsgrenzwerte nicht erreichen, können Sie anhand der folgenden Informationen ermitteln, ob das Problem mit dem .NET CLR-IOCP-Threadpool auftritt.

Die .NET CLR-Threadpools verwenden einen Wert, um zu bestimmen, wann mit der Drosselung der Erstellung von Threads begonnen werden soll. Diese Einstellung kann durch Aufrufen der ThreadPool.GetMinThreads(Int32, Int32) -Methode oder beim Analysieren eines Prozessabbilds mithilfe von ! SOS-Debuggererweiterung SOS.dll (SOS-Debugerweiterung).

0:000> ! C:\windows\Microsoft.NET\Framework64\v4.0.30319\sos.threadpool
CPU-Auslastung: 0 %
Arbeitsthread: Gesamt: 16 ausgeführt: 0 Leerlauf: 16 MaxLimit: 250 MinLimit: 125
Arbeitsanforderung in der Warteschlange: 0
Anzahl der Timer: 35
Completion Port Thread:Total: 26 Free: 0 MaxFree: 16 CurrentLimit: 28 MaxLimit: 1000 MinLimit: 125

The observed problem is when the .NET CLR IOCP thread pool enters a condition where a new thread is only created every 500 ms (two per second) prior to the thread pool MinLimit value for the thread pool. Andere erwartete Faktoren, die auch zu einer Verzögerung bei der Threaderstellung beitragen können, sind Speicherdruck oder hohe CPU-Auslastung.

Überwachen Sie den Prozess, der den WCF-Dienst hostet. Wenn Sie ein Problem beim Skalieren von Threads vor den von Ihnen festgelegten Mindestschwellenwerten feststellen, tritt möglicherweise das Problem mit dem .NET CLR-IOCP-Threadpool auf. Um festzustellen, ob dies der Fall ist, sollte die Leistung verwendet werden, um die Erstellungsrate des Prozessthreads im Vergleich zur eingehenden Anforderungsrate zu überwachen. Protokollieren oder zeigen Sie dazu die folgenden Leistungsindikatoren an (unten sehen Sie ein Beispiel für einen VON IIS (WAS) gehosteten WCF 4.0-Dienst mithilfe einer HTTP-Bindung):

Leistungsindikator Instanzen
Prozess-/Threadanzahl Alle W3WP(x)-Instanzen
HTTP-Dienstanforderungswarteschlangen /Eintreffensrate
ASP.NET Apps v(4 oder 2) / Ausgeführte Anforderungen
ASP.NET Apps v(4 oder 2) / Ausführungszeit der Anforderung

Sie können die WCF-Leistungsindikatoren verwenden, wenn sie auch aktiviert sind:

WCF-Leistungsindikatoren

Es ist normal, eine langsam wachsende Threadanzahl zu sehen, wenn die Eintreffensrate (Clientanforderungsmuster) demselben Muster folgt. Es liegt nur dann ein Problem vor, wenn es eine sofortige Spitzenzahl eingehender Anforderungen gibt und die Threadanzahl mit einer Geschwindigkeit von zwei Threads pro Sekunde langsam zunimmt, während die WCF-Antwortzeit zunimmt.

Dieser Screenshot zeigt einen Arbeitsprozess, der nach einiger Zeit auf das Problem mit der Skalierbarkeit des .NET IOCP-Threadpools gestoßen ist. Wenn der Prozess zum ersten Mal gestartet wurde, werden die IOCP-Threads normalerweise parallel zum Laden eingehender Anforderungen erstellt. In diesem AppPool (W3WP.EXE) wurden zwei WCF-Dienste ausgeführt. Ein Dienst hat den standardmäßigen .NET IOCP-Threadpool verwendet, der bei 10:22:14 und erneut um 10:23:34 eine Anzahl von 100 Anforderungen empfangen hat. Der zweite WCF-Dienst verwendet die obige Problemumgehung, um im .NET Worker-Threadpool ausgeführt zu werden, und es wurden 100 Anforderungen bei 10:22:54 empfangen. Nach dem Eintreten in diesen Zustand ist eine Prozesswiederverwendung erforderlich, um den IOCP-Threadpool in einen funktionsfähigen, skalierbaren Zustand wiederherzustellen.

Der Screenshot zeigt den Arbeitsprozess nach der Skalierbarkeit des Threadpools.