TCP-Optimierung für höhere Netzwerkleistung


Auf dieser Seite werden Methoden zum Berechnen der richtigen Einstellungen für eine geringere Latenz bei TCP-Verbindungen in Google Cloud und in Hybridszenarien beschrieben. Auf dieser Seite werden auch Möglichkeiten zum Verbessern der Verbindungslatenz zwischen Prozessen in Google Cloud beschrieben.

Eine moderne Architektur mit Mikrodiensten sieht vor, dass Entwickler kleine Dienste mit jeweils nur einer Aufgabe erstellen. Diese Dienste kommunizieren über TCP oder UDP miteinander, je nachdem, welche Zuverlässigkeit für das System erforderlich ist. Für auf Mikrodiensten basierende Systeme sind daher eine zuverlässige Kommunikation und niedrige Latenz äußerst wichtig.

Google Cloud bietet durch das globale Netzwerk neben Zuverlässigkeit auch eine niedrige Latenz, sodass Ihre Anwendungsbenutzer auch global agieren können. Ein globales Netzwerk bedeutet, dass Sie ein VPC-Netzwerk (Virtual Private Cloud) erstellen, das Regionen und Zonen umfasst. Anwendungen können Verbindungen über Regionen und Zonen hinweg aufbauen, ohne das Google Cloud-Netzwerk zu verlassen.

Wurden Anwendungen für eine herkömmliche Rechenzentrumsumgebung geschrieben, können sie langsam sein, wenn sie in eine Hybrid-Cloud-Umgebung verschoben und demnach einige Anwendungskomponenten in einem Unternehmensrechenzentrum und andere in der Cloud ausgeführt werden. Ein langsamer Betrieb kann aus einer Reihe von Faktoren resultieren. Dieser Artikel konzentriert sich auf Umlaufzeitlatenzen und die Auswirkungen der Latenz auf die TCP-Leistung in Anwendungen, die eine beträchtliche Datenmenge über einen Teil des Netzwerks verschieben.

Problem: Latenz und TCP-Verhalten

TCP verwendet einen Windowing-Mechanismus, um zu verhindern, dass ein schneller Sender einen langsamen Empfänger überlastet. Der Empfänger gibt an, wie viele Daten der Sender senden soll, bevor der Sender auf eine Aktualisierung des Fensters vom Empfänger warten muss. Wenn eine empfangende Anwendung keine Daten über die Verbindung empfangen kann, gibt es daher eine Beschränkung, wie viele Daten beim Warten auf die Anwendung in die Warteschlange gestellt werden können.

Das TCP-Fenster ermöglicht eine effiziente Verwendung des Speichers auf dem sendenden und dem empfangenden System. Während die empfangende Anwendung Daten aufnimmt, werden Aktualisierungen des Fensters an den Absender gesendet. Die kürzeste Zeit, in der die Fensteraktualisierung ausgeführt werden kann, nennt man die Umlaufzeit. Daraus ergibt sich folgende Formel für eine der Einschränkungen der Massenübertragungsleistung einer TCP-Verbindung:

Durchsatz <= Fenstergröße / Umlaufzeitlatenz

Im ursprünglichen Design für TCP hat dieses Fenster eine maximale Größe von 65.535 Byte (64 KiB - 1). Dies war die maximale Datenmenge, die der Sender senden konnte, bevor er eine Aktualisierung des Fensters erhalten musste, um weitere Daten senden zu können.

Änderungen in TCP seit Einführung des Protokolls

Seit der Einführung von TCP haben sich einige wichtige Funktionen geändert:

  • Die typischen Netzwerkgeschwindigkeiten wurden um vier Größenordnungen erhöht.
  • Der typische Speicher in einem System hat um vier Größenordnungen zugenommen.

Die erste Änderung hat zur Folge, dass die ursprünglichen TCP-Fenstergrößen zu einer ineffizienten Nutzung von Netzwerkressourcen führten. Ein Sender, der die Daten eines Fensters mit der höchstmöglichen Geschwindigkeit im Rahmen der Netzwerkbedingungen sendete, befand sich anschließend sehr lange im Leerlauf, während er auf die Aktualisierung des TCP-Fensters wartete. Die zweite Änderung bewirkt, dass Sender und Empfänger mehr Arbeitsspeicher für das Netzwerk nutzen können, um der durch die erste Änderung erzeugten Einschränkung entgegenzutreten.

Das folgende Diagramm veranschaulicht diesen Austausch:

Der Sender sendet nur 64 KB Daten und wartet nach dem Senden sehr lange auf eine Aktualisierung des Fensters

Der Sender kann das Netzwerk nicht vollständig nutzen, da er vor dem Senden zusätzlicher Daten auf die Aktualisierung des TCP-Fensters warten muss.

Mehr Daten gleichzeitig senden

Die Lösung besteht darin, mehr Daten gleichzeitig zu senden. Mit zunehmender Bandbreite des Netzwerks passen mehr Daten in die Pipe (Netzwerk) und je länger diese wird, desto länger dauert es, bis der Empfang der Daten bestätigt wird. Diese Beziehung wird als Verzögerungs-Bandbreiten-Produkt (Bandwidth-Delay Product, BDP) bezeichnet. Sie wird durch Multiplikation der Bandbreite mit der Umlaufzeit (RTT) berechnet. Dieser Wert gibt die optimale Anzahl von Bits an, die zum Füllen der Pipe gesendet werden sollten. Die Formel lautet:

BDP (Bits) = Bandbreite (Bits/Sekunde) * RTT (Sekunden)

Das berechnete BDP wird als TCP-Fenstergröße zur Optimierung verwendet.

Beispiel: Sie haben ein 10-Gbit/s-Netzwerk mit einer RTT von 30 Millisekunden. Sie verwenden für die Fenstergröße den Wert der ursprünglichen TCP-Fenstergröße (65.535 Byte). Mit diesem Wert wird die vorhandene Bandbreite nicht annähernd ausgenutzt. Die maximal mögliche TCP-Leistung dieser Verbindung lautet:

(65.535 Byte * 8 Bits/Byte) = Bandbreite * 0,030 Sekunden
Bandbreite = (65.535 Byte * 8 Bits/Byte) / 0,030 Sekunden
Bandbreite = 524.280 Bit / 0,030 Sekunden
Bandbreite = 17.476.000 Bit/Sekunde

Anders ausgedrückt: Diese Werte führen zu einem Durchsatz von etwas mehr als 17 Mbit pro Sekunde, was einen kleinen Bruchteil der 10-Gbit/s-Kapazität des Netzwerks darstellt.

Lösung: TCP-Fenstergröße skalieren

Es wurden Erweiterungen des TCP-Protokolls eingeführt, mit denen die Fenstergröße auf viel höhere Werte skaliert werden kann, um die durch das ursprüngliche Design der TCP-Fenstergröße bedingten Leistungsbeschränkungen aufzuheben. Die Fensterskalierung unterstützt Fenster bis 1.073.725.440 Byte oder fast 1 GiB. Diese Funktion wird in RFC 7323 als TCP-Fensterskalierungsoption beschrieben.

Die Skalierung der Fenstergröße erweitert die Definition des TCP-Fensters auf 30 Bit und verwendet dann einen impliziten Skalierungsfaktor, um diesen 30-Bit-Wert in das 16-Bit-Fensterfeld des TCP-Headers zu übernehmen. Verwenden Sie den folgenden Befehl, um festzustellen, ob die Funktion auf Linux-basierten Systemen aktiviert ist:

sudo sysctl net.ipv4.tcp_window_scaling

Auf allen virtuellen Linux-Maschinen für Google Cloud ist diese Funktion standardmäßig aktiviert. Ein Rückgabewert von 1 bedeutet, dass die Option aktiviert ist. Wenn die Funktion deaktiviert ist, können Sie sie mit diesem Befehl aktivieren:

sudo sysctl -w net.ipv4.tcp_window_scaling=1

Durchsatz bei höherer Fenstergröße

Mit dem vorherigen Beispiel kann der Nutzen der Fensterskalierung belegt werden. Geht man wie zuvor von einem 10-Gbit/s-Netzwerk mit einer Latenz von 30 Millisekunden aus, berechnet sich die neue Fenstergröße mithilfe der folgenden Formel so:

(Verbindungsgeschwindigkeit * Latenz) / 8 Bits = Fenstergröße

Wenn Sie die Zahlen aus dem Beispiel einsetzen, erhalten Sie:

(10 Gbit/s * 30 ms / 1000 s) / 8 Bit/Byte = Fenstergröße
(10.000 Mbit/s * 0,030 Sekunden) / 8 Bits/Byte = 37,5 MB

Durch Vergrößern des TCP-Fensters auf 37 MB kann die theoretische Grenze für die TCP-Massenübertragung auf einen Wert erhöht werden, der sich der Netzwerkkapazität nähert. Natürlich können viele andere Faktoren die Leistung einschränken, darunter die Systemauslastung, die durchschnittlichen Paketgröße und die Anzahl anderer Datenströme, die die Verbindung gemeinsam nutzen. Die neue Fenstergröße verschiebt jedoch deutlich die Grenzen, die die zuvor geringere Fenstergröße setzte.

Linux-Einstellungen zum Ändern der TCP-Fenstergröße festlegen

In Linux wird die TCP-Fenstergröße von den folgenden sysctl(8)-Einstellungen beeinflusst:

net.core.rmem_max
net.core.wmem_max
net.ipv4.tcp_rmem
net.ipv4.tcp_wmem

Die ersten beiden Einstellungen beeinflussen die maximale TCP-Fenstergröße für Anwendungen, die versuchen, die TCP-Fenstergröße direkt zu steuern. Dabei wird die Anfrage der Anwendungen auf höchstens diese Werte begrenzt. Die nächsten beiden Einstellungen beeinflussen die TCP-Fenstergröße für Anwendungen, die von Linux automatisch angepasst werden.

Der optimale Wert für die Fenstergröße hängt von den jeweiligen Umständen ab. Ein Ausgangspunkt ist jedoch das größte BDP-Produkt (Verzögerungs-Bandbreiten-Produkt) für den oder die Pfade, über die das System Daten senden soll. In einem solchen Fall können Sie die Einstellungen über die folgenden Schritte festlegen:

  1. Überprüfen Sie, dass Sie Root-Berechtigungen haben.
  2. Rufen Sie die aktuellen Zwischenspeichereinstellungen ab. Speichern Sie die Einstellungen, falls Sie Ihre Änderungen rückgängig machen möchten.

    sudo sysctl -a | grep mem
    
  3. Legen Sie eine Umgebungsvariable auf die neue TCP-Fenstergröße fest, die Sie verwenden möchten:

    MaxExpectedPathBDP=8388608
    
  4. Legen Sie die maximale Größe des Zwischenspeichers für eingehende Daten des Betriebssystems für alle Verbindungstypen fest:

    sudo sysctl -w net.core.rmem_max=$MaxExpectedPathBDP
    
  5. Legen Sie die maximale Größe des Zwischenspeichers für ausgehende Daten des Betriebssystems für alle Verbindungstypen fest:

    sudo sysctl -w net.core.wmem_max=$MaxExpectedPathBDP
    
  6. Legen Sie die Einstellungen für den Zwischenspeicher eingehender TCP-Daten (tcp_rmem) fest:

    sudo sysctl -w net.ipv4.tcp_rmem="4096 87380 $MaxExpectedPathBDP"
    

    Die Einstellung tcp_rmem hat drei Werte:

    • Die minimale Größe des Zwischenspeichers für eingehende Daten, die einem TCP-Socket zugewiesen werden kann. In diesem Beispiel beträgt der Wert 4096 Byte.
    • Die Standardgröße des Zwischenspeichers für eingehende Daten, die auch den Wert /proc/sys/net/core/rmem_default überschreibt, der von anderen Protokollen verwendet wird. Im Beispiel beträgt der Wert 87380 Byte.
    • Die maximale Größe des Zwischenspeichers für eingehende Daten, die einem TCP-Socket zugewiesen werden kann. Im Beispiel ist diese auf den Wert eingestellt, den Sie zuvor festgelegt haben (8388608 Byte).
  7. Legen Sie die Einstellungen für den TCP-Zwischenspeicher (tcp_wmem) fest:

    sudo sysctl -w net.ipv4.tcp_wmem="4096 16384 $MaxExpectedPathBDP"
    

    Die Einstellung tcp_wmem hat drei Werte:

    • Der für einen einzelnen TCP-Socket verfügbare minimale Zwischenspeicher für ausgehende TCP-Daten
    • Der Standardzwischenspeicher, der für einen einzelnen TCP-Socket zulässig ist
    • Der maximale Zwischenspeicher für ausgehende TCP-Daten
  8. Stellen Sie die Parameter so ein, dass nachfolgende Verbindungen die angegebenen Werte verwenden:

    sudo sysctl -w net.ipv4.route.flush=1
    

Diese Einstellungen bleiben auch nach einem Neustart bestehen, wenn Sie die zuvor festgelegten Befehle der Datei /etc/sysctl.conf hinzufügen:

sudo bash -c 'cat << EOF >> /etc/sysctl.conf
net.core.rmem_max=8388608
net.core.wmem_max=8388608
net.ipv4.tcp_rmem=4096 87380 8388608
net.ipv4.tcp_wmem=4096 16384 8388608
net.ipv4.route.flush=1
EOF'

RTT mit aktualisierter Fenstergröße testen

Wenn TCP für die Verwendung des BDP groß genug ist, ändert sich das Bild wie in der folgenden Abbildung dargestellt:

Der Sender sendet eine große Datenmenge und muss nicht lange auf eine Aktualisierung des Fensters warten.

Die TCP-Fenstergröße kann immer auf der Grundlage der für den jeweiligen Vorgang verfügbaren Ressourcen und des verwendeten TCP-Algorithmus angepasst werden. Wie das Diagramm zeigt, lässt die Fensterskalierung eine Verbindung zu, die weit über die in der ursprünglichen TCP-Spezifikation definierte 65-KiB-Fenstergröße hinausgeht.

Sie können das selbst testen. Legen Sie zuerst die notwendigen Änderungen der TCP-Fenstergröße auf Ihrem lokalen Computer und einem Remote-Computer fest. Führen Sie dann diese Befehle aus:

dd if=/dev/urandom of=sample.txt bs=1M count=1024 iflag=fullblock
scp sample.txt your_username@remotehost.com:/some/remote/directory

Der erste Befehl erstellt eine 1 GB große Datei sample.txt mit zufälligen Daten. Der zweite Befehl kopiert diese Datei von Ihrem lokalen Computer auf einen Remote-Computer.

In der scp-Befehlsausgabe in der Konsole wird die Bandbreite in Kbit/s angegeben. Sie sollten beträchtliche Unterschiede in der Anzeige vor und nach der Änderung der TCP-Fenstergröße feststellen.

Nächste Schritte