通过持久TCP连接取得HTML和CSS文件
在我们两个请求的例子中,总共只节约了一次往返时间。但是,更常见的情况是一次TCP连接要发送N 次HTTP请求,这时:
没有持久连接,每次请求都会导致两次往返延迟;
有持久连接,只有第一次请求会导致两次往返延迟,后续请求只会导致一次往返延迟。
在启用持久连接的情况下,N 次请求节省的总延迟时间就是(N -1)×RTT。还记得吗,前面说过,在当代Web应用中,N 的平均值是90,而且还在继续增加。因此,依靠持久连接节约的时间,很快就可以用秒来衡量了!这充分说明持久化HTTP是每个Web应用的关键优化手段。
HTTP管道持久HTTP可以让我们重用已有的连接来完成多次应用请求,但多次请求必须严格满足先进先出(FIFO)的队列顺序:发送请求,等待响应完成,再发送客户端队列中的下一个请求。HTTP管道是一个很小但对上述工作流却非常重要的一次优化。管道可以让我们把
FIFO队列从客户端(请求队列)迁移到服务器(响应队列)。
要理解这样做的好处,我们再看一看通过持久TCP连接取得HTML和CSS文件示意图。首先,服务器处理完第一次请求后,会发生了一次完整的往返:先是响应回传,接着是第二次请求。在此期间服务器空闲。如果服务器能在处理完第一次请求后,立即开始处理第二次请求呢?甚至,如果服务器可以并行或在多线程上或者使用多个工作进程,同时处理两个请求呢?
通过尽早分派请求,不被每次响应阻塞,可以再次消除额外的网络往返。这样,就从非持久连接状态下的每个请求两次往返,变成了整个请求队列只需要两次网络往返!
现在我们暂停一会,回顾一下在性能优化方面的收获。一开始,每个请求要用两个TCP连接,总延迟为284 ms。在使用持久连接后,避免了一次握手往返,总延迟减少为228 ms。最后,通过使用HTTP管道,又减少了两次请求之间的一次往返,总延迟减少为172 ms。这样,从284 ms到172 ms,这40%的性能提升完全拜简单的协议优化所赐。
而且,这40%的性能提升还不是固定不变的。这个数字与我们选择的网络延迟和两个请求的例子有关。希望读者自己能够尝试一些不同的情况,比如延迟更高、请求更多的情况。尝试之后,你会惊讶于性能提升效果比这里还要高得多。事实上,网络延迟越高,请求越多,节省的时间就越多。我觉得大家很有必要自己动手验证一下这个结果。因此,越是大型应用,网络优化的影响越大。
不过,这还不算完。眼光敏锐的读者可能已经发现了,我们可以在服务器上并行处理请求。理论上讲,没有障碍可以阻止服务器同时处理管道中的请求,从而再减少20 ms的延迟。
可惜的是,当我们想要采取这个优化措施时,发现了HTTP 1.x协议的一些局限性。HTTP 1.x只能严格串行地返回响应。特别是,HTTP 1.x不允许一个连接上的多个响应数据交错到达(多路复用),因而一个响应必须完全返回后,下一个响应才会开始传输。为说明这一点,我们可以看看服务器并行处理请求的情况(如下图)。
上图演示了如下几个方面:
HTML和CSS请求同时到达,但先处理的是HTML请求;
服务器并行处理两个请求,其中处理HTML用时40 ms,处理CSS用时20 ms;
CSS请求先处理完成,但被缓冲起来以等候发送HTML响应;
发送完HTML响应后,再发送服务器缓冲中的CSS响应。”
即使客户端同时发送了两个请求,而且CSS资源先准备就绪,服务器也会先发送HTML响应,然后再交付CSS。这种情况通常被称作队首阻塞 ,并经常导致次优化交付:不能充分利用网络连接,造成服务器缓冲开销,最终导致无法预测的客户端延迟。假如第一个请求无限期挂起,或者要花很长时间才能处理完,怎么办呢?在HTTP 1.1中,所有后续的请求都将被阻塞,等待它完成。
实际中,由于不可能实现多路复用,HTTP管道会导致HTTP服务器、代理和客户端出现很多微妙的,不见文档记载的问题:
一个慢响应就会阻塞所有后续请求;
并行处理请求时,服务器必须缓冲管道中的响应,从而占用服务器资源,如果有个响应非常大,则很容易形成服务器的受攻击面;
响应失败可能终止TCP连接,从页强迫客户端重新发送对所有后续资源的请求,导致重复处理;
由于可能存在中间代理,因此检测管道兼容性,确保可靠性很重要;
如果中间代理不支持管道,那它可能会中断连接,也可能会把所有请求串联起来。