TCP性能优化实战

简介: 本次和大家聊一下TCP性能优化。

本次和大家聊一下TCP性能优化。

TCP全称为Transmission Control Protocol,每一个IT人士对TCP都有一定了解。TCP协议属于底层协议,对于大部分研发人员来说,这是透明的,无需关心TCP的实现与细节。

不过如果想做深入的性能优化,TCP是绕不过去的一环。要讲TCP性能优化,必须先回顾一下TCP的一些细节。让我们先来看一下TCP的首部格式

TCP报文段的首部格式


TCP报文段首部的前20个字节是固定的,后面有4n字节是根据需要而增加的选项(n是整数)。因此TCP首部的最小长度是20字节。

  • 序号:字段值指的是本报文段所发送的数据的第一个字节的序号
  • 确认号:是期望收到对方下一个报文段的第一个数据字节的序号。若确认号为= N,则表明:到序号N-1为止的所有数据都已正确收到
  • ACK: 仅当ACK = 1时确认号字段才有效,当ACK = 0时确认号无效。TCP规定,在连接建立后所有的传送的报文段都必须把ACK置为1
  • SYN: 在连接建立时用来同步序号。当SYN=1而ACK=0时,表明这是一个连接请求报文段。对方若同意建立连接,则应在响应的报文段中使SYN=1和ACK=1,因此SYN置为1就表示这是一个连接请求或连接接受报文
  • 窗口:窗口字段明确指出了现在允许对方发送的数据量。窗口值经常在动态变化。
  • 选项:

    • 最大报文段长度MSS:

      • 以太网Ethernet最大的数据帧是1518字节。以太网帧的帧头14字节和帧尾CRC校验4字节(共占18字节),剩下承载上层协议的地方也就是Data域最大就只剩1500字节. 这个值我们就把它称之为MTU。
      • 为了达到最佳的传输效能TCP协议在建立连接的时候通常要协商双方的MSS值,这个值TCP协议在实现的时候往往用MTU值代替,MSS一般在1420~1460,1460是由1500 - 20(IP头)- 20/60(TCP头)计算出的。
    • 窗口扩大选项:TCP首部中窗口字段长度是16位,因此最大的窗口大小为64K字节。可以将窗口最大值增大到2^(16+14)-1=2^30-1

三次握手

原理

所有TCP连接一开始都要经过三次握手,如下图所示:

img

  • SYN: 客户端选择一个随机序列号x,并发送一个SYN分组,其中可能还包括其他TCP标志和选项。
  • SYN ACK: 服务器给x加1,并选择自己的一个随机序列号y,追加自己的标志和选项,然后返回响应。
  • ACK: 客户端给x和y加1并发送握手期间的最后一个ACK分组。

上面的内容我们在书上看过多次,这次我们用wireshark抓包看一下详情:

本机ip为192.168.1.102,服务器ip为122.51.162

sync


sync ack

ack


三次握手完成后,客户端与服务器之间就可以通信了。

这个启动通信的过程适用于所有TCP连接,因此对所有使用TCP的应用具有非常大的性能影响,因为每次传输应用数据之前,都必须经历一次完整的往返。

优化

三次握手带来的延迟使得每创建一个新TCP连接都要付出很大代价。而这也决定了提高TCP应用性能的关键,在于想办法重用连接。

TCP快速打开

TFO(TCP fast open)允许服务器和客户端在连接建立握手阶段交换数据,从而使应用节省了一个RTT的时延。

但是TFO会引起一些问题,因此协议要求TCP实现必须默认禁止TFO。需要在某个服务端口上启用TFO功能的时候需要应用程序显示启用。


设置:sysctl -n net.ipv4.tcp_fastopen = 0x203

限制:并不能解决所有问题,它虽然有助于减少三次握手的往返时间,但却只能在某些情况下有效,如随同SYN分组一起发送的数据净荷有最大尺寸限制、只能发送某些类型的HTTP请求,以及由于依赖加密cookie,只能应用于重复的连接。

效果:经过流量分析和网络模拟,谷歌研究人员发现TFO平均可以降低HTTP事务网络延迟15%、整个页面加载时间10%以上。在某些延迟很长的情况下,降低幅度甚至可达40%。

尽最大可能重用已经建立的TCP连接

长链接(Keep-Alive)

Keep-Alive,HTTP 1.1 之后默认开启,指在一个 TCP 连接中可以持续发送多份数据而不会断开连接

Keep-Alive能够实现,需要服务端支持:

Httpd守护进程,如nginx需要设置keepalive_timeout

  • keepalive_timeout=0:建立tcp连接 + 传送http请求 + 执行时间 + 传送http响应 + 关闭tcp连接 + 2MSL
  • keepalive_timeout>0:建立tcp连接 + (最后一个响应时间 – 第一个请求时间) + 关闭tcp连接 + 2MSL

另外TCP自身也有Keep-Alive,是检测TCP连接状况的保鲜机制

  • net.ipv4.tcpkeepalivetime:表示TCP链接在多少秒之后没有数据报文传输启动探测报文
  • net.ipv4.tcpkeepaliveintvl:前一个探测报文和后一个探测报文之间的时间间隔
  • net.ipv4.tcpkeepaliveprobes:探测的次数
负载均衡

基本原理:客户端(如:ClientA)与负载均衡设备之间进行三次握手并发送 HTTP 请求。负载均衡设备收到请求后,会检测服务器是否存在空闲的长链接,如果不存在,服务器将建立一个新连接。当 HTTP 请求响应完成后,客户端与负载均衡设备协商关闭连接,而负载均衡则保持与服务器之间的这个连接。当有其他客户端(如:ClientB)需要发送 HTTP 请求时,负载均衡设备会直接向服务器之间保持的这个空闲连接发送 HTTP 请求,避免来由于新建 TCP 连接造成的延时和服务器资源耗费。

接收窗口rwnd

流量控制是一种预防发送端过多向接收端发送数据的机制。否则,接收端可能因为忙碌、负载重或缓冲区容量有限而无法处理。为实现流量控制,

TCP连接的每一方都要通告自己的接收窗口(rwnd),其中包含能够保存数据的缓冲区空间大小信息。

img

第一次建立连接时,两端都会使用自身系统的默认设置来发送rwnd。每个ACK分组都会携带相应的最新rwnd值,以便两端动态调整数据流速,使之适应发送端和接收端的容量及处理能力。

最初的TCP规范分配给通告窗口大小的字段是16位的,这相当于设定了发送端和接收端窗口的最大值(2的16次方即65 535字节)。为解决这个问题,RFC 1323提供了“TCP窗口缩放”(TCPWindow Scaling)选项,可以把接收窗口大小由65 535字节提高到1G字节!

缩放TCP窗口是在三次握手期间完成的,其中有一个值表示在将来的ACK中左移16位窗口字段的位数。

优化

客户端与服务器之间最大可以传输数据量取rwnd和cwnd变量中的最小值。

开启窗口缩放

开启窗口缩放,能使接收窗口大小从2^16升级到2^30,可以获得更好的传输性能。

查看:sysctl net.ipv4.tcp_window_scaling


设置:sysctl -w net.ipv4.tcp_window_scaling=1

效果:比起不开启窗口缩放,能够充分利用带宽

这里讲述一下带宽延迟积。BDP(Bandwidth-delay product,带宽延迟积)数据链路的容量与其端到端延迟的乘积。这个结果就是任意时刻处于在途未确认状态的最大数据量。

发送端或接收端无论谁被迫频繁地停止等待之前分组的ACK,都会造成数据缺口,从而必然限制连接的最大吞吐量。

无论实际或通告的带宽是多大,窗口过小都会限制连接的吞吐量。

知道往返时间和两端的实际带宽也可以计算最优窗口大小。这一次我们假设往返时间为100 ms,发送端的可用带宽为10 Mbps,接收端则为100 Mbps+。还假设两端之间没有网络拥塞,我们的目标就是充分利用客户端的10 Mbps带宽:

img

窗口至少需要122.1 KB才能充分利用10 Mbps带宽!如果没“窗口缩放,TCP接收窗口最大只有64 KB,无论网络性能有多好,永远无法充分利用带宽。

慢启动与拥塞避免

接收窗口对性能很重要,但拥塞窗口比接收窗口更重要。

客户端与服务器之间最大可以传输(未经ACK确认的)数据量取rwnd和cwnd变量中的最小值,而一开始的cwnd很小,通过慢启动算法不断增大。

慢启动和拥塞避免的算法有很多,这里使用Tahoe版本的TCP版本进行展示,这个也是带有拥塞控制功能的第一个TCP版本,使用的拥塞避免算法为AIMD(Multiplicative Decrease and Additive Increase,倍减加增)。

image.png

  • SS:Slow Start,慢启动阶段。TCP 刚开始传输的时候,速度是慢慢涨起来的,除非遇到丢包,否则速度会一直指数性增长。
  • CA:Congestion Avoid,拥塞避免阶段。当拥塞窗口大于ssthresh后, CWND增长速度会下降,不再像 SS 那样指数增,而是线性增。
  • 超时:当数据发送方感知到丢包时,会记录此时的 CWND,并计算合理的 ssthresh 值,一般ssthresh会置为超时时CWND的一半,发送端会骤降 CWND 到最初始的状态,当 CWND 重新由小至大增长,直到 sshtresh 时,不再 SS 而是 CA

服务器会有一个默认cwnd初始值。最初,cwnd的值只有1个TCP段。1999年4月,RFC 2581将其增加到了4个TCP段。2013年4月,RFC 6928再次将其提高到10个TCP段。

计算题

问题:cwnd大小达到N所需的时间


下面我们就来看一个例子,假设:

• 客户端和服务器的接收窗口为65535字节(64 KB);

• 初始的拥塞窗口:4段(RFC 2581);

• 往返时间是56 ms(伦敦到纽约);


这个例子说明网络正常情况下,要达到最大传输量,需要224ms。因为慢启动限制了可用的吞吐量,而这对于小文件传输非常不利,因为拥塞控制尚处于slowstart阶段,传输就完毕了。

优化

确保cwnd大小为10

查看

  1. 写脚本

    probe kernel.function("tcp_init_cwnd").return

    {

    printf("tcp_init_cwnd return: %d\n", $return)

    }

  2. 把服务器内核升级到最新版本(Linux:3.2+)

增大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+内核默认的拥塞预防算法。

减少传输数据量

方案

  1. 减少传输冗余数据
  2. 压缩要传输的数据:gzip、protobuf、webp等
  3. 再快也快不过什么也不用发送

减少往返时间

方案

  1. 多机房部署服务器
  2. 使用CDN

队首阻塞

队首(HOL,Head of Line)阻塞:如果中途有一个分组没能到达接收端,那么后续分组必须保存在接收端的TCP缓冲区,等待丢失的分组重发并到达接收端。这一切都发生在TCP层,应用程序对TCP重发和缓冲区中排队的分组一无所知,必须等待分组全部到达才能访问数据。在此之前,应用程序只能在通过套接字读数据时感觉到延迟交付。

img

优点:应用程序不用关心分组重排和重组,从而让代码保持简洁。

缺点:分组到达时间会存在无法预知的延迟变化。这个时间变化通常被称为抖动,也是影响应用程序性能的一个主要因素。

优化

UDP

无法优化,这是TCP的基础逻辑,目前没有优化的可能。

无需按序交付数据或能够处理分组丢失的应用程序,以及对延迟或抖动要求很高的应用程序,最好选择UDP等协议。

一般的音频或者游戏等应用,可以选择使用UDP协议

总结

针对TCP的优化建议

  1. 服务器配置调优

    • 服务器使用最新版本
    • 增大TCP的初始拥塞窗口
    • 慢启动重启
    • 窗口缩放(RFC 1323)
    • TCP快速打开
    • 可用ss命令或sysctl -a | grep tcp查看相关配置
  2. 应用程序行为调优

    • 再快也快不过什么也不用发送,能少发就少发
    • 我们不能让数据传输得更快,但可以让它们传输的距离更短
    • 重用TCP连接是提升性能的关键
  3. 性能检查清单

    • 把服务器内核升级到最新版本(Linux:3.2+);
    • 确保cwnd大小为10;
    • 禁用空闲后的慢启动;
    • 确保启动窗口缩放;
    • 减少传输冗余数据;
    • 压缩要传输的数据;
    • 把服务器放到离用户近的地方以减少往返时间;
    • 尽最大可能重用已经建立的TCP连接。

资料

  1. Web权威性能指南
  2. TCP 滑动窗口 与窗口缩放因子
  3. TCP的滑动窗口与拥塞窗口
  4. Web 性能优化 - TCP
  5. 就是要你懂TCP--性能优化大全)
  6. TCP报文段的首部格式
  7. TCP Socket通信详细过程
  8. TCP三次握手以及SYN,ACK,Seq的不详细解释
  9. Wireshark数据包分析
  10. Wireshark网络分析就这么简单
  11. TCP-fastopen(TFO)
  12. TCP系列40—拥塞控制—3、慢启动和拥塞避免概述
  13. TCP系列41—拥塞控制—4、Linux中的慢启动和拥塞避免(一)
  14. HTTP Keep-Alive是什么?如何工作?(理解TCP生命周期)
  15. nginx - KeepAlive详细解释

最后

大家如果喜欢我的文章,可以关注我的公众号(程序员麻辣烫)

往期文章回顾:

技术

  1. TCP性能优化
  2. 限流实现1
  3. Redis实现分布式锁
  4. Golang源码BUG追查
  5. 事务原子性、一致性、持久性的实现原理
  6. CDN请求过程详解
  7. 记博客服务被压垮的历程
  8. 常用缓存技巧
  9. 如何高效对接第三方支付
  10. Gin框架简洁版
  11. InnoDB锁与事务简析

读书笔记

  1. 如何锻炼自己的记忆力
  2. 简单的逻辑学-读后感
  3. 热风-读后感
  4. 论语-读后感

思考

  1. 对项目管理的一些看法
  2. 对产品经理的一些思考
  3. 关于程序员职业发展的思考
  4. 关于代码review的思考
  5. Markdown编辑器推荐-typora
相关实践学习
容器服务Serverless版ACK Serverless 快速入门:在线魔方应用部署和监控
通过本实验,您将了解到容器服务Serverless版ACK Serverless 的基本产品能力,即可以实现快速部署一个在线魔方应用,并借助阿里云容器服务成熟的产品生态,实现在线应用的企业级监控,提升应用稳定性。
云原生实践公开课
课程大纲 开篇:如何学习并实践云原生技术 基础篇: 5 步上手 Kubernetes 进阶篇:生产环境下的 K8s 实践 相关的阿里云产品:容器服务 ACK 容器服务 Kubernetes 版(简称 ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情: https://www.aliyun.com/product/kubernetes
相关文章
|
4月前
|
网络协议 网络性能优化 API
dpdk课程学习之练习笔记三(tcp的简单实现)
dpdk课程学习之练习笔记三(tcp的简单实现)
50 0
|
5月前
|
监控 网络协议 Java
Java TCP长连接详解:实现稳定、高效的网络通信
Java TCP长连接详解:实现稳定、高效的网络通信
|
1月前
|
缓存 网络协议 算法
详细分析高频的TCP知识点总结
详细分析高频的TCP知识点总结
28 0
|
3月前
|
存储 负载均衡 网络协议
高性能网络编程 - 关于单台服务器并发TCP连接数理论值的讨论
高性能网络编程 - 关于单台服务器并发TCP连接数理论值的讨论
54 0
|
4月前
|
网络协议 算法 Java
今天讲讲TCP(详解)
今天讲讲TCP(详解)
40 0
|
存储 网络协议 数据格式
TCP网络编程模型从入门到实战基础篇,单服务器单个用户非并发版本
TCP网络编程模型从入门到实战基础篇,单服务器单个用户非并发版本
TCP网络编程模型从入门到实战基础篇,单服务器单个用户非并发版本
|
网络协议 安全 测试技术
TCP 编程快速入门案例分析 | 学习笔记
快速学习 TCP 编程快速入门案例分析
130 0
TCP 编程快速入门案例分析 | 学习笔记
|
域名解析 缓存 网络协议
前端有必要了解的一些 TCP 基础
前端有必要了解的一些 TCP 基础
|
网络协议 算法 安全
深入浅出 BPF TCP 拥塞算法实现原理
深入浅出 BPF TCP 拥塞算法实现原理
435 0