TCPチャネルを介してデータを送信するときに遅延が発生します。理解できないチャネルです。リンクは、エンドツーエンドのレイテンシが約40msの1Gbリンクです。現在のセットアップでは、レイテンシ( 1つのメッセージから送信者ユーザースペースから受信者ユーザースペースに移動する時間)は100ミリ秒に達することがあります。
送信側ソケットは、TCP_NODELAYオプションで構成されます。送信バッファー(SO_SNDBUF)は8MBに構成されています。受信バッファ(SO_RCVBUF)も8MBに設定されています。 Tcpウィンドウスケーリングがアクティブになります。
pdate-1:データを運ぶためにzeromq 3.1.1ミドルウェアを使用しています。 TCP_NODELAYフラグを含むソケット構成は、ミドルウェアによって実行されます。 rxやtxのようないくつかのオプションはアクセス可能ですが、TCP_NODELAYはバッファサイズを発行しません。私が理解している限り、TCP_NODELAYは、データが可能な限り送信されるようにするためにアクティブ化されます。一方、実際のソケット送信とメッセージ送信の決定は、2つの別々のスレッドで実行されます。バッチの最初のメッセージが送信されるときに複数のメッセージが使用可能な場合は、適切なバッチ処理が行われます。
以下のフレームが抽出されたtcpdumpを使用してキャプチャを実行しました。最初のTCPハンドシェイクの後、送信者(172.17.152.124)はデータの送信を開始します。初期ウィンドウサイズは、受信者が5840バイト、送信者が5792バイトです。
私の問題は、送信側が2つのフレーム(#6と#7)を送信してから停止し、ACKが受信側から戻ってくるのを待つことです。私の知る限り、レシーバーのウィンドウサイズに達しておらず、転送が停止しないはずです(初期受信ウィンドウサイズ5840バイトで未解決の384バイト)。 TCPが何であるかを正しく理解していないと思い始めています。誰かが明確にするのを手伝ってくれませんか?
pdate-2:私のデータペイロードは、マジックナンバーとそれに続くタイムスタンプで構成されています。ペイロードのタイムスタンプを、tcpdumpによって入力されたタイムスタンプと比較することにより、遅延パケットを分離しました。フレーム#9のペイロードtsは、フレーム#6および#7のペイロードtsに非常に近く、フレーム#8で受信されたackのタイムスタンプより明らかに小さい。
pdate-1:フレーム#9がすぐに送信されないという事実は、TCPチャネルのスロースタートによって説明できます。実際、問題も表示されます接続が数分間実行されると、スロースタートは一般的な説明ではないようです。
20:53:26.017415 IP 172.17.60.9.39943> 172.17.152.124.56001:フラグ[S]、シーケンス2473022771、勝利5840、オプション[mss 1460、sackOK、TS val 4219180820 ecr 0、nop、wscale 8]、長さ0
20:53:26.017423 IP 172.17.152.124.56001> 172.17.60.9.39943:フラグ[S.]、シーケンス2948065596、ack 2473022772、win 5792、オプション[mss 1460、sackOK、TS val 186598852 ecr 219180820、nop、wscale 9 ]、長さ0
20:53:26.091940 IP 172.17.60.9.39943> 172.17.152.124.56001:フラグ[。]、ack 1、win 23、オプション[nop、nop、TS val 4219180894 ecr 186598852]、長さ0
20:53:26.091958 IP 172.17.60.9.39943> 172.17.152.124.56001:フラグ[P.]、シーケンス1:15、ack 1、w in 23、オプション[nop、nop、TS val 4219180895 ecr 186598852]、長さ14
20:53:26.091964 IP 172.17.152.124.56001> 172.17.60.9.39943:フラグ[。]、ack 15、win 12、オプション[nop、nop、TS val 186598927 ecr 4219180895]、長さ0
20:53:26.128298 IP 172.17.152.124.56001> 172.17.60.9.39943:フラグ[P。]、seq 1:257、ack 15、win 12、オプション[nop、nop、TS val 186598963 ecr 4219180895]、長さ256
20:53:26.128519 IP 172.17.152.124.56001> 172.17.60.9.39943:フラグ[P.]、シーケンス257:385、ack 15、win 12、オプション[nop、nop、TS val 186598963 ecr 4219180895]、長さ128
20:53:26.202465 IP 172.17.60.9.39943> 172.17.152.124.56001:フラグ[。]、ack 257、win 27、オプション[nop、nop、TS val 4219181005 ecr 186598963]、長さ0
20:53:26.202475 IP 172.17.152.124.56001> 172.17.60.9.39943:フラグ[。]、seq 385:1833、ack 15、win 12、オプション[nop、nop、TS val 186599037 ecr 4219181005]、長さ1448
20:53:26.202480 IP 172.17.152.124.56001> 172.17.60.9.39943:フラグ[P.]、シーケンス1833:2305、ack 15、win 12、オプション[nop、nop、TS val 186599037 ecr 4219181005]、長さ472
これが問題になる場合は、両端がLinux RHEL5ボックスで、2.6.18カーネルとネットワークカードがe1000eドライバーを使用しています。
pdate- /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
トラフィックをもう少し掘り下げた後、私のデータは小さなアイドルバーストが連続した小さなバーストのシーケンスにすぎないことがわかりました。
便利なツールss
を使用して、接続の現在の輻輳ウィンドウサイズを取得できました(出力のcwnd
値を参照):
[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
ツールを数回実行したところ、輻輳ウィンドウのサイズが定期的に初期値(私のLinuxボックスでは10ms)にリセットされていることがわかりました。接続は常にスロースタートフェーズにループバックしていました。スロースタート期間中に、ウィンドウサイズを超えるメッセージ数のバーストが遅延し、バーストの最初のパケットに関連する確認応答を待機していました。
トラフィックがバーストのシーケンスで構成されているという事実は、輻輳ウィンドウサイズのリセットを説明している可能性があります。
アイドル期間後にスロースタートモードを無効にすることで、遅延を取り除くことができました。
[user @ Host〜] $ cat/proc/sys/net/ipv4/tcp_slow_start_after_idle 0
これは、どこかでの設定のような微妙なことにはなりません。これは、TCPの上に層をなすプロトコルの問題またはコードのバグです。TCPの場合を除き、魔法の「高速化」スイッチはありません。レイテンシーが非常に高いネットワークや、ノイズによるパケット損失などの異常なケース。
最も明白な説明は、コードが非常に小さなチャンクでwrite
またはsend
を呼び出す場合です。送信ごとに少なくとも2KB、理想的には16KBを蓄積する必要があります。メッセージをバッチ処理すると言いますが、それが何を意味するのかは明確ではありません。 write
またはsend
への1回の呼び出しでそれらを渡しますか?それらをTCPの上に階層化されたプロトコルの単一のプロトコルデータユニットにバンドルしますか?これらの両方を実行すると、レイテンシが大幅に向上します。
また、TCP_NODELAYを削除します。スループットが低下する可能性があります。これは、TCPで動作するように設計されていないアプリケーション、または次に送信する必要がある側を予測できないアプリケーションのみを対象としています。
もちろん、実際にTCPの上にプロトコルをレイヤー化している場合を除いて、次に送信する側がわからない場合(telnet
など)) 。次に、TCP_NODELAYを設定することは理にかなっています。この種のプロトコルを低レイテンシで動作させるには、かなりの専門知識が必要です。それがあなたの状況である場合は、TCPの上に重ねているプロトコル、そのプロトコルデータユニットについて詳細を投稿してくださいサイズはどのように見えるか、そして何がいつ送信するかを決定するもの.
実際に一度に利用可能なメッセージをバッチ処理し、write
またはsend
への単一の呼び出しでそれらを渡す場合、おそらく問題は、反対側がアプリケーションを送信していないことです。各バッチのレイヤー確認。これらはTCP ACKsパケットに便乗することで遅延を改善します。プロトコルはそれらを含めて、サイドが交互になり、遅延を抑えるのに役立ちます。