Netzwerklatenz und -durchsatz

Drei hauptprobleme beziehen sich auf die optimale Nutzung des Netzwerks:

  • Netzwerklatenz
  • Netzwerksättigung
  • Auswirkungen auf die Paketverarbeitung

In diesem Abschnitt wird eine Programmieraufgabe vorgestellt, die die Verwendung von RPC erfordert, und anschließend werden 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 erläutert.

Bevor Sie die beiden Lösungen erörtern, werden in den nächsten Abschnitten Probleme mit der Netzwerkleistung erläutert und erläutert.

Netzwerklatenz

Netzwerkbandbreite und Netzwerklatenz sind separate Begriffe. Netzwerke mit hoher Bandbreite garantieren keine geringe Latenz. Beispielsweise weist ein Netzwerkpfad, der eine Satellitenverbindung durchsucht, häufig eine hohe Latenz auf, obwohl der Durchsatz sehr hoch ist. Es ist nicht ungewöhnlich, dass ein Netzwerkroundtrip, der eine Satellitenverbindung durchsucht, über eine Wartezeit von fünf oder mehr Sekunden verfügt. Eine solche Verzögerung hat folgende Auswirkungen: Eine Anwendung, die zum Senden einer Anforderung, zum Warten auf eine Antwort, zum Senden einer weiteren Anforderung, zum Warten auf eine andere Antwort usw. entwickelt wurde, 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 in der Regel konstant bleibt. Daher ist es unwahrscheinlich, dass die Latenz für vorhandene Satellitennetzwerke verbessert wird.

Netzwerksättigung

Eine gewisse Sättigung tritt in vielen Netzwerken auf. Die am einfachsten zu sättigenden Netzwerke sind langsame Modemverbindungen, z. B. 56.000 analoge Standardmodems. Ethernet-Verbindungen mit vielen Computern in einem einzelnen Segment können jedoch auch überlastigt werden. Dasselbe gilt für Wide Area Networks mit einer geringen Bandbreite oder einer anderweitig überlasteten Verbindung, z. B. einem Router oder Switch, der eine begrenzte Menge an Datenverkehr verarbeiten kann. Wenn das Netzwerk in solchen Fällen mehr Pakete sendet, als die schwachste Verknüpfung verarbeiten kann, werden Pakete gelöscht. Um Überlastungen zu vermeiden, skaliert der Windows TCP-Stapel zurück, wenn verworfene Pakete erkannt werden, was zu erheblichen Verzögerungen führen kann.

Auswirkungen auf die Paketverarbeitung

Wenn Programme für Umgebungen höherer Ebene wie RPC, COM und sogar Windows Sockets entwickelt werden, vergessen Entwickler tendenziell, wie viel Arbeit im Hintergrund für jedes gesendete oder empfangene Paket erfolgt. Wenn ein Paket aus dem Netzwerk eingeht, wird ein Interrupt von der Netzwerkkarte vom Computer bedient. Anschließend wird ein verzögerter Prozeduraufruf (Deferred Procedure Call, DPC) in die Warteschlange eingereiht und muss die Treiber durchlaufen. Wenn eine Sicherheitsform verwendet wird, muss das Paket möglicherweise entschlüsselt oder der kryptografische Hash überprüft werden. Bei jedem Zustand müssen auch mehrere Gültigkeitsprüfungen durchgeführt werden. Erst dann erreicht das Paket das endgültige Ziel: den Servercode. Das Senden vieler kleiner Datenblöcke führt zu einem Mehraufwand bei der Paketverarbeitung für jeden kleinen Datenabschnitt. Das Senden eines großen Datenabschnitts verbraucht in der Regel deutlich weniger CPU-Zeit im gesamten System, 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

Imagine eine Anwendung, 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 Studio-Dateiroutinen für lokale Dateien zu spiegeln. Dies kann zu einer bereinigten und vertrauten Schnittstelle führen. Hier ist eine abgekü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 zu sein, aber tatsächlich ist dies ein zeitbewähntes Rezept für leistungsbezogene Notfälle. Im Gegensatz zur gängigen Meinung ist der Remoteprozeduraufruf nicht einfach ein lokaler Prozeduraufruf mit einer Verbindung zwischen dem Aufrufer und dem Aufgerufenen.

Um zu sehen, wie dieses Rezept die Leistung verbraucht, betrachten Sie eine 2K-Datei, in der 20 Bytes vom Anfang und dann 20 Bytes vom Ende gelesen werden, und sehen Sie sich an, wie dies funktioniert. Auf clientseitiger Seite werden die folgenden Aufrufe durchgeführt (viele Codepfade werden aus Gründen der Kürze weggelassen):

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

Stellen Sie sich nun vor, dass der Server durch eine Satellitenverbindung mit einer Roundtripzeit von fünf Sekunden vom Client getrennt ist. Jeder dieser Aufrufe muss auf eine Antwort warten, bevor er fortgesetzt werden kann. Dies bedeutet ein absolutes Minimum für die Ausführung dieser Sequenz von 25 Sekunden. Da wir nur 40 Bytes abrufen, ist dies eine unerhört langsame Leistung. Die Kunden dieser Anwendung wären sehr mühsam.

Stellen Sie sich nun vor, das Netzwerk ist überlastet, da die Kapazität eines Routers an einer Stelle im Netzwerkpfad überlastet ist. Dieser Entwurf zwingt den Router, mindestens 10 Pakete zu verarbeiten, wenn keine Sicherheit besteht (eines für jede Anforderung und eines für jede Antwort). Auch das ist nicht gut.

Dieser Entwurf erzwingt auch, dass der Server fünf Pakete empfängt und fünf Pakete sendet. Auch hier keine sehr gute Implementierung.

Beispiel 2: Ein besser entworfener RPC-Server

Lassen Sie uns die in Beispiel 1 erläuterte Schnittstelle umgestalten und prüfen, ob wir sie verbessern können. Es ist wichtig zu beachten, dass zum Wirklichen dieses Servers Kenntnisse des Verwendungsmusters für die angegebenen Dateien erforderlich sind: Dieses Wissen wird in diesem Beispiel nicht vorausgesetzt. Daher ist dies ein besser entworfener RPC-Server, aber kein optimal entworfener RPC-Server.

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

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 auf einen Lese- und Schreibvorgang reduziert, was ein optionales Öffnen für denselben Vorgang sowie ein optionales Schließen und Suchen ermöglicht.

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

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

Wenn Sie den besser entworfenen RPC-Server in Betracht ziehen, überprüft der Server beim zweiten Aufruf, ob der _ Dateiname NULL ist, und verwendet die gespeicherte geöffnete Datei in rfp. Anschließend werden Suchanweisungen angezeigt, und der Dateizeiger wird 20 Bytes vor dem Ende positioniert, bevor er gelesen wird. Anschließend wird erkannt, dass das Flag CloseWhenDone auf TRUE festgelegt ist. Anschließend wird die Datei geschlossen, und rfp wird geschlossen.

Im Netzwerk mit hoher Latenz dauert diese bessere Version 10 Sekunden (2,5-mal schneller) und erfordert nur die Verarbeitung von vier Paketen. zwei empfangen vom Server, und zwei werden vom Server gesendet. Die zusätzlichen Ifs und unmarshaling, die der Server ausführt, sind im Vergleich zu allen anderen Vernachlässigungen vernachlässigbar.

Wenn die kausale Reihenfolge ordnungsgemäß angegeben wird, kann die Schnittstelle sogar asynchron ausgeführt werden, und die beiden Aufrufe können parallel gesendet werden. Wenn die kausale Reihenfolge verwendet wird, werden Aufrufe weiterhin in der Reihenfolge verteilt, was bedeutet, dass im Netzwerk mit hoher Latenz nur eine Verzögerung von fünf Sekunden erreicht wird, 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 annimmt, wobei jedes Element des Arrays einen bestimmten Dateivorgang beschreibt. Eine Remotevariation von Punkt-/Erfassungs-E/A. Der Ansatz lohnt sich, solange das Ergebnis jedes Vorgangs keine weitere Verarbeitung auf dem Client erfordert. Mit anderen Worten: Die Anwendung liest die 20 Bytes am Ende, unabhängig davon, was die ersten 20 Bytes sind.

Wenn jedoch einige Verarbeitungen für die ersten 20 Bytes ausgeführt werden müssen, nachdem sie gelesen wurden, um den nächsten Vorgang zu bestimmen, funktioniert das Reduzieren alles in einen Vorgang nicht (zumindest nicht in allen Fällen). Das Unerreichte von RPC ist, dass eine Anwendung über beide Methoden in der Schnittstelle verfügen und je nach Bedarf eine der beiden Methoden aufrufen kann.

Wenn das Netzwerk beteiligt ist, empfiehlt es sich im Allgemeinen, so viele Aufrufe wie möglich in einem einzelnen Aufruf zu kombinieren. 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.