1. TCP连接
HTTP连接实际上就是TCP连接和一些使用连接的规则。
TCP连接是因特网上的可靠连接。TCP为HTTP提供了一条可靠的比特传输管道。从TCP连接一端填入的字节会在另一端以原有的顺序、正确地传送出来。
TCP流是分段的、由IP分组传送的。HTTPS在HTTP层与TCP层之间增加了一个称为TSL或SSL的密码加密层。
HTTP要传送一条报文时,会以流的形式将报文数据的内容通过一条打开的TCP连接按序传输。TCP收到数据流之后,会将数据流砍成被称作段的小数据块,并将段封装在IP分组中,通过因特网进行传输。
每个TCP段都是由IP分组承载,从一个IP地址发送到另一个IP地址的。每个IP分组中都包括:
- 一个IP分组首部(通常为20字节)
- 一个TCP分组首部(通常为20字节)
- 一个TCP数据块(0个或多个字节)
IP首部包含了源和目的IP地址、长度和其它一些标记。TCP段的首部包含了TCP端口号、TCP控制标记,以及用于数据排序和完整性检查的一些数值。
在任意时刻,每一个计算机都可以有几条TCP连接处于打开状态。TCP连接是通过4个值来识别的:
<源IP地址、源端口号、目的IP地址、目的端口号>
这4个值一起唯一地定义了一条连接。
2. 影响HTTP性能的因素
- DNS解析时延
- TCP握手时延
- HTTP报文请求、处理与响应时延
- 延迟确认
由于因特网自身无法确保可靠的分组传输(因特网路由器超负荷的话,可以随意丢弃分组),所以TCP实现了自己的确认机制来确保数据的成功传输。
每个TCP段都有一个序列号和数据完整性校验和。每个段的接受者接收到完好的段时,都会向发送者回送小的确认分组。如果发送者没有在指定的窗口时间内收到确认信息,发送者就认为分组已被破坏或损毁,并重发数据。
由于确认分组很小,为了有效地利用网络,TCP允许在发往相同方向的输出数据分组中对其进行“捎带”。为了增加确认报文找到同向传输数据分组的可能性,很多TCP栈都实现了一种“延迟确认”算法。“延迟确认”算法会在一个特定的窗口时间内(通常是100~200毫秒)将输出确认存放到缓冲区中,以寻找能够捎带它的输出数据分组。如果在那段时间内没有输出数据分组,就将输出确认放在单独的分组中传送。 - 慢启动拥塞控制
- Nagle算法
Nagle鼓励发送全尺寸的段。只有当所有其他分组都被确认之后,Nagle算法才允许发送非全尺寸的分组。
Nagle算法会引起几种HTTP性能问题。首先,小的HTTP报文可能无法填满一个分组,可能会因为等待那些永远都不会到来的额外数据而产生时延。其次,Nagle算法与延迟确认之间的交互存在交互问题——Nagle算法会阻止数据的发送,直到有确认分组到达为止,但确认分组自身会被“延迟确认”算法延迟100~200毫秒。 - TIME_WAIT时延和端口耗尽
3. HTTP性能的提升方式
- 并行连接
并行连接的速度可能会更快,但不一定总是更快。而且,打开大量连接会消耗很多内存资源,从而引发自身的性能问题。实际上,浏览器确实使用了并行连接,但它们会将并行连接的数量限制在一个较小的值(通常是4个)。服务器可以随意关闭来自特定客户端的超量连接。
并行连接的缺点: - 每个事务都会打开/关闭一条新的连接,会耗费时间和带宽
- 由于TCP慢启动特性的存在,每条连接的性能都会有所降低
- 可打开的并行连接的数量实际上是有限的
- 持久连接
在事务处理结束之后仍然保持在打开状态的TCP连接被称为持久连接。重用已对目标服务器打开的空闲持久连接,可以避开缓慢的连接建立阶段。而且,已经打开的连接还可以避免慢启动的拥塞适应阶段,以便更快速地进行数据的传输。
持久连接配合并行连接使用可能是最高效的方式。现在,很对Web应用程序都会打开少量的并行连接,每一条并行连接都是持久连接。
持久连接有两种类型:比较老的HTTP/1.0+“keep-alive”连接,以及现代的HTTP/1.1“persistent”连接。 - HTTP/1.0+ “keep-alive”连接
实现HTTP/1.0 keep-alive连接的客户端可以通过包含Connection: Keep-Alive首部请求将一条连接保持在打开状态。
如果服务器愿意为下一条请求将连接保持在打开状态,就在响应中包含相同的首部。如果响应中没有Connection: Keep-Alive首部,客户端就认为服务端不支持keep-alive,会在发回响应报文之后将连接关闭。
Keep-Alive选项
- 参数timeout是在Keep-Alive响应首部发送的。它估计了服务器希望将连接保持在活跃状态的时间。这并不是一个承诺值。
- 参数max是在Keep-Alive响应首部发送的。它估计了服务器还希望为多少个事务保持此连接的活跃状态。这并不是一个承诺值。
- HTTP/1.1 “persistent”连接
与HTTP/1.0+的Keep-Alive连接不同,HTTP/1.1的持久连接在默认情况下是激活的。要在事务处理结束之后将连接关闭,HTTP/1.1应用程序必须向报文中显示地添加一个Connection: close首部。 - 管道化连接
HTTP/1.1允许在持久连接上可选地使用请求管道。在响应到达之前,可以将多条请求放入队列。当第一条请求通过网络流向另一端的服务器时,第二条和第三条请求也可以开始发送了。在高时延网络环境下,这样做可以降低网络的环回时间,提高性能。
使用管道化连接的限制: - 如果HTTP客户端无法确认连接是持久的,就不应该使用管道。
- 必须按照与请求相同的顺序回送HTTP响应。
- HTTP客户端必须做好连接会在任意时刻关闭的准备,还要准备好重发所有未完成的管道化请求。
- HTTP客户端不应该用管道化的方式发送会产生副作用的请求(比如POST)。因为出错的时候,客户端无法了解服务端到底执行了请求序列中的哪些请求,而像POST这种非幂等请求是不能随便地重试的,所以出错时,就存在某些方法永远不会被执行的风险。
4. 正确地关闭连接
- Content-Length及截尾操作
每条HTTP响应都应该有精确的Content-Length首部,用以描述响应主体的尺寸。一些老的HTTP服务器会省略Content-Length首部或者包含错误的长度指示,这样就要依赖服务器发出的连接关闭来说明数据的真实结尾。 - 正常关闭连接
实现正常关闭的应用程序应该首先关闭它们的输出信道,然后等待连接另一端的对等实体关闭它的输出信道。当两端都告诉对方它们不会再发送任何数据之后,连接就会被完全关闭。