面试官:Redis在建立连接时,为什么要禁用 Nagle算法?

共 2117字,需浏览 5分钟

 ·

2022-06-26 16:26

你知道的越多,不知道的就越多,业余的像一棵小草!

你来,我们一起精进!你不来,我和你的竞争对手一起精进!

编辑:业余草

推荐: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算法:

  1. --使用writev,而不是两次调用write,单个writev调用会使tcp输出一次而不是两次,只产生一个tcp分节,这是首选方法;
  2. --把两次写操作的数据复制到单个缓冲区,然后对缓冲区调用一次write;
  3. --关闭Nagle算法,调用write两次;有损于网络,通常不考虑

禁止Nagle和开启Nagle算法发送数据与确认示意图:

Redis 客户端连接

Redis 通过监听一个 TCP 端口或者 Unix socket 的方式来接收来自客户端的连接,当一个连接建立后,Redis 内部会进行以下一些操作:

  • 首先,客户端 socket 会被设置为非阻塞模式,因为 Redis 在网络事件处理上采用的是非阻塞多路复用模型。
  • 然后为这个 socket 设置 TCP_NODELAY 属性,禁用 Nagle 算法
  • 然后创建一个可读的文件事件用于监听这个客户端 socket 的数据发送

Redis 的性能很强,如果不禁用 Nagle 算法,数据就可能会存在较高的延迟。

浏览 22
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报