HTTP简史
HTTP(HyperText Transfer Protocol,超文本传输协议)。
HTTP 0.9:只有一行的协议
Tim Berners-Lee罗列HTTP协议的几条宏观设计目标:支持文件传输、能够请求对超文本文档的索引搜索、格式化协商机制,以及能够把客户端引导至不同的服务器。
HTTP 0.9的功能:
- 客户端/服务器、请求/响应协议;
- ASCII协议,运行于TCP/IP链接之上;
- 设计用来传输超文本文档(HTML);
- 服务器与客户端之间的连接在每次请求之后都会关闭。
HTTP 1.0:迅速发展及参考性RFC
RFC 1945催收HTTP 1.0,关键变化:
- 请求可以由于多行首部字段构成;
- 响应对象前面添加一个响应状态行;
- 响应对象也有自己的由换行符分隔的首部字段;
- 响应对象不局限于超文本;
- 服务器与客户端之间的连接在每次请求之后都会关闭。
请求和响应首部都使用ASCII编码,但响应对象本身可以是任何类型:HTML、纯文本、图片,或其他内容类型。
HTTP 1.1:互联网标准
HTTP 1.1标准厘清之前版本中很多有歧义的地方,还加入很多重要的性能优化:持久连接、分块编码传输、字节范围请求、增强的缓存机制、传输编码及请求管道。
HTTP 1.1改变HTTP协议的语义,默认使用持久连接。除非明确告知(通过Connection: close首部),否则服务器默认会保持连接打开。
HTTP 1.0,可通过Connection:Keep-Alive首部来启用。
HTTP 2.0:改进传输性能
HTTP 2.0的主要目标是改进传输性能,实现低延迟和高吞吐量。
Web性能要点
Web性能优化思路:
- 延迟和带宽对Web性能的影响;
- 传输协议(TCP)对HTTP的限制;
- HTTP协议自身的功能和缺陷;
- Web应用的发展趋势及性能需求;
- 浏览器局限性和优化思路。
超文本、网页和Web应用
互联网发展史,至少带给三种体验:超文本文档、富媒体网页和交互式Web应用。
多TCP连接目前仍然存在,性能的关键指标已经从文档加载时间,变成页面加载时间。PLT,Page Load Time,简单定义:浏览器中的加载旋转图标停止旋转的时间。更技术的定义则是浏览器中的onload事件,这个事件由浏览器在文档及其所有依赖资源(JS、图片,等等)下载完毕时触发。
Web应用把网页的简单依赖关系(在标签中使用媒体作为基本内容的补充)转换成复杂的依赖关系:标记定义结构、样式表定义布局,脚本构建最终的交互式应用,响应用户输入,并在交互期间创建样式表和标记。
浏览器在解析HTML文档的基础上构建DOM(Document Object Model,文档对象模型)。CSSOM(CSS Object Model,CSS对象模型),也会基于特定的样式表规则和资源构建而成。这两个模型共同创建渲染树,之后浏览器就有足够的信息去进行布局,并在屏幕上绘制图形。
应用的性能,特别是首次加载时的渲染前时间,直接取决于标记、样式表和JS这三者之间的依赖关系。
剖析现代Web应用
HTTP Archive一直在抓取世界上最热门的网站(Alexa前100万名中的30多万名),记录、聚合并分析每个网站使用的资源、内容类型、首部及其他元数据的数量。
速度、性能与用户期望
Web性能优化的经验法则:必须250ms内渲染页面,不行的话,至少提供视觉反馈,才能保证用户不走开!
分析资源瀑布
资源瀑布:浏览器内置工具。如下图,某CSDN页面,Chrome浏览器F12,点击Network,可看到大部分请求在1700ms-2500ms内渲染完成。
开源的在线工具,如WebPageTest,可在不同的浏览器中呈现资源瀑布。
每一个HTTP请求都由很多独立的阶段构成:DNS解析、TCP连接握手、TLS协商(必要时)、发送HTTP请求,然后下载内容。
Start Render(绿色的竖线)会在所有资源下载完成前开始,以便用户在页面构建期间就能与之交互。DocumentComplete事件(蓝色的竖线)也会在剩余资源下载完成前触发。
讨论Web性能时有三个不同测量指标:最早渲染时间、文档完成时间和最后资源获取时间。
资源瀑布图记录的是HTTP请求,而连接视图展示每个TCP连接的生命期,这些连接用于获取后端服务器的资源。连接视图的底部显示带宽利用率曲线。可用连接的带宽利用率很低,限制Web性能的主要因素是客户端与服务器之间的网络往返延迟。
性能来源:计算、渲染和网络访问
Web应用的执行主要涉及三个任务:取得资源、页面布局和渲染、JS执行。渲染和脚本执行在一个线程上交错进行,不可能并发修改生成的DOM。
降低RTT,而不是提高带宽。
减少页面加载时间的方法,就是减少加载每个页面过程中的往返次数。
大多数HTTP数据流都是小型突发性数据流,而TCP则是为持久连接和大块数据传输而进行过优化的。
人造和真实用户性能度量
宽泛地说,在受控度量环境下完成的任何测试都可称为人造测试。
人造测试不能发现所有性能瓶颈。特别是在人造环境下收集到的性能数据缺乏现实当中的多样性,难以据之确定应用带给用户的最终体验。这个问题有如下表现:
- 场景及页面选择:很难重复真实用户的导航模式;
- 浏览器缓存:用户缓存不同,性能差别很大;
- 中介设施:中间代理和缓存对性能影响很大;
- 硬件多样化:不同的CPU、GPU和内存比比皆是;
- 浏览器多样化:各种浏览器版本,有新有旧;
- 上网方式:真实连接的带宽和延迟可能不断变化。
真实用户度量(RUM,Real-User Measurement)。
W3C Web Performance Working Group通过引入NavigationTiming API为做真实用户测试提供便利,这个API目前已得到很多现代桌面和移动浏览器的支持。
Navigation Timing提供以前无法访问的数据,如DNS和TCP连接时间,且精确度极高(微秒级时间戳)。要获得这些数据,可在浏览器中访问标准的performance.timing
对象。
分析性能数据时,要时时关注数据的潜在分布,抛开均值,多用直方图、中位值和分位数。对于偏态分布和多重模态分布,均值是个没有意义的指标。
Navigation Timing只针对根文档提供性能计时器,因此W3C Performance Group还标准化另外两个API:
- User Timing:JS API,可以标记和度量特定 应用的性能指标,提供高精确度的计时结果
- ResourceTiming:针对页面中的每个资源都提供类似的性能数据,可以让我们收集到关于页面的完整性能数据
针对浏览器的优化建议
核心优化策略来说,可宽泛地分为两类:
- 基于文档的优化:熟悉网络协议,文档、CSS和JS解析管道,发现和优先安排关键网络资源,尽早分派请求并取得页面,使其尽快达到可交互的状态。主要方法是优先获取资源、提前解析等
- 推测性优化:浏览器可以学习用户的导航模式,执行推测性优化,尝试预测用户的下一次操作。然后,预先解析DNS、预先连接可能的目标。
大多数浏览器都利用如下四种技术:
- 资源预取和排定优先次序:文档、CSS和JS解析器可以与网络协议层沟通,声明每种资源的优先级:初始渲染必需的阻塞资源具有最高优先级,而低优先级的请求可能会被临时保存在队列中;
- DNS预解析:对可能的域名进行提前解析,避免将来HTTP请求时的DNS延迟。预解析可以通过学习导航历史、用户的鼠标悬停,或其他页面信号来触发;
- TCP预连接:DNS解析之后,浏览器可以根据预测的HTTP请求,推测性地打开TCP连接。如果猜对的话,则可以节省一次完整的往返(TCP握手)时间;
- 页面预渲染:某些浏览器可以让我们提示下一个可能的目标,从而在隐藏的标签页中预先渲染整个页面。这样,当用户真的触发导航时,就能立即切换过来。
HTML文档在浏览器中是递增解析的,服务器会尽可能频繁发送文档的每一可用部分,这样客户端才能尽早发现和获取关键资源。
HTTP 1.x
HTTP 1.x增强性能的特性:
- 持久化连接以支持连接重用;
- 分块传输编码以支持流式响应;
- 请求管道以支持并行请求处理;
- 字节服务以支持基于范围的资源请求;
- 改进的更好的缓存机制。
《高性能网站建设指南》中概括14条规则,有一半针对网络优化:
- 减少DNS查询:每次域名解析都需要一次网络往返,增加请求的延迟,在查询期间会阻塞请求。
- 减少HTTP请求:任何请求都不如没有请求更快,因此要去掉页面上没有必要的资源。
- 使用CDN:从地理上把数据放到接近客户端的地方,可以显著减少每次TCP连接的网络延迟,增加吞吐量。
- 添加Expires首部并配置ETag标签:相关资源应该缓存,以避免重复请求每个页面中相同的资源。Expires首部可用于指定缓存时间,在这个时间内可以直接从缓存取得资源,完全避免HTTP请求。ETag及Last-Modified首部提供一个与缓存相关的机制,相当于最后一次更新的指纹或时间戳。
- Gzip资源:所有文本资源都应该使用Gzip压缩,然后再在客户端与服务器间传输。一般来说,Gzip可以减少60%~80%的文件大小,也是一个相对简单(只要在服务器上配置一个选项),但优化效果较好的举措。
- 避免HTTP重定向:HTTP重定向极其耗时,特别是把客户端定向到一个完全不同的域名的情况下,还会导致额外的DNS查询、TCP连接延迟,等等。
消除和减少不必要的网络延迟,把传输的字节数降到最少。这两个根本问题永远是优化的核心,对任何应用都有效。
如果遗留历史站点(涉及前后端、网关等各模块)只能使用HTTP 1.1,且一时半会不能升级到HTTP 2.0。那就需要使用各种优化技术,如:连接文件、拼合图标、分割域名或嵌入资源。事实上,有些问题在升级到HTTP 2.0后,可能会依旧存在。因此,哪怕在2024年9月份的当下,熟悉HTTP 1.1版本的优化技术还是很有必要的。
持久连接
由一个新TCP连接发送的HTTP请求所花的总时间,最少等于两次网络往返的时间:一次用于握手,一次用于请求和响应。这是所有非持久HTTP会话都要付出的固定时间成本。
添加对HTTP持久连接的支持,就可以避免第二次TCP连接时的三次握手、消除另一次TCP慢启动的往返,节约整整一次网络延迟。
HTTP管道
持久HTTP可以重用已有的连接来完成多次应用请求,但多次请求必须严格满足先进先出(FIFO)的队列顺序:发送请求,等待响应完成,再发送客户端队列中的下一个请求。HTTP管道可以把FIFO队列从客户端(请求队列)迁移到服务器(响应队列)。
理想很丰满,现实很骨感。
HTTP 1.x的局限性:只能严格串行地返回响应。不允许一个连接上的多个响应数据交错到达(多路复用),因而一个响应必须完全返回后,下一个响应才会开始传输。
队首阻塞:即使客户端同时发送两个请求,而且CSS资源先准备就绪,服务器也会先发送HTML响应,然后再交付CSS。并经常导致次优化交付:不能充分利用网络连接,造成服务器缓冲开销,最终导致无法预测的客户端延迟。
TCP队首阻塞:TCP要求严格按照顺序交付,丢失一个TCP分组就会阻塞所有高序号的分组,除非重传那个丢失的分组,这样就会导致额外的应用延迟。
实际中,由于不可能实现多路复用,HTTP管道会导致HTTP服务器、代理和客户端出现很多微妙的,不见文档记载的问题:
- 一个慢响应就会阻塞所有后续请求;
- 并行处理请求时,服务器必须缓冲管道中的响应,从而占用服务器资源,如果有个响应非常大,则很容易形成服务器的受攻击面;
- 响应失败可能终止TCP连接,从页强迫客户端重新发送对所有后续资源的请求,导致重复处理;
- 由于可能存在中间代理,因此检测管道兼容性,确保可靠性很重要;
- 如果中间代理不支持管道,那它可能会中断连接,也可能会把所有请求串联起来。
使用多个TCP连接
大多数现代浏览器,包括桌面和移动浏览器,都支持每个主机打开6个连接。这意味着:
- 客户端可以并行分派最多6个请求;
- 服务器可以并行处理最多6个请求;
- 第一次往返可以发送的累计分组数量(TCP cwnd)增长为原来的6倍。
域名分区
HTTP 1.x协议的一项空白强迫浏览器开发商引入并维护着连接池,每个主机(域名)最多6个TCP流。对于现代Web站点来说,6个并行连接显然不够用。
解决方法,把所有资源分散到多个子域名,即可突破浏览器的连接限制,实现更高的并行能力。域名分区使用得越多,并行能力就越强!缺点是:每个新主机名都要求有一次额外的DNS查询,每多一个套接字都会多消耗两端的一些资源,Web站点开发者必须手工分离这些资源,把它们托管到多个主机上。
把多个域名解析到同一个IP地址是很常见的做法。所有分区都通过CNAME DNS记录指向同一个服务器,而浏览器连接限制针对的是主机名,不是IP地址。每个分区也可以指向一个CDN或其他可以访问到的服务器。
最优的分区数目,取决于页面中资源的数量(每个页面都可能不一样),以及客户端连接的可用带宽和延迟(因客户端而异)。
分区数目不宜过多,应该经过反复验证测试确定出一个合适的数目。DNS查询和TCP慢启动导致的额外消耗对高延迟客户端的影响最大。即,移动客户端经常是受过度域名分区影响最大的!
度量和控制协议开销
RFC 2616(HTTP 1.1)没有对HTTP首部的大小规定任何限制。然而,实际中,很多服务器和代理都会将其限制在8KB或16KB之内。
减少要传输的首部数据(高度重复且未压缩),可以节省相当于一次往返的延迟时间,显著提升很多Web应用的性能。
Cookie在很多应用中都是常见的性能瓶颈,很多开发者都会忽略它给每次请求增加的额外负担。
连接与拼合
嵌入资源
把资源嵌入文档可以减少请求的次数。任何MIME类型,只要浏览器能理解,都可以通过类似方式嵌入到页面中,包括PDF、音频、视频。不过,有些浏览器会限制数据URI的大小,比如IE8最大只允许32KB。
base64编码使用64个ASCII符号和空白符将任意字节流编码为ASCII字符串。编码过程中,base64会导致被编码的流变成原来的4/3,即增大33%的字节开销。
如果应用要使用很小的、个别的文件,在考虑是否嵌入时,可参考如下建议:
- 如果文件很小,且只有个别页面使用,可考虑嵌入;
- 如果文件很小,但需要在多个页面中重用,则考虑集中打包;
- 如果小文件经常需要更新,就不要嵌入;
- 通过减少HTTP cookie的大小将协议开销最小化。
HTTP 2.0
HTTP 2.0的目的:通过支持请求与响应的多路复用来减少延迟,通过压缩HTTP首部字段将协议开销降至最低,同时增加对请求优先级和服务器端推送的支持。
HTTP 2.0修改格式化数据(分帧)的方式,以及客户端与服务器间传输这些数据的方式,新的组帧机制隐藏所有复杂性。
历史及其与SPDY的渊源
谷歌开发的SPDY协议目标:
- PLT降低50%;
- 无需网站开发者修改任何内容;
- 把部署复杂性降至最低,无需变更网络基础设施;
- 与开源社区合作开发这个新协议;
- 收集真实性能数据,验证这个实验性协议是否有效。
SPDY引入新的二进制分帧数据层,以实现多向请求和响应、优先次序、最小化及消除不必要的网络延迟,目的是更有效地利用底层TCP连接。
走向HTTP 2.0
参考SPDY,HTTP/2.0应该满足如下条件:
- 相对于使用TCP的HTTP 1.1,用户在大多数情况下的感知延迟要有实质上、可度量的改进;
- 解决HTTP队首阻塞问题;
- 并行操作无需与服务器建立多个连接,从而改进TCP的利用率,特别是拥塞控制方面;
- 保持HTTP 1.1的语义,利用现有文档,包括(但不限于)HTTP方法、状态码、URI、首部字段;
- 明确规定HTTP 2.0如何与HTTP 1.x互操作,特别是在中间介质上;
- 明确指出所有新的可扩展机制以及适当的扩展策略;
HTTP 2.0增加新的二进制分帧数据层,服务端推送。
设计和技术目标
HTTP/2.0通过支持首部字段压缩和在同一连接上发送多个并发消息,让应用更有效地利用网络资源,减少感知的延迟时间。
二进制分帧层
二进制分帧层,定义如何封装HTTP消息并在客户端与服务器之间传输。
HTTP 1.x以换行符作为纯文本的分隔符,而HTTP 2.0将所有传输的信息分割为更小的消息和帧,并对它们采用二进制格式的编码。
流、消息和帧
概念:
- 流:已建立的连接上的双向字节流
- 消息:与逻辑消息对应的完整的一系列数据帧
- 帧:HTTP 2.0通信的最小单位,每个帧包含帧首部,至少也会标识出当前帧所属的流。
所有HTTP 2.0通信都在一个连接上完成,这个连接可以承载任意数量的双向数据流。相应地,每个数据流以消息的形式发送,而消息由一或多个帧组成,这些帧可以乱序发送,然后再根据每个帧首部的流标识符重新组装。
HTTP 2.0的所有帧都采用二进制编码,所有首部数据都会被压缩。
总结:
- 所有通信都在一个TCP连接上完成。
- 流是连接中的一个虚拟信道,可以承载双向的消息;每个流都有一个唯一的整数标识符。
- 消息是指逻辑上的HTTP消息,如请求、响应等,由一或多个帧组成。
- 帧是最小的通信单位,承载着特定类型的数据,如HTTP首部、负荷等。
- 很多流可以并行地在同一个TCP连接上交换消息。
多向请求与响应
二进制分帧层突破这些限制(多个响应必须排队、队首阻塞等),实现多向请求和响应:客户端和服务器可以把HTTP消息分解为互不依赖的帧,然后乱序发送,最后再在另一端把它们重新组合起来。
多向请求和响应会在整个Web技术栈中引发一系列连锁反应,从而带来巨大的性能提升:
- 可以并行交错地发送请求,请求之间互不影响;
- 可以并行交错地发送响应,响应之间互不干扰;
- 只使用一个连接即可并行发送多个请求和响应;
- 消除不必要的延迟,从而减少页面加载的时间;
- 不必再为绕过HTTP 1.x限制而多做很多工作;
- 减少TCP连接的数量;
- 减少客户端和服务器的CPU及内存占用。
请求优先级
把HTTP消息分解为很多独立的帧后,就可通过优化这些帧的交错和传输顺序,进一步提升性能。每个流都带有一个31比特的优先值,0表示最高优先级, 2 31 − 1 2^{31}-1 231−1表示最低优先级。
HTTP 2.0没有规定处理优先级的具体算法,只规定一种赋予数据优先级的机制,当然要求客户端与服务器能够交换这些数据。
每个来源一个连接
流量控制
服务器推送
首部压缩
有效的HTTP 2.0升级与发现
二进制分帧简介
优化应用交付
性能优化最佳实践
所有应用都应该致力于消除或减少不必要的网络延迟,将需要传输的数据压缩至最少。这两条标准是经典的性能优化最佳实践,是其他数十条性能准则的出发点:
- 减少DNS查找:每一次主机名解析都需要一次网络往返,从而增加请求的延迟时间,同时还会阻塞后续请求。
- 重用TCP连接:尽可能使用持久连接,以消除TCP握手和慢启动延迟。
- 减少HTTP重定向:HTTP重定向极费时间,特别是不同域名之间的重定向,更加费时;这里面既有额外的DNS查询、TCP握手,还有其他延迟。最佳的重定向次数为零。
- 使用CDN:把数据放到离用户地理位置更近的地方,可以显著减少每次TCP连接的网络延迟,增大吞吐量。这一条既适用于静态内容,也适用于动态内容。
- 去掉不必要的资源:任何请求都不如没有请求快。
- 在客户端缓存资源:应该缓存应用资源,从而避免每次请求都发送相同的内容。
- 传输压缩过的内容:传输前应该压缩应用资源,把要传输的字节减至最少:确保对每种要传输的资源采用最好的压缩手段。
- 消除不必要的请求开销:减少请求的HTTP首部数据(如HTTP cookie),节省的时间相当于几次往返的延迟时间。
- 并行处理请求和响应:请求和响应的排队都会导致延迟,无论是客户端还是服务器端。
- 针对协议版本采取优化措施:HTTP 1.x支持有限的并行机制,要求打包资源、跨域分散资源等。HTTP 2.0只要建立一个连接就能实现最优性能,同时无需针对HTTP 1.x的那些优化方法。
客户端缓存资源
保证首部包含适当的缓存字段:
- Cache-Control首部用于指定缓存时间;
- Last-Modified和ETag首部提供验证机制。
同时指定缓存时间和验证方法。
压缩传输的数据
HTML、CSS和JavaScript等文本资源的大小经过gzip压缩平均可以减少60%~80%。而图片则需要仔细考量:
- 图片一般会占到一个网页需要传输的总字节数的一半;
- 通过去掉不必要的元数据可以把图片文件变小;
- 要调整大小就在服务器上调整,避免传输不必要的字节;
- 应该根据图像选择最优的图片格式;
- 尽可能使用有损压缩。
WebP:谷歌开发的一种新图片格式,无损压缩和有损压缩效能都有所提升:
- WebP的无损压缩图片比PNG的小26%;
- WebP的有损压缩图片比JPG的小25%~34%;
- WebP支持无损透明压缩,但因此仅增加22%的字节。
Chrome Data Compression Proxy和Opera Turbo等工具为用户降低带宽占用的主要手段,就是重新把每张图片编码为WebP格式。