Netzwerklatenz und -durchsatz
Drei Hauptprobleme betreffen 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 entwickelt dann zwei Lösungen: eine schlecht geschriebene und eine gut geschriebene. Beide Lösungen werden dann untersucht, und ihre Auswirkungen auf die Netzwerkleistung werden erläutert.
Bevor die beiden Lösungen erörtert werden, werden in den nächsten Abschnitten netzwerkbezogene Leistungsprobleme 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 durchquert, häufig eine hohe Latenz auf, obwohl der Durchsatz sehr hoch ist. Es ist nicht ungewöhnlich, dass eine Netzwerk-Rundreise, die eine Satellitenverbindung durchquert, fünf oder mehr Sekunden Latenz hat. Die Auswirkung einer solchen Verzögerung ist folgendes: 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 verbesserungen bei der Latenz für vorhandene Satellitennetzwerke auftreten.
Netzwerksättigung
Eine gewisse Sättigung tritt in vielen Netzwerken auf. Die am einfachsten zu sättigenden Netzwerke sind langsame Modemverbindungen, z. B. standard 56k Analogmodems. Ethernet-Verbindungen mit vielen Computern in einem einzelnen Segment können jedoch auch gesättigt werden. Das gleiche gilt für Weitverkehrsnetzwerke mit einer geringen Bandbreite oder einer anderweitig überlasteten Verbindung, z. B. einem Router oder Switch, der eine begrenzte Menge an Datenverkehr verarbeiten kann. In diesen Fällen werden Pakete gelöscht, wenn das Netzwerk mehr Pakete sendet, als sein schwächster Link verarbeiten kann. Um eine Überlastung zu vermeiden, wird der Windows-TCP-Stapel zurückskaliert, wenn gelöschte Pakete erkannt werden, 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 tendenziell, wie viel Arbeit hinter den Kulissen für jedes gesendete oder empfangene Paket geschieht. Wenn ein Paket aus dem Netzwerk eingeht, wird ein Interrupt aus dem Netzwerk Karte vom Computer gewartet. Anschließend wird ein DPC (Deferred Procedure Call) in die Warteschlange gestellt und muss seinen Weg über die Treiber nehmen. Wenn eine Art von Sicherheit verwendet wird, muss das Paket möglicherweise entschlüsselt oder der kryptografische Hash überprüft werden. In jedem Zustand müssen auch eine Reihe von Gültigkeitsprüfungen durchgeführt werden. Erst dann kommt das Paket am endgültigen Ziel an: dem Servercode. Das Senden vieler kleiner Datenblöcke führt zu Paketverarbeitungsaufwand für jeden kleinen Datenblock. Das Senden eines großen Datenblocks beansprucht im gesamten System in der Regel deutlich weniger CPU-Zeit, auch wenn die Ausführungskosten für viele kleine Blöcke im Vergleich zu einem großen Blöcke 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 Studio-Dateiroutinen für lokale Dateien zu Spiegel. Dies kann zu einer täuschend sauber 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 eigentlich ist dies ein altesbehrwürdiges Rezept für ein Leistungsdesaster. Entgegen der gängigen Meinung ist der Remoteprozeduraufruf nicht einfach ein lokaler Prozeduranruf mit einer Verbindung zwischen dem Anrufer und dem Angerufenen.
Um zu sehen, wie dieses Rezept die Leistung heizt, 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 der Clientseite werden die folgenden Aufrufe ausgefü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, was ein absolutes Minimum für die Ausführung dieser Sequenz von 25 Sekunden bedeutet. Wenn wir nur 40 Bytes abrufen, ist dies eine unverschämt langsame Leistung. Kunden dieser Anwendung wären wütend.
Stellen Sie sich nun vor, das Netzwerk ist überlastet, da die Kapazität eines Routers irgendwo im Netzwerkpfad überlastet ist. Dieser Entwurf zwingt den Router, mindestens 10 Pakete zu verarbeiten, wenn wir keine Sicherheit haben (eines für jede Anforderung und eines für jede Antwort). Auch das ist nicht gut.
Dieser Entwurf erzwingt außerdem, 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 neu gestalten und sehen, ob wir sie verbessern können. Es ist wichtig zu beachten, dass für einen wirklich guten Server Kenntnisse über das Verwendungsmuster für die angegebenen Dateien erforderlich sind: Ein solches Wissen wird für dieses Beispiel nicht vorausgesetzt. Daher handelt es sich hierbei um einen besser konzipierten RPC-Server, aber nicht um einen optimal gestalteten 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 der folgende:
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 schreibvorgänge reduziert, was ein optionales Öffnen für denselben Vorgang sowie ein optionales Schließen und Suchen ermöglicht.
Die gleiche Vorgangssequenz sieht beim Schreiben in abgekürzter Form wie folgt aus:
remote_read("c:\\sample.txt", ..., &rfp, FALSE, NULL);
remote_read(NULL, ..., &rfp, TRUE, seek_to_20_bytes_before_end);
Wenn Sie den besser gestalteten RPC-Server in Betracht ziehen, überprüft der Server beim zweiten Aufruf, ob der file_nameNULL ist, und verwendet die gespeicherte offene Datei in rfp. Dann wird angezeigt, dass Suchanweisungen vorhanden sind, und der Dateizeiger wird 20 Bytes vor dem Ende positioniert, bevor er gelesen wird. Wenn sie fertig ist, erkennt es, dass das CloseWhenDone-Flag auf TRUE festgelegt ist, schließt die Datei und schließt rfp.
Im Netzwerk mit hoher Latenz dauert die Ausführung dieser besseren Version 10 Sekunden (2,5 Mal schneller) und erfordert nur die Verarbeitung von vier Paketen; zwei empfängt vom Server und zwei sendet vom Server. Die zusätzlichen Ifs und die Entmarrung, die der Server ausführt, sind im Vergleich zu allem anderen vernachlässigbar.
Wenn die kausale Reihenfolge ordnungsgemäß angegeben ist, kann die Schnittstelle sogar asynchron erfolgen, und die beiden Aufrufe können parallel gesendet werden. Wenn die kausale Reihenfolge verwendet wird, werden Anrufe weiterhin in der richtigen Reihenfolge gesendet, was bedeutet, dass im Netzwerk mit hoher Latenz nur eine Verzögerung von fünf Sekunden besteht, obwohl die Anzahl der gesendeten und empfangenen Pakete gleich ist.
Wir können dies noch weiter reduzieren, indem wir eine Methode erstellen, die ein Array von Strukturen akzeptiert, jedes Element des Arrays, das einen bestimmten Dateivorgang beschreibt. Eine Remotevariation von Scatter/Gather-E/A. Der Ansatz zahlt sich aus, solange das Ergebnis der einzelnen Vorgänge 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 gelesen haben.
Wenn jedoch für die ersten 20 Bytes nach dem Lesen eine Verarbeitung ausgeführt werden muss, um den nächsten Vorgang zu bestimmen, funktioniert das Zusammenbrechen alles in einen Vorgang nicht (zumindest nicht in allen Fällen). Die Eleganz von RPC besteht darin, dass eine Anwendung beide Methoden in der Schnittstelle haben und je nach Bedarf beide Methoden aufrufen kann.
Im Allgemeinen ist es am besten, wenn das Netzwerk beteiligt ist, so viele Aufrufe wie möglich in einem einzelnen Anruf 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 im Wesentlichen die Pipeline voll.