目的:
更快的内容到达时间
核心思路:
- 更好的传输效率
- 更少的请求数量
- 更小的资源大小
- 合适的缓存策略
最佳实践:
-
减少DNS查找:
每次主机名的解析都需要一次网络往返,增加了请求的延迟时间,还会阻塞后续请求
-
重用TCP链接:
尽可能使用持久连接,消除因TCP握手和慢启动导致的延迟
-
减少HTTP重定向:
重定向需要额外的DNS查询和TCP握手,非常耗时,最佳的重定向次数为0
-
压缩传输的资源:
比如Gzip、图片压缩
-
使用缓存:
比如HTTP缓存、CDN缓存、Service Worker缓存
-
使用CDN(内容分发网络):
把数据放在离用户地理位置更近的节点,可以明显减少每次TCP连接的网络延迟,增大吞吐量
-
删除没有必要请求的资源:
省流、省时
-
在客户端缓存资源:
缓存必要的应用资源,避免重复请求相同内容,比如图片
-
压缩传输内容:
传输数据之前先压缩,减少传输的字节,需要注意在压缩的时候确保对每种不同的资源采用最好的压缩手段
-
消除不必要的请求开销:
减少请求的HTTP头部数据,比如cookie
-
并行处理请求和响应:
请求和响应的排队都会导致延迟,可以尝试并行处理,比如利用多个HTTP1.1连接实现并行下载,在可能的情况下使用HTTP管道计数
-
优化协议版本:
升级到HTTP2.0
-
采用服务端渲染:
解决SPA应用首屏渲染慢的问题
-
预渲染加载静态页面:
页面渲染的极致性能,比较适合静态页面
具体优化策略
DNS解析优化
一般来说,典型的一次DNS解析需要耗费20-120毫秒,当网站资源依赖于多个不同域的时候,时间会成倍的增加,从而影响了网站的加载时间。
DNS解析时间可通过浏览器F12 - Network - Waterfall / Timing查看,查看前需清除浏览器DNS缓存和操作系统DNS缓存
在前端方面,可以通过以下两点来进行优化:
1、减少DNS的请求次数
-
缓存DNS以提高性能,一般浏览器会默认帮我们做这件事情;
-
减少域名的数量,可将资源划分为至少2个但不超过4个域名,这即减少了DNS查找又兼顾了并行下载效率;
2、进行DNS预获取:dns-prefetch
-
预解析后面要加载的文件,或者用户会尝试打开的链接;
-
默认情况下浏览器会对页面中的跨域域名进行预获取并且缓存;
-
使用方式:
<link rel="dns-prefetch" href="//xxx.xxx.xxx">;
HTTP优化
1、长连接
HTTP 协议的初始版本中,每进行一次通信就要断开一次tcp连接,但是随着互联网的发展,文档中包含大量富文本的场景多了起来。
为了解决这个问题,有些浏览器在请求时,用了一个非标准字段:Connection: keep-alive来要求服务器不要关闭tcp连接,以便其他请求复用。
HTTP/1.1 中,最大的变化就是引入了持久连接(长连接):
-
长连接:默认开启,超时自动关闭;
-
管道机制:同一tcp连接里边,客户端可以同时发送多个请求,服务端按顺序返回响应(请求并行,响应串行);
-
Content-Length:用来区分数据包是属于哪一个响应的;
-
分块传输编码(流模式):
Transfer-Encoding: chunked-
用于耗时的动态操作,产生一块数据,就发送一块;
-
每个非空的数据块之前,都有一个16进制的数值,表示这个块的长度;
-
最后一个大小为0的块,表示本次回应的数据发送完了;
-
-
存在的问题:
同一tcp连接里边,服务端是按次序处理响应的,如果前边的回应比较慢,后面就会有许多请求排队等着,造成“队头阻塞”,为了避免这个问题,只有两种方法:
-
减少请求数;
-
同时多开持久连接;
-
2、HTTP/2
此命名法表示不会再有子版本,下一个版本直接是HTTP/3,HTTP 2最大的变化就是二进制协议。
-
二进制协议
http/1.1中,头信息是文本,数据体可以是文本,可以是二进制;
http/2中,头信息和数据体都是二进制,并且统称为 “帧” :头信息帧、数据帧;
-
多工
http/2中,客户端和服务端可以同时发送多个请求和响应,且不用按照顺序,避免了“队头阻塞”;
这样双向、实时的通信,就叫做多工。 查看示例
-
数据流
-
数据流ID
http/2中,将每个请求和回应的所有数据包,称为一个数据流,每个数据流都有一个独一无二的编号(ID);
一个tcp连接中,回应的数据包中必须标记数据流的ID,用以区分属于哪个数据流(响应);
规定:客户端发出的数据流ID,一律为奇数,服务端发出的,为偶数;
-
取消数据流
http/1.1中,只能通过断开tcp连接取消请求;
http/2中,可以取消某一次请求(发送RST_STREAM帧),同时保持tcp的连接以便其他请求使用;
-
优先级
客户端可指定数据流的优先级,优先级越高,服务器越早回应;
-
-
头信息压缩
http/2中引入了头信息压缩机制,可使用gzip、compress等压缩后再发送;
http/1.1中,每次请求必须带上所有的cookie、User-Agent等信息,一模一样的内容重复附带,浪费带宽,也影响速度;
http/2中,可在客户端和服务端各维护一份头信息表,根据字段生成索引号,后续只需要发送索引号即可;
-
服务器推送
http/2允许服务器未经请求,主动向客户端发送数据,比如静态资源的推送;
数据压缩
1、响应数据压缩
客户端使用 Accept-Encoding: gzip, deflate, br, ... 告诉服务器它支持的压缩格式;
服务端使用 Content-Encoding: gzip 告诉客户端它使用的压缩格式;
主要针对文本
2、请求数据压缩
-
请求头信息压缩
http/2支持头部信息压缩,http/2之前不支持;
-
请求体数据压缩
一般需要自定义,与服务端协商解决,常用的三种压缩方式:gzip、deflate、deflate-raw;
HTTP缓存(重要)
使用缓存能有效减少带宽浪费,减少浏览器延迟,是一种提升网站性能和用户体验的有效策略;
缓存的原理是在首次请求后保存一份响应资源的副本,当再次发起相同请求,如果判断缓存命中则拦截请求,返回之前存储的副本,从而避免向服务端发起资源请求;
浏览器缓存属于私有缓存,只能被单独的用户使用,它又细分为强制缓存和协商缓存;
二者最大的区别是,强制缓存只判断数据是否过期,协商缓存则需要询问服务器数据有更新;
强缓存
http/1.0中,使用 Expire:UTC时间格式 来判断缓存是否过期;
这样有个很大的弊端:过分依赖本地时间戳,如果客户端时间与服务端时间不同步,那么对缓存的判断就无法与预期相符;
http/1.1中,新增了 Cache-Control 字段来对Expire进行扩展和完善,常用参数如下:
-
max-age: xxx,xxx定义一个滑动时间,单位s,代表资源的过期时间;
-
no-cache: 强制进行协商缓存;
-
no-store: 禁止使用缓存策略,客户端的每次请求都需要服务端给予全新响应;
-
public: 允许代理服务器进行缓存(共享缓存);
-
private: 只允许客户端进行缓存(私有缓存),默认;
-
s-maxage: 设置代理服务器的缓存时间,需搭配public使用;
协商缓存
协商缓存需搭配 Cache-Control: no-cache 使用;
服务端在响应头设置 last-modified: UTC时间格式,之后客户端再次请求时会在请求头通过 If-Modified-Since: UTC时间格式 带入这个时间,服务端拿到后跟当前文件修改时间作对比,如果一致就返回304,客户端拿到304响应就会去缓存中取数据;
last-modified 能满足大多数场景,但也有两点不足:
-
如果文件只改了名字没有改内容,时间戳也会更新,容易造成带宽资源浪费;
-
时间戳单位是秒,如果文件在毫秒级别内完成修改,就无法验证缓存的有效性了;
http/1.1新增了一个ETag(Entity Tag 实体标签)来弥补这个不足:服务端设置 ETag: 指纹串,客服端再次请求时会通过 If-None-Match 带入,服务端拿到后对比并返回相应状态码;
指纹串可根据内容或指定属性生成,可使用第三方包
ETag虽然补充了last-modified的不足,但它也存在一些弊端:
-
如果资源的尺寸大、数量多、修改频繁,生成ETag的过程会影响服务器的性能;
-
ETag分强验证和弱验证,弱验证根据资源部分属性生成,无法保证每个字节都相同,特别是在服务器集群场景下,无法保证协商缓存验证的准确率;
缓存如何决策
我们既希望缓存能在客户端尽可能久的保存,又希望它能在资源发生修改时及时进行更新,如何解决这两个互斥的优化诉求?
我们可以将网站所需的资源按照不同的类型制定不同的缓存策略:
-
HTML文件包含了其他文件,需要保证其内容发生修改时能及时更新,建议使用协商缓存 no-cache;
-
图片资源一般都是修改,且考虑到数量和大小对缓存空间的影响,可采用强制缓存且过期时间不宜过长 max-age=86400;
-
文本文件可能存在内容的不定期修改,可考虑增加文件指纹(比如打包命名的hash串),文件名修改导致请求的URL不同,必然会对资源重新请求保证其时效性。同时为了兼顾重用效率,考虑到浏览器和CDN等中间代理的缓存,其过期时间可适当延长到一年,即 public, max-age=31536000;
-
包含用户敏感信息的脚本文件,为防止代理服务器缓存,可设置 private;
CDN缓存
即内容分发网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、调度及内容分发等,使用户在请求内容时能就近获取,以此来降低阻塞,提高响应速度。
CDN主要针对静态资源的存放,一般需要前后端配合来进行优化。
这里放一个示例:主站请求的域名为 www.xxx.com ,静态资源请求CDN服务器的域名可以用 a.xxx.com 和 b.xxx.com,设计成与主站域名不同的原因主要有两点:
-
避免对静态资源的请求携带不必要的cookie信息;
-
浏览器对同一域名下并发请求的限制;
要注意对同一资源使用不同域名请求的问题,这样会导致缓存无法重用。