TCP优化
什么是TCP?
TCP的全称:Transmission Control Protocol 传输控制协议,是一种面向连接的、可靠的、基于字节流的传输层通信协议。
TCP三次握手
所有的TCP链接一开始都必须经过三次握手。
三次握手过程:
- 客户端生成随机序列号x,并发送一个SYN分组,给到服务器端,询问服务器是否准备好?
- 服务端给客户端返回x+1,生成一个随机序列号y,并发送SYN和ACK分组。告诉浏览器我已准备好,你准备好了么?
- 客户端接收到SYN和ACK分组之后,将x和y再加1,并发送握手期间最后的ACK分组。告诉服务端我准备好了,我们链接吧!
客户端在发送ACK分组之后会立即发送数据。而服务器必须等到ACK分组之后响应数据。
为什么需要三次握手?。,。。
原因:TCP是不可靠的,但是我们要建立可靠的连接来发送可靠的数据,三次是握手是理论上的最小值。
我们假设不是三次握手会发生什么?
我们以A代替客户端,B代替服务端
- 假设只有一次握手,A在给B发送一个包的时候,传过去了,A都不知道B收到了这个包。。。。(如果网络断开连接,A这个时候会周期性的超时重传!!!)
- 如有有两次握手,B收到了A的SYN,B也给A发送了SYN+ACK,但是B此时并不知道A是否接收到了确认消息。。。。。(如果在B发出确认的SYN+ACK的时候网络又断了,导致B发送的确认消息丢了,B也会超时重传!!!)
- 如果是三次握手,A给B发出了ACK,A和B确认两边都是互通的,这样就可以发送数据了。(如果A发送最后一个ACK失败了,依然会超时重传!!!)
- 如果是第四次握手,我们会发现根本用不到,多了就会浪费资源。
为什么要优化TCP呢?
我们的每个TCP连接都需要建立三次握手,如果客户端与服务器距离过长,会造成非常大的性能影响。
所以,优化TCP的关键是想办法重用链接。
方案:长链接(keep-alive),TCP负载均衡、TFO这三个是常用方案。
长链接(keep-alive)
作用:是用于监测两个数据之间的数据链路(Data link)是否正常工作,或防止链路中断的信息。
HTTP/1.1 之后是默认开启的,指在TCP链接中可以持续发送多份数据而不会断开连接。
它的设置需要服务端的支持,如Nginx:
- keepalive_timeout=0:建立TCP连接 + 传送HTTP请求 + 执行时间 + 传送HTTP响应 + 关闭TCP连接 + 2MSL
- keepalive_timeout>0:建立TCP连接 + (最后一个响应时间 - 第一个请求时间)+ 关闭TCP连接 + 2MSL
TCP也有自己的keep-alive,是用来检测TCP连接状况的保鲜机制:
- net.ipv4.keepalivetime:KeepAlive的空闲时长,或者说每次正常发送心跳的周期(表示TCP链接在多少秒之后没有数据报文传输启动探测报文),默认值为7200s(2小时)
- net.ipv4.keepaliveintvl:KeepAlive探测包的发送间隔(前一个探测报文和后一个探测报文的时间间隔)默认值为75s。
- net.ipv4.keepaliveprobes:探测次数。
负载均衡
假设:A、B均为客户端,中间有一个负载均衡服务器C、然后到服务端D。
原理:A与C进行三次握手并发送HTTP请求,C在请求D之前查看是否存在空闲的长连接,如果存在就直接用,不存在就再次建立连接。请求完成之后,A与C协商关闭连接,C与D依然保持连接。当B与C进行三次握手并发送HTTP请求时,C会利用与D之间的空闲连接直接发起HTTP请求。这样就避免了重新建立TCP造成的延迟和服务器资源耗费。
TFO(TCP Fast Open)
尽管开启了长连接,依然会有一部分请求重新发起连接,经过三次握手就会造成延迟(RTT时延)。
TFO的目标就是解决这个延迟问题,在握手的期间也可以进行交换数据。
基本原理:
- 客户端发送一个SYN,尾部加上一个FOC请求,只有四个字节。
- 服务端收到FOC请求,根据来源IP地址生成Cookie(8字节),将这个cookie加载到SYN+ACK包的末尾,发送至客户端。
- 客户端缓存获取到的Cookie给下一次请求使用。
- 下一次请求开始,客户端发送SYN包,这时候后面带上缓存的Cookie,然后就开始正式发送数据。
- 服务器验证Cookie正确,将数据交给上层应用处理得到结果,然后发送SYN+ACK,不再等待客户端的ACK确认,就开始发送响应数据。
我们看到,TFO的核心是一个安全的Cookie,这样可能存在的问题就是客户端会在收到服务端的SYN包之后,没有返回ACK时,就将数据发出了。这个时候客户端至少应该在对应的连接路径上临时禁制TFO功能。
客户端在超时重传SYN包(或服务端超时重传SYN+ACK包)的时候,应该去掉Fast Open选项对应的数据。以免因不兼容而导致连接建立失败。
网络拥塞与慢启动
什么是网络拥塞?
拥塞:即对供不应求,对资源的需求超过了可用的资源,网络性能下降,整个网络的吞吐量随之负荷的增大而减小,甚至会发生拥塞崩溃的现象。
为了减缓网络拥塞现象,TCP 加入许多机制用来控制双向发送数据的速度。如流量监控、拥塞控制、拥塞预防机制等。
慢启动如何解决以及原理?
我们知道,客户端和服务端在建立连接之初,客户端并不知道可用宽带是多少。
因此,需要一个估算机制,然后还可以根据网络中不断变化的条件而动态改变速度。
具体实现如下:
-
发送方通过 TCP 连接初始化并维护一个拥塞窗口变量(cwnd)。并规定发送端与接收端之间最大可以传输的数据量为接收窗口(rwnd)与拥塞窗口(rwnd)的最小值。
-
cwnd 最初的值只有一个 TCP 段,1999年提升至 4 个 TCP 段,2013年,提升至 10 个 TCP 段。
-
发送端向接受端发送 TCP 段后,停下来,等待确认。
-
此后,每收到一个 ACK,慢启动算法都会告诉发送端,cwnd 窗口增加一个 TCP 段,并可以多发送两个新的分组。这个阶段称为指数增长阶段。
设置:在内核中增加一个控制initcwnd的proc参数,/proc/sys/net/ipv4/tcp_initcwnd。该方法对所有的TCP连接有效。
限制:初始拥塞窗口不能设置特别大,否则会导致交换节点的缓冲区被填满,多出来的分组必须删掉,相应的主机会在网络中制造越来越多的数据报副本,使得整个网络陷入瘫痪。行业内各大cdn厂商都调整过init_cwnd值,普遍取值在10-20之间
禁用慢启动重启
名词解释:SSR(Slow-Start Restart,慢启动重启)会在连接空闲一定时间后重置连接的拥塞窗口。
原因:在连接空闲的同时,网络状况也可能发生了变化,为了避免拥塞,理应将拥塞窗口重置回“安全的”默认值。
查看: sysctl net.ipv4.tcp_slow_start_after_idle
设置: sysctl -w net.ipv4.tcp_slow_start_after_idle=0
效果:对于那些会出现突发空闲的长周期TCP连接(比如HTTP的keep-alive连接)有很大的影响,具体提升性能根据网络性能和数据量大小不同而不同
更改拥塞避免算法
拥塞控制算法对TCP性能影响很大,除了上面提到的AIMD算法,还有众多其他算法。
PRR(Proportional Rate Reduction,比例降速)就是RFC 6937规定的一个新算法,其目标就是改进丢包后的恢复速度。
效果:根据谷歌的测量,实现新算法后,因丢包造成的平均连接延迟减少了3%~10%。
设置:升级服务器。PRR现在是Linux 3.2+内核默认的拥塞预防算法。