HTTP权威指南第四章(1)

HTTP连接管理

本文涉及的内容主要有:

  • HTTP是如何使用TCP进行连接的

  • HTTP连接的限制,包括时延/瓶颈存在的障碍

  • 如何优化HTTP连接和显示.

由于个人原因,本文只到HTTP连接优化的并行连接部分.其他部分得要日后再说了.本文不会对TCP/IP进行过多的阐述,最多只能到大概能讲两个协议是如何交互的程度.由于是抄书加上本人自作聪明的一些理解,请各位发现错误的时候,有时间可以说一声.

一:TCP连接

TCP/IP协议是一种常用的分组交换网络分层协议.客户端通过建立与服务器的连接,从而交换报文.报文永远不会丢失,受损或者失序1.

HTTP连接实际上就是建立一条TCP连接管道,而后通过TCP提供的可靠的比特传输管道 进行交互,一端填入的字节会以原有的顺序正确地传送到另一端.而这种传送,是通过分段来实现的.

以传送报文为例,连接过程是这样的:

  1. HTTP应用程序打开一条TCP连接,并以流的形式将报文数据按序传输.

  2. TCP连接将数据分为小数据块,称作”段”

  3. 段被封装到IP分组中通过网络进行传输.

每个TCP段都由IP分组承载,在IP地址之间发送.其分组包括:

  • 一个IP分组首部

  • 一个TCP段首部

  • 一个TCP数据块

为了保持TCP连接的正确运行,TCP用的是端口来实现.每个端口对应一个应用程序,每个IP地址对应一个计算机.要通过四个值:源IP地址/目标IP地址/源端口/目标端口来识别一个TCP连接.所以,每个连接的四个值不能重合(这实际上会导致性能问题,后面会提及)

1.1.TCP套接字

为了操纵TCP连接,操作系统提供了一系列TCP编程接口.下面就是套接字API提供的主要接口了.其隐藏了所有细节.

套接字API调用描述
s = socket(<parameters>)建立一个新的套接字(未命名/未关联)
bind(s,<localIP:port>)为套接字赋予端口和本地IP
connect(s, remote IP:port)建立与远程服务器和端口的连接
listen(s,...)标识一个本地套接字,使其可以合法地接受连接
s2 = accept(s)等待他人建立到本地端口的连接
result = read(s, buffer, n)尝试从套接字向缓冲区读取n个字节
data = write(s, buffer, n)尝试从缓冲区向套接字写入n个字节
close(s) 完全关闭TCP连接
shutdown(s)关闭TCP连接的一端(输入或者输出)
getsocketopt()读取某个内部套接字配置选项的值
setsocketopt()修改某个内部套接字配置选项的值

套接字API隐藏了所有细节:所有底层网络协议的握手细节/TCP数据流/IP分组之间的分段和重装等等.

二:TCP性能的考虑

由于HTTP协议位于TCP协议的上层,HTTP事务的性能大部分取决于TCP通道的性能.下面会列出几个TCP的某些基本性能特点,用于辅助我们理解HTTP的连接优化特性.如果对提升TCP的性能要考虑的细节不感兴趣,可以跳过…

2.1.HTTP事务的时延

HTTP事务在DNS查询/建立连接/传输请求和响应报文这四个部分存在时延.与其相比,真正的HTTP事务处理时间很短.产生时延的主要原因有以下几点:

  • 客户端需要通过URI来确定Web服务器的IP地址和端口号.而如果最近没有访问该站点,则通过DNS解析系统可能需要花数十秒的时间查询IP地址.2

  • 客户端会发送一条TCP连接请求,并等待服务器应答.所以每个新的TCP连接都会存在连接建立时延.虽然值很小,但是如果事务数以百计的话,就很可观了.

  • 建立连接以后,客户端发送请求/服务器处理请求和响应报文等,都需要时间

这些时延的影响因素很多:硬件速度,网络,服务器的负载等等,甚至TCP协议的复杂性也会产生影响.

2.2.性能聚焦区域

常见的TCP相关时延有:

  • TCP连接建立握手

  • TCP慢启动拥塞控制

  • 数据聚集的Nagle算法

  • 用于捎带确认的TCP延迟确认算法

  • TIME_WAIT的时延和端口耗尽

下面这部分的内容针对的是高性能HTTP软件编写,但一我本身水平不够,二这一等级的性能优化并不总是需要.如果愿意,请继续往下读.

2.3.TCP连接建立握手

建立一条新的TCP连接时,TCP软件会交换一系列IP分组,沟通与连接有关的参数.如果连接只用于传输少量数据,则会严重降低HTTP的性能.其建立握手的步骤是:

  1. 向服务器发送小的TCP分组,其中设置一个特殊的SNY标记,说明这是连接请求.

  2. 服务器接受连接,计算连接参数,并返回一个SNY和ACK标记都被置位的TCP分组,说明接受连接请求.

  3. 客户端发送一条确认信息,通知其连接建立成功.该部分允许客户端发送数据.

这些分组由TCP/IP软件管理,只是会导致创建TCP连接时,存在时延.由于一个事务不会交换太多数据,而多次握手会.所以小的HTTP事务可能在创建TCP连接的时候花费大量时间.为此,后面会讨论如何重用现存连接来减少该类型的时延.

2.4.延迟确认

TCP实现了自己的确认机制保证数据成功传输.这个机制的工作过程是这样的:

  1. 赋予每个TCP段一个序列号和完整性校验和,发送出去.

  2. 服务器收到以后,发送一个小的确认分组

  3. 客户端如何没有在指定时间内收到确认分组,则认为分组已经损坏,并重新发送数据.

由于确认报文很小,可以将返回的确认信息与数据分组结合.而延迟确认就是:HTTP服务器会在一个特定时间内将输出确认存储到缓冲区中,寻找可以捎带确认的输出数据分组.如;如果没有,则单独发送.

这与HTTP具有双峰特征的请求–应答行为结合,导致捎带确认的可能性降低.这种回传分组不总是那么多,所以该算法会引入相当大的时延.根据平台的差异,可以调整或者禁用该算法.

P.S.对TCP配置进行的任意修改,都要绝对保证应用程序不会引发这些算法所要避免的问题.

2.5.TCP慢启动

TCP数据传输的性能还取决于连接的使用期(age ).TCP连接会随时间进行自我”调谐”,性能会从最初的限制到提高传输速度.这被称为TCP慢启动(slow start ),用于防止过载和拥塞.

但这种行为限制了一个TCP端点在任意时刻可以传输的分组数量.发送的分组数量限制是随着时间和一次次成功接受分组而减少的(就是说,分组数量越来越多).这叫做打开拥塞窗口.

由于这种特性的存在,新连接的传输速度总是比较慢的,于是引入了重用先存连接的工具,这就是稍后介绍的持久连接.

2.6.Nagle算法

TCP有一个数据流端口,可以放入任意大小的数据.但是如果发送大量包含少量数据的分组,则会导致性能严重下降.3

而Negle算法则试图在发送一个分组之前,绑定大量TCP数据,以提高网络的效率.该算法鼓励发送全尺寸的段(LAN上是1500字节,www上是几百字节).只有其他分组都被确认以后,该算法才会允许发送非全尺寸的分组 ;如果其他分组仍在传输中,那一部分数据就会缓存起来;只有积累的尺寸足够,或者有挂起分组被确认的时候,其才会将缓存的数据发送出去.

该算法会引发多种性能问题:

  • 小的HTTP报文可能无法塞满全尺寸分组,会一直等待其他数据而导致时延.

  • 该算法会阻止数据发送,直到有确认分组到达;但确认分组自身由于延迟确认算法会导致时延.

所以,HTTP程序员会在栈中设置参数TCP_NODELAY禁用该算法.但这需要保证写入大块数据!

2.7.TIME_WAIT积累与端口耗尽

这是严重的性能问题,会影响性能基准.虽然较少出现,但是遇到性能基准问题时,通常是这个问题.且结果特别糟糕.

当某个TCP连接关闭时,内存中会维持一个小控制块,记录最近的TCP连接的端口号和IP,会维持一段时间(2分钟左右),通常是所估计的最大分段使用期的两倍(成为2MSL).

但是,现在高速路由的出现导致重复分组几乎不可能在关闭连接的几分钟之后,出现在服务器上.所以,有的操作系统会改小这个值.但要小心:分组确实会被赋值,如果来自之前的复制分组插入连接值相同的新TCP流,则会破坏TCP数据.

在性能基准环境下,这个2MSL的连接关闭延迟会成为大问题.由于:

  • 测试的机子只有几台,IP数量受限

  • 服务器在默认端口上监听,端口号也受限了.

假设只有一台测试用机器,这样四个值(源IP地址/目标IP地址/源端口/目标端口)就只有一个值是可变的了.由于可用的端口数量有限,连接率(每秒连接的次数)就会固定在一个较低的次数.要修正该问题,有以下方法:

  • 是增加客户端负载生成及其的数量

  • 循环使用虚拟IP地址

但是,如果在大量连接处于打开状态的情况,或者处于等待状态的连接分配大量控制块的情况,会导致操作系统的速度大降.

三、HTTP连接的处理

正常情况下,HTTP连接过程中存在一串HTTP中间实体(代理/高速缓存等等),而中间实体之间会交流一些信息,这些信息就存放在Connection首部中.这在等下会提及;除此之外,HTTP的事务处理通常是串行进行,所以存在性能延时的叠加.这也是我们要考虑的问题.

3.1.Connection首部

两个相邻的HTTP应用层程序会在共享的一条连接中应用一组选项,而Connection首部则有一个用逗号分割的连接标签 列表,指定了不会给其他连接的选项.比如,使用Connection: close指定发送完下一跳报文后关闭的连接.

Connection首部承载的标签有三种:

  • HTTP首部字段名.列出只与当前连接相关的字段

  • 任意标签名,用于描述使用的非标准选项

  • close,关闭连接

由于Connection首部可以避免对本地首部的无意转发,所以将逐跳首部名放入其中被称作”对首部的保护”.Connection首部及其列举字段都会在转发时被删除.

3.2.串行事务处理

如果使用串行事务处理,TCP的性能时延会叠加起来.假设加载一个包含了三张图片的Web页面,发起4个HTTP事务来显示页面,且每个事务都需要一个新的连接,则会叠加连接时延和慢启动时延.

这种时延除了实际上的等待时间,还有心理上的4.同时加载多幅图片会比较好.除此之外,如果浏览器在对象加载完成之前无法知晓对象尺寸,同时需要尺寸信息决定如何排版.这时候如果加载对象数量不够,就不会显示任何东西.可能加载进度正常,但是用户只能看着白屏大怒.

现在,有四种方法可以提高HTTP的连接性能.

  • 并行连接:通过多条TCP连接发起HTTP请求.

  • 持久连接:通过重用TCP连接,消除连接和关闭时延

  • 管道化连接:共享TCP连接发起并发的HTTP请求.

  • 复用的连接:交替传送请求和响应报文.(实验中)

四、并行连接

该方法允许客户端打开多条连接,并行地执行多个HTTP事务.包含嵌入对象的组合页面如果可以克服单条连接的空载时间和带宽限制,就可以重叠时延.如果带宽够大,就可可以将未用带宽进行分配.时延并不是不存在,但是会使得其大部分重叠.

4.1.局限

并行连接的速度并不总是更快 .这有两个原因:

  • 用户的带宽不足.如果带宽足够小,则不得不花费大部分时间来传送数据.这时 如果连接的服务器速度较快,则带宽会迅速耗尽.

  • 如果多个对象竞争优先的带宽,则会导致每个对象的速度都慢下来.

  • 打开大量连接会消耗大量内存,对于服务器和客户端都是.所以,浏览器会限制并行连接的总数,而服务器会随意关闭来自特定客户端的超量连接.

P.S.安慰剂

用户通常觉得并行连接更快,因为:用户看得见加载的进展.

  1. 实际上,如果计算机或者网络崩溃,连接会断开,但这种时候对通知两端通信中断.
  2. 浏览器会存储一个DNS缓存,保存最近访问的站点的IP地址.所以如果是经常访问的网站,可以立即完成查询.
  3. 这种行为称作”发送端傻窗口综合症”,有违社会公序良俗,应该立法禁止之.(‘_’)
  4. 即使同时加载多幅图片比较慢,人们心理上也会觉得快一些…
    原文作者:HTTP
    原文地址: https://segmentfault.com/a/1190000004415159
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞