通过TCP通道发送数据时,我遇到了延迟,我无法理解。 链路是1Gb链路,端到端延迟大约为40ms。 在我目前的设置中,等待时间(从发送者用户空间到接收者用户空间的一条消息的时间)可以达到100ms。
发件人套接字使用TCP_NODELAY选项进行configuration。 发送缓冲区(SO_SNDBUF)被configuration为8MB。 接收缓冲区(SO_RCVBUF)也被configuration为8MB。 TCP窗口缩放被激活。
update-1 :我使用zeromq 3.1.1中间件来传输数据。 套接字configuration,包括TCP_NODELAY标志由中间件执行。 有些选项可以像rx和tx一样发送缓冲区大小而不是TCP_NODELAY。 据我所知,TCP_NODELAY被激活,以确保数据被发送尽可能。 同时,实际的套接字发送和发送消息的决定是在两个独立的线程中执行的。 如果批量中的第一条消息发送时有多条消息可用,则进行正确的批处理。
我用tcpdump从下面的帧中提取了一个捕获。 初始TCP握手后,发送方(172.17.152.124)开始发送数据。 初始窗口大小为接收方为5840字节,发送方为5792字节。
我的问题是,发送者发送两个帧(#6和#7),然后停下来,等待一个确认从接收器回来。 据我所知,接收器的窗口大小没有达到,传输不应停止(384字节未完成,初始接收窗口大小为5840字节)。 我开始认为我没有正确理解TCP是什么。 有人可以帮助澄清?
更新-2 :我的数据有效载荷由一个幻数和一个时间戳组成。 我通过比较有效负载的时间戳和tcpdump的时间戳,隔离了延迟的数据包。 帧#9的有效载荷ts非常接近帧#6和#7,并且明显小于帧#8中接收到的应答的时间戳。
update-1 :帧#9不立即发送的事实可以通过TCP通道的慢启动来解释。 事实上,一旦连接运行了几分钟,问题也会出现,所以慢启动似乎不是一般的解释。
20:53:26.017415 IP 172.17.60.9.39943> 172.17.152.124.56001:标志[S],seq 2473022771,win 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:Flags [S.],seq 2948065596,ack 2473022772,win 5792,options [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.],seq 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:Flags [。],ack 15,win 12,options [nop,nop,TS val 186598927 ecr 4219180895],length 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:Flags [P.],seq 257:385,ack 15,win 12,options [nop,nop,TS val 186598963 ecr 4219180895],length 128
20:53:26.202465 IP 172.17.60.9.39943> 172.17.152.124.56001:标记[。],ack 257,win 27,options [nop,nop,TS val 4219181005 ecr 186598963],长度为0
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],length 1448
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],长度472
如果这一点很重要的话,两端都是Linux RHEL5盒,2.6.18内核和网卡都使用e1000e驱动。
update-3 /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
我多次运行这个工具,发现拥塞窗口大小被定期重置为初始值(10ms,在我的Linux机器上)。 连接不断循环回到慢启动阶段。 在慢启动期间,具有超过窗口大小的消息的突发被延迟,等待与突发的第一个分组相关的消息。
stream量由一系列突发组成的事实可能解释了拥塞窗口大小的重置。
通过在闲置期间停用慢启动模式,我能够摆脱延迟。
[user @ host〜] $ cat / proc / sys / net / ipv4 / tcp_slow_start_after_idle 0
这不会是一个微妙的东西就像一个设置的地方。 在TCP或代码错误的情况下,这将会是一个问题。 对于TCP而言,除了非常高的延迟或由噪声引起的丢包等exception情况之外,TCP没有任何魔术“更快”的转换。
最明显的解释是如果代码调用write或send非常小的块。 您需要每次发送至less2KB,最好是16KB。 你说你批量的消息,但不清楚这是什么意思。 你在一次电话中传递给他们write还是send ? 您是否将它们绑定到单个协议数据单元中,以便在TCP上分层协议? 做这两件事情有很大的延迟。
另外,摆脱TCP_NODELAY。 它可以减less吞吐量。 只适用于没有devise用于TCP的应用程序或不能预测接下来需要传输哪一方的应用程序。
当然,除非你实际上在TCP之上分层了一个协议,你不知道接下来哪个端口要传输(比如telnet )。 那么设置TCP_NODELAY是有意义的。 需要大量的专业知识才能使这种协议以低延迟工作。 如果这是您的情况,请在TCP之上发布有关您正在分层的协议的更多细节,其协议数据单元大小是什么样的,以及什么时候确定哪一边传输。
如果你实际上批量处理一次可用的消息,并在一次调用中传递它们来write或send ,那么很可能问题在于另一方没有为每批发送应用层确认。 这些通过提供TCP ACK数据包来提高延迟。 你的协议应该包括他们,以确保双方交替,这有助于延迟下降。