大型站点TCP/IP协议优化

  • 2022 年 1 月 12 日
  • 笔记

作为一个DAU上百万或千万的站点,不仅仅需要做好网站应用程序、数据库的优化,还应从TCP/IP协议层去进行相关的优化;

在我的工作中,曾使用到了以下的几种基本的优化方式:

增大最大连接数

在Linux系统里,所有的网络连接都是通过文件描述符(file descriptor)来实现的,因此一个进程所能打开的文件描述符数量决定了这个进程所能创建的最大连接数;

由于Linux系统对进程的文件描述符数量限制是1024;对于大规模的分布式站点来说,这样的连接数限制是远远不够的,建议适当增大该值:

首先在Linux中查询文件描述符数量限制:

ulimit -n

默认情况下会显示1024,然后编辑 /etc/security/limits.conf 文件,加入下面两句:

* soft nofile 10000
* hard nofile 10000

重启系统之后,再使用ulimit -n查看得到的结果就是10000了。

减少TCP断开连接时的TIME_WAIT时间

在TCP断开连接的四次挥手结束阶段,连接断开的发起方会进入到TIME_WAIT状态。

在你的Linux服务器上执行如下命令:

netstat -n | grep 'tcp'

在输出的最后一列你会可能看到值为TIME_WAIT的行,这代表该TCP连接已经进入了TIME_WAIT状态,进入到该状态的连接是不会释放的,默认情况下需要等待2MSL的时间(1MSL大致等于TTL衰减为0的时间,在RFC793中规定为2分钟)。因此在断开连接之后的2个2分钟时间段内,连接会一直保持为TIME_WAIT并持有文件句柄不释放,在大型站点中会导致服务器连接很快背耗尽,实际上对于现在的网速,等待2MSL(4分钟)的时间过长,建议将该时间设置为30秒即可:

  • 编辑 /etc/sysctl.conf 文件,添加下面的行:
    net.ipv4.tcp_fin_timeout = 30
    net.ipv4.tcp_tw_recycle = 1
    net.ipv4.tcp_tw_reuse = 1
  • 执行 sudo /sbin/sysctl -p 命令,使修改立即生效。

禁用延迟确认

 在TCP协议中,延迟确认(Delayed ACK)是一把双刃剑;在绝大多数情况下,延迟确认在服务器收到数据包之后,不需要对每个数据包理解响应ACK,而是将多个数据包的ACK响应合并为一个,从而提高了网络传输的性能并降低了网络的负载,然而在某些条件下,也会带来负面影响;

影响主要有两方面:

  • TCP协议在在RFC 896中引入了Nagle算法,该算法主要用于解决在TCP传输过程中的小包问题(small-packet problem),大概意思是发送方在发送少量的数据时,并非立刻发送,而是在需要发送的数据量累计到一定阈值(MSS:Maximum Segment Size)时才开始发送;维基百科给了一个形象的解释:
    if there is new data to send then
        if the window size ≥ MSS and available data is ≥ MSS then
            send complete MSS segment now
        else
            if there is unconfirmed data still in the pipe then
                enqueue data in the buffer until an acknowledge is received
            else
                send data immediately
            end if
        end if
    end if

    注意上述中的“an acknowledge is received”,也就是说如果一个发送方正在使用Nagle算法来发送小规模数据,这些数据可能迟迟不会被发送出去,直到接收方响应了一个ACK,而接收方因为延迟确认的缘故,只会在延迟确认超时时间达到之后才会发送ACK给发送方;从而导致了发送数据的时间被拉长。

  •  在丢包严重的网络环境中,延迟确认会使得传输的性能变得更为低下;
    例如发送方需要发送序列号为1、2、3、4、5、6、7、8、9的九个包到接收方,如果1、2、3三个包都发送成功了,第4、5、6、7四个包发送失败,接收方在收到第8个包时,返回一个ACK seq=4的包给发送方,发送方重发第4个包,但接收方由于延迟确认的原因不会在收到第4个包时立即发送ACK seq=5的包,而是要等到延迟确认超时之后才响应,接下来发送方会重发第5个包,接收方又要等到延迟确认超时之后再响应ACK seq=6……以此类推,这会使得原本不太好的网络传输雪上加霜。

正如上面所述,延迟确认是一把双刃剑,在大多数环境下具有促进作用,因此关闭与否识情况而定,在某些情况下可以适当减小延迟确认超时时间。

启用SACK

上面谈到延迟确认带来的两方面影响中,在第二个方面里提到了丢包之后发送方需要等到接收方响应ACK之后,才会重发丢失的包;如接收到ACK 4时才重发第4个包,而后接收到ACK 5时重发第5个包,接收到ACK 6时重发第6个包……,如果丢包数量较大时,这个步骤会显得格外的绵长。

SACK(Selective Acknowledgment)解决了这个问题,它使得接收方在收到第8个包之后响应的ACK seq=4这个包里,会把它已经收到的包序列号也写入(这里是8,代表第8个包已经收到);接收方在收到这个ACK包之后,就可以知道第1、2、3、8四个包发送成功,从而推断出需要重发第4、5、6、7这四个包。

SACK是双方面的,需要发送方和接收方同时启用才能生效。

使用QUIC协议

TCP作为一个面向连接的安全可靠的传输协议,它也有很多天然的缺陷,例如上面谈到的失败重发问题,以及其他诸如建立/断开连接需要三次握手四次挥手的问题、发送包时的队头阻塞问题,因此传输的性能并不够好;Google提出了基于UDP协议的上层QUIC协议;它在建立/断开连接时无需三次握手四次挥手,它采用RAID5算法缓解了TCP协议中丢包时的失败重传问题。

现在Google的Chrome浏览器、Microsoft的Edge浏览器都支持Quic协议,同时即将到来的HTTP/3也是建立在QUIC协议之上(RFC 9000)。

很多流行的Web服务器如腾讯的Caddy内置了对QUIC协议的支持,Nginx也推出了官方支持QUIC和HTTP/3的预览版Nginx-quic。