面试官:Redis在建立连接时,为什么要禁用 Nagle算法?
你知道的越多,不知道的就越多,业余的像一棵小草!
你来,我们一起精进!你不来,我和你的竞争对手一起精进!
编辑:业余草
推荐:https://www.xttblog.com/?p=5347
面试官:Redis 在建立连接时,为什么要禁用 Nagle 算法?
Nagle 算法
我们以 SSH 协议举例,通常在 SSH 连接中,单次击键就会引发数据流的传输。如果使用 IPv4,一次按键会生成约 88 字节大小的 TCP/IPv4 包(使用安全加密和认证):20 字节的 IP 头部,20 字节的 TCP 头部(假设没有选项),数据部分为 48 字节。这些小包(称为微型报(tinygram))会造成相当高的网络传输代价。也就是说,与包的其他部分相比,有效的应用数据所占比例甚微。
上述问题不会对局域网产生很大影响,因为大部分局域网不存在拥塞,而且这些包无需传输很远。然而对于广域网来说则会加重拥塞,严重影响网络性能。John Nagle 提出了一种简单有效的解决方法,现在称其为 Nagle 算法。下面首先介绍该算法是怎样运行的:
Nagle 算法的基本定义是任一时刻,最多只能有一个未被确认的小段。所谓“小段”,指的是长度小于 MSS 尺寸的数据块,而未被确认则是指没有收到对方的 ACK 数据包。Nagle 算法的规则(参考 tcp_output.c 文件里 tcp_nagle_check 函数注释):
如果包长度达到 MSS,则允许发送; 如果该数据包含有 FIN,则允许发送; 设置了 TCP_NODELAY 选项,则允许发送; 未设置 TCP_CORK 选项时,若所有发出去的小数据包(包长度小于 MSS)均被确认,则允许发送; 上述条件都未满足,但发送了超时(一般为 200 ms),则立即发送。
该算法的精妙之处在于它实现了自时钟(self-clocking)控制:ACK 返回得快,数据传输也越快。在相对高延迟的广域网中,更需要减少微型报的数目,该算法使得单位时间内发送的报文段数据更少。也就是说,RTT 控制着发包速率。
Nagle 算法解决的问题
Nagle 算法是为了减少广域网的小分组数目,从而减小网络拥塞的出现的。
该算法要求一个 tcp 连接上最多只能有一个未被确认的未完成的小分组,在该分组 ack 到达之前不能发送其他的小分组,tcp 需要收集这些少量的分组,并在 ack 到来时以一个分组的方式发送出去;其中小分组的定义是小于 MSS 的任何分组。
该算法的优越之处在于它是自适应的,确认到达的越快,数据也就发哦送的越快;而在希望减少微小分组数目的低速广域网上,则会发送更少的分组。
延迟 ACK
如果 tcp 对每个数据包都发送一个ack确认,那么只是一个单独的数据包为了发送一个 ack 代价比较高,所以 tcp 会延迟一段时间,如果这段时间内有数据发送到对端,则捎带发送 ack,如果在延迟 ack 定时器触发时候,发现 ack 尚未发送,则立即单独发送;
延迟 ACK 好处
避免糊涂窗口综合症; 发送数据的时候将ack捎带发送,不必单独发送ack; 如果延迟时间内有多个数据段到达,那么允许协议栈发送一个ack确认多个报文段;
当 Nagle 遇上延迟 ACK
试想如下典型操作,写-写-读,即通过多个写小片数据向对端发送单个逻辑的操作,两次写数据长度小于 MSS,当第一次写数据到达对端后,对端延迟 ack,不发送 ack,而本端因为要发送的数据长度小于 MSS,所以 nagle 算法起作用,数据并不会立即发送,而是等待对端发送的第一次数据确认 ack;这样的情况下,需要等待对端超时发送 ack,然后本段才能发送第二次写的数据,从而造成延迟。
关闭 Nagle 算法
使用 TCP 套接字选项 TCP_NODELAY 可以关闭套接字选项;
如下场景考虑关闭 Nagle 算法:
(1) 对端不向本端发送数据,并且对延时比较敏感的操作;这种操作没法捎带ack;
(2) 如上写-写-读操作;对于此种情况,优先使用其他方式,而不是关闭Nagle算法:
--使用writev,而不是两次调用write,单个writev调用会使tcp输出一次而不是两次,只产生一个tcp分节,这是首选方法; --把两次写操作的数据复制到单个缓冲区,然后对缓冲区调用一次write; --关闭Nagle算法,调用write两次;有损于网络,通常不考虑
禁止Nagle和开启Nagle算法发送数据与确认示意图:
Redis 客户端连接
Redis 通过监听一个 TCP 端口或者 Unix socket 的方式来接收来自客户端的连接,当一个连接建立后,Redis 内部会进行以下一些操作:
首先,客户端 socket 会被设置为非阻塞模式,因为 Redis 在网络事件处理上采用的是非阻塞多路复用模型。 然后为这个 socket 设置 TCP_NODELAY 属性,禁用 Nagle 算法 然后创建一个可读的文件事件用于监听这个客户端 socket 的数据发送
Redis 的性能很强,如果不禁用 Nagle 算法,数据就可能会存在较高的延迟。