Freigeben über


Skalierbarkeit

Der Begriff Skalierbarkeit wird häufig missbraucht. Für diesen Abschnitt wird eine duale Definition bereitgestellt:

  • Skalierbarkeit ist die Möglichkeit, die verfügbare Verarbeitungsleistung eines Multiprozessorsystems (2, 4, 8, 32 oder mehr Prozessoren) vollständig zu nutzen.
  • Skalierbarkeit ist die Möglichkeit, eine große Anzahl von Clients zu bedienen.

Diese beiden verwandten Definitionen werden häufig als Hochskalieren bezeichnet. Am Ende dieses Themas finden Sie Tipps zum Herunterskalieren.

Diese Diskussion konzentriert sich ausschließlich auf das Schreiben skalierbarer Server, nicht auf skalierbare Clients, da skalierbare Server häufigere Anforderungen sind. In diesem Abschnitt wird auch die Skalierbarkeit nur im Kontext von RPC- und RPC-Servern behandelt. Bewährte Methoden für die Skalierbarkeit, z. B. das Reduzieren von Konflikten, das Vermeiden häufiger Cachefehler an globalen Speicherstandorten oder die Vermeidung von Falschfreigaben, werden hier nicht erläutert.

RPC-Threadingmodell

Wenn ein RPC-Aufruf von einem Server empfangen wird, wird die Serverroutine (Managerroutine) für einen thread aufgerufen, der von RPC bereitgestellt wird. RPC verwendet einen adaptiven Threadpool, der sich erhöht und verringert, wenn die Workload schwankt. Ab Windows 2000 ist der Kern des RPC-Threadpools ein Vervollständigungsport. Der Vervollständigungsport und seine Verwendung durch RPC sind auf Serverroutinen mit null bis geringem Konflikt eingestellt. Dies bedeutet, dass der RPC-Threadpool die Anzahl der Wartungsthreads aggressiv erhöht, wenn einige blockiert werden. Es wird davon ausgegangen, dass die Blockierung selten ist, und wenn ein Thread blockiert wird, ist dies eine temporäre Bedingung, die schnell aufgelöst wird. Dieser Ansatz ermöglicht Effizienz bei Servern mit geringen Konflikten. Ein RPC-Server für Void-Aufrufe, der auf einem 550-MHz-Server mit acht Prozessoren arbeitet, auf den über ein Hochgeschwindigkeitsnetzwerk des System Area Network (SAN) zugegriffen wird, bedient über 30.000 void Anrufe pro Sekunde von über 200 Remoteclients. Dies entspricht mehr als 108 Millionen Anrufen pro Stunde.

Das Ergebnis ist, dass der aggressive Threadpool tatsächlich im Weg steht, wenn die Konflikte auf dem Server hoch sind. Stellen Sie sich zur Veranschaulichung einen Hochleistungsserver vor, der für den Remotezugriff auf Dateien verwendet wird. Angenommen, der Server verfolgt den einfachsten Ansatz: Er liest/schreibt die Datei einfach synchron in dem Thread, für den dieser RPC die Serverroutine aufruft. Angenommen, wir verfügen über einen Server mit vier Prozessoren, der viele Clients bedient.

Der Server beginnt mit fünf Threads (dies variiert tatsächlich, aber der Einfachheit halber werden fünf Threads verwendet). Sobald RPC den ersten RPC-Aufruf aufgenommen hat, wird der Aufruf an die Serverroutine weitergeleitet, und die Serverroutine gibt die E/A aus. Selten fehlt der Dateicache und blockiert das Warten auf das Ergebnis. Sobald es blockiert wird, wird der fünfte Thread freigegeben, um eine Anforderung aufzunehmen, und ein sechster Thread wird als heißer Standbymodus erstellt. Angenommen, jeder zehnte E/A-Vorgang verpasst den Cache und blockiert 100 Millisekunden (ein beliebiger Zeitwert), und vorausgesetzt, der Server mit vier Prozessoren bedient etwa 20.000 Aufrufe pro Sekunde (5.000 Aufrufe pro Prozessor), würde eine vereinfachte Modellierung vorhersagen, dass jeder Prozessor etwa 50 Threads erzeugt. Dies setzt voraus, dass ein Aufruf, der blockiert wird, alle 2 Millisekunden kommt, und nach 100 Millisekunden wird der erste Thread wieder freigegeben, sodass sich der Pool bei etwa 200 Threads (50 pro Prozessor) stabilisiert.

Das tatsächliche Verhalten ist komplizierter, da die hohe Anzahl von Threads zu zusätzlichen Kontextwechseln führt, die den Server verlangsamen und auch die Geschwindigkeit der Erstellung neuer Threads verlangsamen, aber die Grundidee ist klar. Die Anzahl der Threads steigt schnell, wenn Threads auf dem Server blockieren und auf etwas warten (sei es eine E/A oder der Zugriff auf eine Ressource).

RPC und der Vervollständigungsport, der eingehende Anforderungen abgibt, versuchen, die Anzahl der verwendbaren RPC-Threads auf dem Server beizubehalten, um der Anzahl der Prozessoren auf dem Computer zu entsprechen. Dies bedeutet, dass auf einem Server mit vier Prozessoren der fünfte Thread, sobald ein Thread zu RPC zurückkehrt, wenn es vier oder mehr verwendbare RPC-Threads gibt, keine neue Anforderung aufnehmen darf, sondern sich stattdessen in einem heißen Standby-Zustand befindet, falls einer der derzeit verwendbaren Threadsblöcke verwendet wird. Wenn der fünfte Thread lange genug als Hot Standby wartet, ohne dass die Anzahl der verwendbaren RPC-Threads unter die Anzahl der Prozessoren fällt, wird er freigegeben, d. h. der Threadpool wird verringert.

Stellen Sie sich einen Server mit vielen Threads vor. Wie bereits erläutert, hat ein RPC-Server viele Threads, aber nur, wenn die Threads häufig blockiert werden. Auf einem Server, auf dem Threads häufig blockiert werden, wird ein Thread, der zu RPC zurückkehrt, bald aus der Hot Standby-Liste genommen, da alle derzeit verwendbaren Threads blockieren und eine Verarbeitungsanforderung erhalten. Wenn ein Thread blockiert wird, wechselt der Threadverteiler im Kernel den Kontext zu einem anderen Thread. Dieser Kontextwechsel allein verbraucht CPU-Zyklen. Der nächste Thread führt unterschiedliche Code aus, greift auf unterschiedliche Datenstrukturen zu und verfügt über einen anderen Stapel, was bedeutet, dass die Trefferrate des Speichercaches (die L1- und L2-Caches) viel niedriger ist, was zu einer langsameren Ausführung führt. Die zahlreichen Threads, die gleichzeitig ausgeführt werden, erhöhen die Konflikte für vorhandene Ressourcen, z. B. Heap, kritische Abschnitte im Servercode usw. Dies erhöht die Konflikte, wenn Konvois über Ressourcen bilden. Wenn der Arbeitsspeicher niedrig ist, führt der Arbeitsspeicherdruck, der durch die große und wachsende Anzahl von Threads ausgeübt wird, zu Seitenfehlern, die die Geschwindigkeit, mit der die Threads blockieren, weiter erhöhen und dazu führen, dass noch mehr Threads erstellt werden. Je nachdem, wie oft er blockiert wird und wie viel physischer Arbeitsspeicher verfügbar ist, kann sich der Server entweder mit einer niedrigeren Leistungsstufe mit einer hohen Kontextwechselrate stabilisieren oder sich bis zu dem Punkt verschlechtern, an dem er nur wiederholt auf die Festplatte und den Kontext wechselt, ohne tatsächliche Arbeit auszuführen. Diese Situation wird sich natürlich nicht unter leichter Workload zeigen, aber eine hohe Workload bringt das Problem schnell an die Oberfläche.

Wie kann dies verhindert werden? Wenn Threads voraussichtlich blockiert werden, deklarieren Sie Aufrufe als asynchron, und sobald die Anforderung in die Serverroutine gelangt ist, stellen Sie sie in eine Warteschlange in einen Pool von Workerthreads, die die asynchronen Funktionen des E/A-Systems und/oder RPC verwenden. Wenn der Server wiederum RPC-Aufrufe ausführt, führen Sie diese asynchron aus, und stellen Sie sicher, dass die Warteschlange nicht zu groß wird. Wenn die Serverroutine Datei-E/A ausführt, verwenden Sie asynchrone Datei-E/A, um mehrere Anforderungen an das E/A-System in die Warteschlange zu stellen und nur wenige Threads in die Warteschlange zu stellen und die Ergebnisse abzurufen. Wenn die Serverroutine erneut Netzwerk-E/A ausführt, verwenden Sie die asynchronen Funktionen des Systems, um die Anforderungen auszugeben und die Antworten asynchron abzurufen, und verwenden Sie so wenige Threads wie möglich. Wenn die E/A abgeschlossen ist oder der vom Server getätigte RPC-Aufruf abgeschlossen ist, schließen Sie den asynchronen RPC-Aufruf ab, der die Anforderung übermittelt hat. Dadurch kann der Server mit so wenigen Threads wie möglich ausgeführt werden, was die Leistung und die Anzahl der Clients erhöht, die ein Server bedienen kann.

Aufskalieren

RPC kann für den Netzwerklastenausgleich (Network Load Balancing, NLB) konfiguriert werden, wenn NLB so konfiguriert ist, dass alle Anforderungen von einer bestimmten Clientadresse an denselben Server gehen. Da jeder RPC-Client einen Verbindungspool öffnet (weitere Informationen finden Sie unter RPC und das Netzwerk), ist es wichtig, dass alle Verbindungen aus dem Pool des angegebenen Clients auf demselben Servercomputer enden. Solange diese Bedingung erfüllt ist, kann ein NLB-Cluster so konfiguriert werden, dass er als ein großer RPC-Server mit potenziell hervorragender Skalierbarkeit funktioniert.