罪魁祸首之一就是“TCP慢启动”,对,这是个功能,不是缺陷。要理解其中的原因,我们必须到TCP栈里面一探究竟,这样我们也能学到一些建立更快速网络服务的有趣的小技巧。
TCP慢启动
TCP协议提供了许多内置的功能,其中我们感兴趣的两个是拥塞控制和拥塞避免。TCP慢启动是TCP层内部实现拥塞控制的一种机制。
慢启动和其它一些算法联合使用,避免发送过多的数据以至网络无力传输,也就是防止网络拥塞。
- wikipedia
表面上的流程很简单:客户端发送一个SYN包,它告知本端的最大缓存大小(rwnd - 接收窗口),发送端回传几个包作为应答(cwnd-拥塞窗口),之后,每次它收到来自客户端的ACK包,它就把发送包(传输中并未收到确认的)的数量加倍。
这个过程也被称作TCP连接的“指数增长”阶段。OSI学院有一个很好的动画演示这个过程:(滚动到底点击播放)。那么,这和我们有什么关系呢?不管你的带宽有多大,每一个TCP连接都要经历这个过程,这也就是说,通常情况下,实际用到的带宽受制于发送端和接收端的缓存大小的设置。
HTTP和TCP慢启动
或者,稍微换个说法,一个10M的带宽平均上只用了16%的容量。老天!这说明,如果我们要提高网速,我们应该着眼于降低客户端和服务器之间的往返时延,而不一定要一味地花钱提高带宽。当然,当你在缓冲一个大文件的时候高带宽很有用,或者跑个速度测试也能唬唬人。问题是,基于HTTP的交互倾向于应用短暂、突发的连接 - 在这样的情况下,我们常常无法占用信道的全部容量!Google做一个研究表明,当带宽从5M升高到10M,页面加载时间只提高了让人失望的5%。
CWND的故事
如果TCP慢启动很慢的话,我们不能让它快一点么?其实,直到最近,Linux TCP栈本身把拥塞窗口(cwnd)的初始值硬编码为3到4个包,这也就是4kb的大小(一个包大约1360字节)。还有个频繁发生的问题,HTTP有每获取一个资源要建立一个连接的毛病。这些加在一块,你的性能就受到了严重限制。
在内核2.6.33版本里,经过了长期讨论和提交很多的IETF修改建议之后,cwnd的初始值被设为10个包。这本身就是一个很大的进步。但是有个问题,猜猜现在大部分服务器跑的是什么版本的内核?没错,也许现在是时候更新你的服务器了。给你个实用的小提示,如果你考虑你的网络服务开始使用SPDY,那么如果不在最近的几个内核上面跑,你实际上不会得到任何的性能提升!TCP栈的一个小小的改动在全局能产生巨大的变化。
那么我该怎么办?
TCP慢启动是一个功能,不是缺陷,而且它的确包含有趣而重大的意义。作为开发者,我们经常会忽视客户端到服务器的往返时延,但是如果如果我们真有志于建立更快的网站,现在该去研究一下这些选择了:在和网络服务通信的时候重用TCP连接,建立支持HTTP keep-alive和流水线的网络应用,重视端到端的时延。噢,还有,别在高于10兆的宽带上浪费钱了,可能你根本就不需要。