网络性能优化
对 TCP 性能的考虑
握手时延
小的 HTTP 事务可能会在 TCP 建立上花费 50%,或更多的时间。后面的小节会讨论 HTTP 是如何通过重用现存连接,来减小这种 TCP 建立时延所造成的影响的。
延迟确认
延迟确认算法会在一个特定的窗口时间(通常是 100 ~ 200 毫秒)内将输出确认存放在缓冲区中,以寻找能够捎带它的输出数据分组。如果在那个时间段内没有输出数据分组,就将确认信息放在单独的分组中传送。
通常,延迟确认算法会引入相当大的时延。根据所使用操作系统的不同,可以调整或禁止延迟确认算法。
和延迟确认一样,Nagle 也没有直接提高性能,启用它的作用只是提高传输效率,减轻网络负担。
累计确认
发送方在一个窗口里发出 n 个包,是不是就能收到 n 个确认包?不一定,确认包一般会少一些。由于 TCP 可以累积起来确认,所以当收到多个包的时候,只需要确认最后一个就可以了。比如客户端用一个包(包号 49)确认了它收到的 10 个包(39 ~ 48 号包)。
超时重传
TCP 超时重传的间隔时间太长,设置一个较小的时间可以减少重传对性能的影响。
发送窗口
发送窗口决定了一口气能发多少字节,而 MSS 决定了这些字节要分多少个包发完。举个例子,在发送窗口为 16000 字节的情况下,如果 MSS 是 1000 字节,那就需要发送 16000/1000=16 个包;而如果 MSS 等于 8000,那要发送的包数就是 16000/8000=2 了。
该网络频繁拥塞,拥塞点大多在 32KB 以上。如果把发送窗口限制在 32KB,就可以避免触碰拥塞点。
TCP Window Scale
在 TCP 刚被发明的时候,全世界的网络带宽都很小,所以最大接收窗口被定义成 65535 字节。随着硬件的革命性进步,65535 字节已经成为性能瓶颈了,怎么样才能扩展呢?TCP 头中只给接收窗口值留了 16 bit,肯定是无法突破 65535(216−1)的。1992 年的 RFC 1323 中提出了一个解决方案,就是在三次握手时,把自己的 Window Scale 信息告知对方。由于 Window Scale 放在 TCP 头之外的 Options 中,所以不需要修改 TCP 头的设计。Window Scale 的作用是向对方声明一个 Shift count,我们把它作为 2 的指数,再乘以 TCP 头中定义的接收窗口,就得到真正的 TCP 接收窗口了。
SACK
TCP SACK(Selective Acknowledgement)是一个 TCP 的选项,来允许 TCP 单独确认非连续的片段,用于告知真正丢失的包,只重传丢失的片段。要使用 SACK,2 个设备必须同时支持 SACK 才可以,建立连接的时候需要使用 SACK Permitted 的 option,如果允许,后续的传输过程中 TCP segment 中的可以携带 SACK option。
只要把“Ack=656925”和“SACK: 661857-663035”这两个因素结合起来,客户端就知道排在后面的数据段 661857-663035 已经送达,但排在前面的 656925-661856(共 4932 字节)反而丢失了,因此它需要重传这段数据。
临界窗口
那临界窗口应该如何取值才合理呢?我能想到的,就是在带宽大的环境中取得大一些,在带宽小的环境中取得小一些。RFC 2001 也是这样建议的,它把临界窗口值定义为发生丢包时拥塞窗口的一半大小。我们可以想象在带宽大的环境中,发生丢包时的拥塞窗口往往也比较大,所以临界窗口值自然会随之加大。
WEB 性能优化
大多数网站性能的瓶颈都是延迟,而不是带宽!
性能检查清单
- 把服务器内核升级到最新版本(Linux:3.2+);
- 确保 cwnd(拥塞窗口) 大小为 10;
- 禁用空闲后的慢启动;
- 确保启动窗口缩放;
- 减少传输冗余数据;
- 压缩要传输的数据;
- 把服务器放到离用户近的地方以减少往返时间;
- 尽最大可能重用已经建立的 TCP 连接。
服务器配置调优
- 增大 TCP 的初始拥塞窗口
加大起始拥塞窗口可以让 TCP 在第一次往返就传输较多数据,而随后的速度提升也会很明显。对于突发性的短暂连接,这也是特别关键的一个优化。
- 慢启动重启
在连接空闲时禁用慢启动可以改善瞬时发送数据的长 TCP 连接的性能。
- 窗口缩放(RFC 1323)
启用窗口缩放可以增大最大接收窗口大小,可以让高延迟的连接达到更好吞吐量。
- TCP 快速打开
在某些条件下,允许在第一个 SYN 分组中发送应用程序数据。TFO(TCP Fast Open,TCP 快速打开)是一种新的优化选项,需要客户端和服务器共同支持。为此,首先要搞清楚你的应用程序是否可以利用这个特性。
应用程序行为调优
- 再快也快不过什么也不用发送,能少发就少发。
- 我们不能让数据传输得更快,但可以让它们传输的距离更短。
- 重用 TCP 连接是提升性能的关键。
移动网络优化
- 轮询在移动网络中代价极高,少用;
- 尽可能使用推送和通知;
- 出站和入站请求应该合并和汇总;
- 非关键性请求应该推迟到无线模块活动时进行。
最佳实践
无论什么网络,也不管所用网络协议是什么版本,所有应用都应该致力于消除或减少不必要的网络延迟,将需要传输的数据压缩至最少。这两条标准是经典的性能优化最佳实践,是其他数十条性能准则的出发点。
- 减少 DNS 查找
每一次主机名解析都需要一次网络往返,从而增加请求的延迟时间,同时还会阻塞后续请求。
- 重用 TCP 连接
尽可能使用持久连接,以消除 TCP 握手和慢启动延迟;参见“慢启动”。
- 减少 HTTP 重定向
HTTP 重定向极费时间,特别是不同域名之间的重定向,更加费时;这里面既有额外的 DNS 查询、TCP 握手,还有其他延迟。最佳的重定向次数为零。
- 使用 CDN(内容分发网络)
把数据放到离用户地理位置更近的地方,可以显著减少每次 TCP 连接的网络延迟,增大吞吐量。这一条既适用于静态内容,也适用于动态内容。
- 去掉不必要的资源
任何请求都不如没有请求快。说到这,所有建议都无需解释。延迟是瓶颈,最快的速度莫过于什么也不传输。然而,HTTP 也提供了很多额外的机制,比如缓存和压缩,还有与其版本对应的一些性能技巧。
- 在客户端缓存资源
应该缓存应用资源,从而避免每次请求都发送相同的内容。
- 传输压缩过的内容
传输前应该压缩应用资源,把要传输的字节减至最少:确保对每种要传输的资源采用最好的压缩手段。
- 消除不必要的请求开销
减少请求的 HTTP 首部数据(比如 HTTP cookie),节省的时间相当于几次往返的延迟时间。
- 并行处理请求和响应
请求和响应的排队都会导致延迟,无论是客户端还是服务器端。这一点经常被忽视,但却会无谓地导致很长延迟。
- 针对协议版本采取优化措施
HTTP 1.x 支持有限的并行机制,要求打包资源、跨域分散资源,等等。相对而言,HTTP 2.0 只要建立一个连接就能实现最优性能,同时无需针对 HTTP 1.x 的那些优化方法。