Freigeben über


Netzwerklatenz und Durchsatz

Drei wichtige Probleme beziehen sich auf eine optimale Nutzung des Netzwerks:

  • Netzwerklatenz
  • Netzwerksättigung
  • Auswirkungen auf die Paketverarbeitung

In diesem Abschnitt wird eine Programmieraufgabe eingeführt, die die Verwendung von RPC erfordert, und dann zwei Lösungen entwickelt: eine schlecht geschriebene und eine gut geschriebene. Beide Lösungen werden dann überprüft, und ihre Auswirkungen auf die Netzwerkleistung werden erörtert.

Bevor Sie die beiden Lösungen besprechen, diskutieren und klären die nächsten Abschnitte netzwerkbezogene Leistungsprobleme.

Netzwerklatenz

Netzwerkbandbreite und Netzwerklatenz sind separate Begriffe. Netzwerke mit hoher Bandbreite garantieren keine geringe Latenz. Beispielsweise hat ein Netzwerkpfad, der eine Satellitenverbindung durchläuft, häufig eine hohe Latenz, obwohl der Durchsatz sehr hoch ist. Es ist nicht ungewöhnlich, dass ein Netzwerk roundtrip einen Satellitenlink durchläuft, um fünf oder mehr Sekunden Latenz zu haben. Die Folge einer solchen Verzögerung ist dies: eine Anwendung, die für das Senden einer Anforderung konzipiert ist, auf eine Antwort warten, eine andere Anforderung senden, auf eine andere Antwort warten usw., wartet mindestens fünf Sekunden auf jeden Paketaustausch, unabhängig davon, wie schnell der Server ist. Trotz der zunehmenden Geschwindigkeit von Computern basieren Satellitenübertragungen und Netzwerkmedien auf der Lichtgeschwindigkeit, die im Allgemeinen konstant bleibt. Daher ist es unwahrscheinlich, dass Verbesserungen der Latenz für vorhandene Satellitennetzwerke auftreten.

Netzwerksättigung

Einige Sättigungen treten in vielen Netzwerken auf. Die einfachsten Netzwerke zur Sättigung sind langsame Modemverbindungen, z. B. standard 56k Analogmodems. Ethernet-Verbindungen mit vielen Computern in einem einzigen Segment können jedoch auch gesättigt werden. Das gleiche gilt für Breitraumnetzwerke mit geringer Bandbreite oder anderweitig überlastete Verbindung, z. B. router oder Switch, der eine begrenzte Menge an Datenverkehr verarbeiten kann. Wenn das Netzwerk in solchen Fällen mehr Pakete sendet als seine schwächste Verbindung verarbeiten kann, fallen Pakete ab. Um eine Überlastung des Windows TCP-Stapels zu vermeiden, wird bei verworfenen Paketen erkannt, was zu erheblichen Verzögerungen führen kann.

Auswirkungen auf die Paketverarbeitung

Wenn Programme für Umgebungen auf höherer Ebene wie RPC, COM und sogar Windows Sockets entwickelt werden, vergessen Entwickler, wie viel Arbeit hinter den Kulissen für jedes gesendete oder empfangene Paket stattfindet. Wenn ein Paket aus dem Netzwerk eintrifft, wird vom Computer eine Unterbrechung der Netzwerkkarte gewartet. Anschließend wird ein Verzögerter Prozeduraufruf (Deferred Procedure Call, DPC) in die Warteschlange gestellt und muss die Treiber durchlaufen. Wenn eine Art von Sicherheit verwendet wird, muss das Paket möglicherweise entschlüsselt oder der kryptografische Hash überprüft werden. Eine Reihe von Gültigkeitsprüfungen muss auch in jedem Zustand durchgeführt werden. Erst dann kommt das Paket am endgültigen Ziel an: der Servercode. Das Senden vieler kleiner Datenblöcke führt zu einem Paketverarbeitungsaufwand für jeden kleinen Datenabschnitt. Das Senden eines großen Datenblocks verbraucht im gesamten System tendenziell deutlich weniger CPU-Zeit, obwohl die Ausführungskosten für viele kleine Blöcke im Vergleich zu einem großen Block für die Serveranwendung identisch sein können.

Beispiel 1: Ein schlecht entworfener RPC-Server

Stellen Sie sich eine Anwendung vor, die auf Remotedateien zugreifen muss, und die Aufgabe besteht darin, eine RPC-Schnittstelle zum Bearbeiten der Remotedatei zu entwerfen. Die einfachste Lösung besteht darin, die Studiodateiroutinen für lokale Dateien zu spiegeln. Dies kann zu einer täuschend sauberen und vertrauten Schnittstelle führen. Hier ist eine gekürzte IDL-Datei:

typedef [context_handle] void *remote_file;
... .
interface remote_file
{
    remote_file remote_fopen(file_name);
    void remote_fclose(remote_file ...);
    size_t remote_fread(void *, size_t, size_t, remote_file ...);
    size_t remote_fwrite(const void *, size_t, size_t, remote_file ...);
    size_t remote_fseek(remote_file ..., long, int);
}

Dies scheint elegant genug, aber tatsächlich ist dies ein zeitwürdiges Rezept für Leistungskatastrophen. Entgegen der gängigen Meinung ist der Remoteprozeduraufruf nicht einfach ein lokaler Prozeduraufruf mit einem Draht zwischen dem Anrufer und dem Angerufenen.

Um zu sehen, wie dieses Rezept die Leistung verbrennt, ziehen Sie eine 2K-Datei in Betracht, wobei 20 Byte von Anfang an gelesen werden, und dann 20 Bytes am Ende, und sehen Sie, wie dies funktioniert. Auf der Clientseite werden die folgenden Aufrufe ausgeführt (viele Codepfade werden aus Platzgründen weggelassen):

rfp = remote_fopen("c:\\sample.txt");
remote_read(...);
remote_fseek(...);
remote_read(...);
remote_fclose(rfp);

Stellen Sie sich nun vor, dass der Server von dem Client durch eine Satellitenverbindung mit einer Fünf-Sekunden-Roundtripzeit getrennt ist. Jede dieser Aufrufe muss auf eine Antwort warten, bevor sie fortgesetzt werden kann, was ein absolutes Minimum für die Ausführung dieser Sequenz von 25 Sekunden bedeutet. Wenn wir nur 40 Bytes abrufen, ist dies empörend langsame Leistung. Kunden dieser Anwendung wären wütend.

Stellen Sie sich nun vor, dass das Netzwerk gesättigt ist, da die Kapazität eines Routers irgendwo im Netzwerkpfad überlastet ist. Dieses Design zwingt den Router, mindestens 10 Pakete zu verarbeiten, wenn wir keine Sicherheit haben (eine für jede Anforderung und eine für jede Antwort). Das ist auch nicht gut.

Dieses Design erzwingt außerdem, dass der Server fünf Pakete empfängt und fünf Pakete sendet. Auch hier ist keine sehr gute Implementierung.

Beispiel 2: Ein besser gestalteter RPC-Server

Lassen Sie uns die in Beispiel 1 erläuterte Schnittstelle neu gestalten und sehen, ob wir sie verbessern können. Es ist wichtig zu beachten, dass für diesen Server wirklich gute Kenntnisse über das Verwendungsmuster für die angegebenen Dateien erforderlich sind: Solche Kenntnisse werden für dieses Beispiel nicht angenommen. Daher ist dies ein besser entworfener RPC-Server, aber kein optimal gestalteter RPC-Server.

Die Idee in diesem Beispiel besteht darin, so viele Remotevorgänge wie möglich in einen Vorgang zu reduzieren. Der erste Versuch ist folgendes:

typedef [context_handle] void *remote_file;
typedef struct
{
    long position;
    int origin;
} remote_seek_instruction;
... .
interface remote_file
{
    remote_fread(file_name, void *, size_t, size_t, [in, out] remote_file ..., BOOL CloseWhenDone, remote_seek_instruction *...);
    size_t remote_fwrite(file_name, const void *, size_t, size_t, [in, out] remote_file ..., BOOL CloseWhenDone, remote_seek_instruction *...);
}

In diesem Beispiel werden alle Vorgänge in lese- und schreibgeschützt, sodass ein optionaler Vorgang für denselben Vorgang geöffnet werden kann, sowie eine optionale Schließen und Suche.

Die gleiche Vorgangssequenz, wenn sie in abgekürzter Form geschrieben wird, sieht wie folgt aus:

remote_read("c:\\sample.txt", ..., &rfp, FALSE, NULL);
remote_read(NULL, ..., &rfp, TRUE, seek_to_20_bytes_before_end);

Beim zweiten Aufruf überprüft der Server, ob der file_nameNULL-ist und die gespeicherte geöffnete Datei in rfp verwendet. Dann sieht es suchanweisungen und positioniert den Dateizeiger 20 Byte vor dem Ende, bevor er vorliest. Wenn Sie fertig sind, wird die CloseWhenDone- Flag auf TRUE-festgelegt, und die Datei wird geschlossen und die Datei geschlossen.

Im Netzwerk mit hoher Latenz dauert diese bessere Version 10 Sekunden, bis sie abgeschlossen ist (2,5 Mal schneller) und erfordert die Verarbeitung von nur vier Paketen. zwei empfangen vom Server und zwei Senden vom Server. Die zusätzlichen , wenn und entmarshaing der Server ausgeführt wird, sind im Vergleich zu allem anderen vernachlässigbar.

Wenn die kausale Sortierung ordnungsgemäß angegeben ist, kann die Schnittstelle sogar asynchron ausgeführt werden, und die beiden Aufrufe können parallel gesendet werden. Wenn die Kausalreihenfolge verwendet wird, werden immer noch Anrufe in der Reihenfolge verteilt, was bedeutet, dass im Netzwerk mit hoher Latenz nur eine Fünf-Sekunden-Verzögerung aussteht, obwohl die Anzahl der gesendeten und empfangenen Pakete identisch ist.

Wir können dies noch weiter reduzieren, indem wir eine Methode erstellen, die ein Array von Strukturen verwendet, jedes Element des Arrays, das einen bestimmten Dateivorgang beschreibt; eine Remotevariation von Punkt/Sammel-E/A. Der Ansatz zahlt sich aus, solange das Ergebnis jeder Operation keine weitere Verarbeitung auf dem Kunden erfordert; Mit anderen Worten, die Anwendung wird die 20 Bytes am Ende lesen, unabhängig davon, was die ersten 20 Bytes gelesen werden.

Wenn jedoch einige Verarbeitungen für die ersten 20 Bytes nach dem Lesen ausgeführt werden müssen, um den nächsten Vorgang zu ermitteln, funktioniert das Reduzieren aller Vorgänge nicht (zumindest nicht in allen Fällen). Die Eleganz von RPC besteht darin, dass eine Anwendung beide Methoden in der Schnittstelle haben kann und beide Methoden je nach Bedarf aufrufen kann.

Im Allgemeinen ist es am besten, so viele Anrufe mit einem einzelnen Anruf wie möglich zu kombinieren, wenn das Netzwerk beteiligt ist. Wenn eine Anwendung über zwei unabhängige Aktivitäten verfügt, verwenden Sie asynchrone Vorgänge, und lassen Sie sie parallel ausführen. Halten Sie die Pipeline im Wesentlichen voll.