我在通过 TCP 通道发送数据时遇到了延迟,我无法理解。该链路是 1Gb 链路,端到端延迟大约为 40ms。在我当前的设置中,延迟(一条消息从发送方用户空间到接收方用户空间的时间)可以达到 100ms。
发送方套接字配置了 TCP_NODELAY 选项。发送方缓冲区 (SO_SNDBUF) 配置为 8MB。接收缓冲区 (SO_RCVBUF) 也配置为 8MB。TCP 窗口缩放已激活。
更新-1:我使用 zeromq 3.1.1 中间件来传输数据。套接字配置(包括 TCP_NODELAY 标志)由中间件执行。一些选项可以访问,例如 rx 和 tx 发射缓冲区大小,但不能访问 TCP_NODELAY。据我所知,TCP_NODELAY 被激活以确保尽可能发送数据。同时,实际套接字发送和发送消息的决定在两个单独的线程中执行。如果在要发送批次中的第一条消息时有多条消息可用,则会进行适当的批处理。
我使用 tcpdump 运行了一次捕获,从中提取了下面的帧。在初始 TCP 握手之后,发送方 (172.17.152.124) 开始发送数据。接收方的初始窗口大小为 5840 字节,发送方的初始窗口大小为 5792 字节。
我的问题是发送方发送了两个帧(#6 和 #7),然后停止,等待接收方返回确认。据我所知,接收方的窗口大小未达到,传输不应停止(初始接收窗口大小为 5840 字节,未完成 384 字节)。我开始认为我没有正确理解 TCP 是什么。有人能帮忙澄清一下吗?
更新-2:我的数据有效载荷由一个魔数和一个时间戳组成。我通过将有效载荷的时间戳与 tcpdump 放置的时间戳进行比较,分离出了延迟的数据包。帧 #9 的有效载荷 ts 与帧 #6 和 #7 的有效载荷 ts 非常接近,并且明显小于帧 #8 中收到的 ack 的时间戳。
更新-1:第 9 帧没有立即发送这一事实可以用 TCP 通道的慢启动来解释。事实上,一旦连接运行几分钟,问题也会出现,因此慢启动似乎不是一般的解释。
20:53:26.017415 IP 172.17.60.9.39943 > 172.17.152.124.56001:标志 [S],序列 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:标志 [S.],seq 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.],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:标志[.],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.],seq 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.],seq 1833:2305,ack 15,win 12,选项 [nop,nop,TS val 186599037 ecr 4219181005],长度 472
如果这很重要,两端都是 Linux RHEL5 盒,带有 2.6.18 内核,网卡使用 e1000e 驱动程序。
更新-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
答案1
在对我的流量进行进一步挖掘之后,我发现我的数据不过是一系列小突发,它们之间有短暂的空闲时间。
使用这个有用的工具ss
,我能够检索我的连接的当前拥塞窗口大小(参见cwnd
输出中的值):
[用户@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 发送 5.6Mbps rcv_space:5792
我多次运行该工具,发现拥塞窗口大小定期重置为初始值(在我的 Linux 机器上为 10ms)。连接不断循环回到慢启动阶段。在慢启动期间,超过窗口大小的消息数量突发会被延迟,等待与突发的第一个数据包相关的确认。
流量由一系列突发组成这一事实很可能解释了拥塞窗口大小的重置。
通过在空闲期后停用慢启动模式,我能够摆脱延迟。
[用户@主机 ~]$ cat /proc/sys/net/ipv4/tcp_slow_start_after_idle 0
答案2
这不是某个地方的设置之类的微妙的事情。这将是 TCP 之上的协议的问题或代码错误。除了网络延迟非常高或噪音导致数据包丢失等特殊情况外,TCP 没有神奇的“加快速度”开关。
最明显的解释是,如果代码调用write
或send
时块非常小。每次发送至少需要累积 2KB,理想情况下为 16KB。你说你批量处理消息,但不清楚这意味着什么。你是在一次调用中将它们传递到write
或吗send
?你是否将它们捆绑到 TCP 顶层协议的单个协议数据单元中?同时执行这两件事对延迟有很大帮助。
另外,删除 TCP_NODELAY。它会降低吞吐量。它只适用于那些不是为使用 TCP 而设计的应用程序,或者那些无法预测哪一方需要下一步传输的应用程序。
当然,除非您实际上是在 TCP 之上分层协议,而您不知道哪一端接下来要传输数据(例如telnet
)。那么设置 TCP_NODELAY 是有意义的。要使这种协议以低延迟工作,需要大量的专业知识。如果您的情况如此,请发布有关您在 TCP 之上分层的协议的更多详细信息,其协议数据单元大小如何,以及决定哪一端何时传输数据的因素。
如果您确实一次批量处理可用消息,并通过对write
或 的一次调用传递它们send
,那么问题很可能是另一方没有为每个批次发送应用层确认。这些通过提供可供搭载的 TCP ACK 数据包来改善延迟。您的协议应该包括它们以确保双方交替,这有助于降低延迟。