Erläuterung der Größe und der Verzögerungen des Linux-TCP-Fensters

7

Ich habe Verzögerungen beim Senden von Daten über einen TCP-Kanal, den ich nicht verstehen kann. Die Verbindung ist eine 1-GB-Verbindung mit einer End-to-End-Latenz von ungefähr 40 ms. In meinem aktuellen Setup kann die Latenz (die Zeit von einer Nachricht vom Senderbenutzerbereich zum Empfängerbenutzerbereich) 100 ms erreichen.

Der Absendersocket wird mit der Option TCP_NODELAY konfiguriert. Der Senderpuffer (SO_SNDBUF) ist auf 8 MB konfiguriert. Der Empfangspuffer (SO_RCVBUF) ist ebenfalls auf 8 MB konfiguriert. Die TCP-Fensterskalierung ist aktiviert.

Update-1 : Ich verwende die Zeromq 3.1.1 Middleware, um Daten zu übertragen. Die Socket-Konfiguration einschließlich des TCP_NODELAY-Flags wird von der Middleware durchgeführt. Auf einige Optionen kann zugegriffen werden, z. B. rx und tx senden Puffergrößen aus, nicht jedoch TCP_NODELAY. Soweit ich verstanden habe, wird TCP_NODELAY aktiviert, um sicherzustellen, dass die Daten so gut wie möglich gesendet werden. In der Zwischenzeit werden die tatsächlichen Socket-Sendungen und die Entscheidung zum Senden einer Nachricht in zwei separaten Threads ausgeführt. Eine ordnungsgemäße Stapelverarbeitung erfolgt, wenn zum Zeitpunkt des Versendens der ersten Nachricht im Stapel mehrere Nachrichten verfügbar sind.

Ich habe mit tcpdump ein Capture ausgeführt, aus dem die folgenden Frames extrahiert wurden. Nach dem ersten TCP-Handshake beginnt der Absender (172.17.152.124) mit dem Senden von Daten. Die anfängliche Fenstergröße beträgt 5840 Byte für den Empfänger und 5792 Byte für den Absender.

Mein Problem ist, dass der Absender zwei Frames sendet (Nr. 6 und Nr. 7) und dann anhält und darauf wartet, dass eine Bestätigung vom Empfänger zurückkommt. Soweit ich sehen kann, wird die Fenstergröße des Empfängers nicht erreicht und die Übertragung sollte nicht gestoppt werden (384 Bytes ausstehend bei einer anfänglichen Empfangsfenstergröße von 5840 Bytes). Ich fange an zu denken, dass ich nicht richtig verstanden habe, was TCP ist. Kann jemand bei der Klärung helfen?

Update-2 : Meine Datennutzlast besteht aus einer magischen Zahl, gefolgt von einem Zeitstempel. Ich habe die verzögerten Pakete isoliert, indem ich die Zeitstempel der Nutzdaten mit den von tcpdump gesetzten Zeitstempeln verglichen habe. Die Nutzlast ts von Frame # 9 liegt sehr nahe an der von Frame # 6 und # 7 und deutlich unter dem Zeitstempel der empfangenen Bestätigung in Frame # 8.

Update-1 : Die Tatsache, dass Frame # 9 nicht sofort gesendet wird, kann durch den langsamen Start des TCP-Kanals erklärt werden. Tatsächlich tritt das Problem auch auf, wenn die Verbindung einige Minuten lang ausgeführt wird, sodass der langsame Start nicht die allgemeine Erklärung zu sein scheint.

  1. 20: 53: 26.017415 IP 172.17.60.9.39943> 172.17.152.124.56001: Flags [S], seq 2473022771, win 5840, options [mss 1460, sackOK, TS val 4219180820 ecr 0, nop, wscale 8], Länge 0

  2. 20: 53: 26.017423 IP 172.17.152.124.56001> 172.17.60.9.39943: Flags [S.], seq 2948065596, ack 2473022772, win 5792, options [mss 1460, sackOK, TS val 186598852 ecr 219180820, nop, wscale 9 ], Länge 0

  3. 20: 53: 26.091940 IP 172.17.60.9.39943> 172.17.152.124.56001: Flags [.], Ack 1, Win 23, Optionen [nop, nop, TS val 4219180894 ecr 186598852], Länge 0

  4. 20: 53: 26.091958 IP 172.17.60.9.39943> 172.17.152.124.56001: Flags [P.], Seq. 1:15, ack 1, w in 23, Optionen [nop, nop, TS val 4219180895 ecr 186598852], Länge 14

  5. 20: 53: 26.091964 IP 172.17.152.124.56001> 172.17.60.9.39943: Flags [.], Ack 15, Win 12, Optionen [nop, nop, TS val 186598927 ecr 4219180895], Länge 0

  6. 20: 53: 26.128298 IP 172.17.152.124.56001> 172.17.60.9.39943: Flags [P.], Sequenz 1: 257, ack 15, win 12, Optionen [nop, nop, TS val 186598963 ecr 4219180895], Länge 256

  7. 20: 53: 26.128519 IP 172.17.152.124.56001> 172.17.60.9.39943: Flags [P.], Sequenz 257: 385, ack 15, win 12, Optionen [nop, nop, TS val 186598963 ecr 4219180895], Länge 128

  8. 20: 53: 26.202465 IP 172.17.60.9.39943> 172.17.152.124.56001: Flags [.], Ack 257, Win 27, Optionen [nop, nop, TS val 4219181005 ecr 186598963], Länge 0

  9. 20: 53: 26.202475 IP 172.17.152.124.56001> 172.17.60.9.39943: Flags [.], Seq 385: 1833, ack 15, win 12, options [nop, nop, TS val 186599037 ecr 4219181005], Länge 1448

  10. 20: 53: 26.202480 IP 172.17.152.124.56001> 172.17.60.9.39943: Flags [P.], seq 1833: 2305, ack 15, win 12, options [nop, nop, TS val 186599037 ecr 4219181005], Länge 472

In diesem Fall handelt es sich bei beiden Enden um Linux-RHEL5-Boxen, wobei 2.6.18-Kernel und Netzwerkkarten e1000e-Treiber verwenden.

update-3 Inhalt von /etc/sysctl.conf

[jlafaye@localhost ~]$ cat /etc/sysctl.conf | grep -v "^#" | grep -v "^$" 
net.ipv4.ip_forward = 0
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.default.accept_source_route = 0
kernel.sysrq = 0
kernel.core_uses_pid = 1
net.ipv4.tcp_syncookies = 1
kernel.msgmnb = 65536
kernel.msgmax = 65536
kernel.shmmax = 68719476736
kernel.shmall = 4294967296
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.core.rmem_default = 1048576
net.core.wmem_default = 1048576
net.ipv4.tcp_rmem = 65536 4194304 16777216
net.ipv4.tcp_wmem = 65536 4194304 16777216 
net.core.netdev_max_backlog = 10000 
net.ipv4.tcp_window_scaling = 1
net.ipv4.tcp_mem = 262144 4194304 16777216
kernel.shmmax = 68719476736
jlafaye
quelle
Woher wissen Sie genau, dass Paket 9 unmittelbar nach dem 7. Juni hätte gesendet werden sollen? Ich stelle fest, dass das PUSH-Flag nicht gesetzt ist.
pjc50
Können wir deine sehen /etc/sysctl.conf?
ewwhite
Ticket bearbeitet mit dem Inhalt von /etc/sysctl.conf
jlafaye

Antworten:

11

Nachdem ich mich ein wenig mehr mit meinem Datenverkehr befasst hatte, konnte ich feststellen, dass meine Daten nichts anderes als eine Folge kleiner Bursts mit kleinen Leerlaufzeiten zwischen ihnen waren.

Mit dem nützlichen Tool sskonnte ich die aktuelle Größe des Überlastungsfensters meiner Verbindung abrufen (siehe den cwndWert in der Ausgabe):

[user @ localhost ~] $ / usr / sbin / ss -i -t -e | grep -A 1 56001

ESTAB 0 0 192.168.1.1:56001
192.168.2.1:45614 uid: 1001 ino: 6873875 sk: 17cd4200ffff8804 ts sackscalable wscale: 8,9 rto: 277 rtt: 74/1 ato: 40 cwnd: 36 send 5.6Mbps rcv_space: 5792

Ich habe das Tool mehrmals ausgeführt und festgestellt, dass die Größe des Überlastungsfensters regelmäßig auf den Anfangswert zurückgesetzt wurde (10 ms auf meiner Linux-Box). Die Verbindung kehrte ständig in die langsame Startphase zurück. Während der langsamen Startperiode wurden Bursts mit einer Anzahl von Nachrichten, die die Fenstergröße überschreiten, verzögert und warteten auf die Bestätigungen, die sich auf die ersten Pakete des Bursts beziehen.

Die Tatsache, dass der Verkehr aus einer Folge von Bursts besteht, erklärt wahrscheinlich das Zurücksetzen der Größe des Überlastungsfensters.

Durch Deaktivieren des langsamen Startmodus nach der Leerlaufzeit konnte ich die Verzögerungen beseitigen.

[user @ host ~] $ cat / proc / sys / net / ipv4 / tcp_slow_start_after_idle 0

jlafaye
quelle
1
Bestätigungen auf Anwendungsebene hätten dieses Problemtool gelöst (indem die ACKs schneller wiederhergestellt wurden), und dies würde keine Optimierung auf Systemebene erfordern. (Wenn Sie die Systeme, auf denen diese Software ausgeführt wird, immer optimieren können, ist dies einfacher. Wenn Sie dies jedoch nicht können, sollten Sie Bestätigungen auf Anwendungsebene hinzufügen.) Übrigens: Gute Detektivarbeit.
David Schwartz
Nun, da Sie die Detektivarbeit erwähnen. Ich habe freiwillig einige Teile der Untersuchung ausgeschlossen, einschließlich derjenigen, die einen Kernel auf kvm ausführen und GDB-Überwachungspunkte zum Wert der Überlastungsfenstergröße hinzufügen.
Jlafaye
@DavidSchwartz: Obwohl Acks auf Anwendungsebene das OP-Problem gelöst hätten, werden sie nicht alle Instanzen dieses Problems lösen. Ich hatte dies mit zwei Simplex-ActiveMQ-Kanälen geschehen, bei denen Kanäle abwechselnd verwendet wurden, wobei ein Kanal immer im Leerlauf war, während der andere verwendet wurde. Zu dem Zeitpunkt, an dem die nächste Nachricht auf einem bestimmten Kanal gesendet wurde, war das Überlastungsfenster aufgrund der Leerlaufzeit bereits geschlossen, und keine Bestätigung auf Anwendungsebene hätte dies geändert. Also Ihr Vorschlag ist gut, aber nicht für alle Situationen (während der Lösung des OP ist in allen Fällen anwendbar).
Tim
3

Dies wird keine subtile Sache wie eine Einstellung irgendwo sein. Dies wird ein Problem mit dem auf TCP geschichteten Protokoll oder ein Codefehler sein. Es gibt keinen magischen "schneller gehen" -Schalter für TCP, außer in ungewöhnlichen Fällen wie Netzwerken mit sehr hoher Latenz oder Paketverlust aufgrund von Rauschen.

Die naheliegendste Erklärung wäre, wenn der Code aufruft writeoder sendmit sehr kleinen Blöcken. Sie müssen mindestens 2 KB pro Sendung akkumulieren, idealerweise 16 KB. Sie sagen, Sie stapeln die Nachrichten, aber es ist nicht klar, was das bedeutet. Übergeben Sie sie in einem Anruf an writeoder send? Bündeln Sie sie in einer einzigen Protokolldateneinheit für das auf TCP geschichtete Protokoll? Beides hilft bei der Latenz sehr.

Entfernen Sie auch TCP_NODELAY. Es kann den Durchsatz reduzieren. Dies gilt nur für Anwendungen, die nicht für die Verwendung mit TCP ausgelegt sind, oder für Anwendungen, die nicht vorhersagen können, welche Seite als Nächstes übertragen muss.

Es sei denn, Sie legen tatsächlich ein Protokoll auf TCP, bei dem Sie nicht wissen, welche Seite als nächstes senden wird (wie telnetzum Beispiel). Dann kann es sinnvoll sein, TCP_NODELAY zu setzen. Um diese Art von Protokoll mit geringer Latenz arbeiten zu können, ist erhebliches Fachwissen erforderlich. Wenn dies Ihre Situation ist, veröffentlichen Sie weitere Details zu dem Protokoll, das Sie über TCP legen, wie die Größe der Protokolldateneinheiten aussieht und was bestimmt, welche Seite wann überträgt.

Wenn Sie die verfügbaren Nachrichten tatsächlich stapelweise gleichzeitig stapeln und in einem einzigen Anruf an writeoder sendweiterleiten, besteht das Problem höchstwahrscheinlich darin, dass die andere Seite nicht für jeden Stapel eine Bestätigung auf Anwendungsebene sendet. Diese verbessern die Latenz, indem TCP-ACK-Pakete huckepack gegeben werden. Ihr Protokoll sollte sie enthalten, um sicherzustellen, dass sich die Seiten abwechseln, wodurch die Latenz gering gehalten wird.

David Schwartz
quelle
Ich bin anderer Meinung: An kleinen Schreibvorgängen ist nichts auszusetzen, wenn Sie möchten, dass sie sofort gesendet werden. Er versucht, ein System mit sehr geringer Latenz zu entwickeln.
pjc50
Das gibt es mit Sicherheit. Wenn Sie einen kleinen Schreibvorgang ausführen, wenn andere Daten sofort gesendet werden sollen, verzögern Sie sinnlos die Daten, die Sie nicht in den kleinen Schreibvorgang aufgenommen haben. (Wie das Problem des OP zeigt.)
David Schwartz
Er sagte: "Eine ordnungsgemäße Stapelverarbeitung wird durchgeführt, wenn zum Zeitpunkt des Versendens der ersten Nachricht im Stapel mehrere Nachrichten verfügbar sind." Da wir nichts über die Herkunft der Daten wissen, können wir nicht sagen, ob neue Daten möglicherweise sofort gesendet werden müssen, nachdem Sie gerade ein Paket gesendet haben.
pjc50
Es ist nicht klar, dass die Nachrichten zu einem einzigen Anruf an writeoder zusammengefasst werden send. Wenn dies jedoch der Fall ist, liegt das Problem wahrscheinlich im Entwurf des auf TCP geschichteten Protokolls - höchstwahrscheinlich im Fehlen von Bestätigungen auf Anwendungsebene. Diese geben ACKs-Pakete zum Huckepack und sind für Anwendungen mit geringer Latenz unerlässlich. (Antwort aktualisiert.)
David Schwartz