性能问题给用户的感受往往是简单而直接的,加载资源慢,运行过程卡顿或者响应交互迟缓。当这些问题摆在前端开发者面前,就是系统级别复杂的场景。
上面结尾也提到了页面的生命周期,这个过程的各个环节就是需要我们深入学习的,我们要做的性能优化和这些是密不可分的,都是针对某个局部过程的优化。
从DNS解析,TCP建立连接到HTTP的请求,以及接下来的前后端的交互,从资源请求,文件解析到关键渲染路径等,每一个环节都可能会产生性能不佳的体验。而我们要去设计可行的优化方案,这个浏览器页面的生命周期必须要清楚。
从输入url到页面加载完成
大家应该都知道这道经典的面试题,从输入url到页面加载完成,整个过程都发生了什么?
这个涉及很多知识点的问题,能够考察面试者在前端性能优化的理解和掌握此过程的深入程度。接下来写一下自己的理解,从前端的角度出发,整个过程分为几个阶段是要清楚的,而每个阶段又可以更加细分到不同知识点。
-
浏览器接收URL,到网络请求线程的开启。
-
一个完整的HTTP请求。
-
服务器接收到请求并转到具体的处理后台。
-
前后台之间的HTTP交互和涉及的缓存机制。
-
浏览器接收到数据包后的关键渲染路径。
-
JS引擎的解析过程。
上面也提到到有一些优化的思路,我们可以看到都是和这里面的知识点有对应的地方。
这张图大家可以收藏多看下,是可以对页面生命周期有个完整的结构。
网络请求线程开启
首先是对URL的解析,解析后就会新建一个网络线程去处理资源下载。
因为网络请求都是单独的线程,因此浏览器会根据解析出得协议,开辟一个网络线程,前往请求资源。
URL一般包括几大部分:
-
protocol:协议头,比如https、http、ftp等
-
host:主机域名或IP地址
-
port:端口号
-
path:目录路径
-
query:查询参数,比如 a=1&&b=2
-
fragment:即#后的hash值,一般用来定位到某个位置
ps:进程:cpu资源分配的最小单位;线程:cpu调度的最小单位,是建立在进程的基础上。
建立HTTP请求
主要分为两部分:
DNS解析和TCP连接。
DNS解析
DNS解析是通过查询将主机名,最终得到该主机名对应的IP地址的过程,简单说就是将域名解析为IP。可以就想成一个人有很多外号,有时候你一时想不起名字,但是外号你直接就能叫出来,比如访问百度,www.baidu.com是域名,我们都是用这个地址去访问,因为百度对应的某个IP是202.108.22.5这种, 是不容易记住的。所以IP,域名都能访问到我们想要的内容,只是IP不容易记住,而域名是很容易记的,就像简称一样。
DNS的两种查询方式:
客户端向本地域名服务器的查询一般是递归查询
本地域名服务器向根域名服务器的查询一般是迭代查询
当局部DNS服务器自己不能回答客户机的DNS查询时,它就需要向其他DNS服务器进行查询。如果客户端请求后,一直等到上一级DNS服务器返回解析结果,就是递归查询。如果是客户端向多个DNS服务器提交了请求,才得到解析结果,就是迭代查询。
递归查询:局部DNS服务器自己以DNS客户的身份向其他DNS服务器进行查询,而不是让客户端自己进行下一步查询。最后得到的查询结果返回给局部DNS服务器,再由局部DNS服务器返回给客户端。
迭代查询:局部DNS服务器把下一步向哪个域名服务器查询返回给客户端,后续由客户端自己进行查询,直到查询到结果。也就是说如果DNS服务器不能返回要查询的IP,不会自己去查,只会告诉你哪个DNS服务器可以处理,让你自己去访问这个DNS服务器。
可以看出,递归查询只会有两种返回:成功或失败;迭代查询返回的是最佳的查询点或者主机地址。
DNS解析速度的优化策略有很多,通常会从以下几个方面思考细节的优化:
-
DNS缓存优化
-
DNS预加载策略
-
页面中资源的域名的合理分配
-
稳定可靠的DNS服务器
DNS预解析
通过域名访问,每次请求都会进行DNS解析,而DNS解析所需的时间差异非常大,所以DNS预解析能让延迟明显减少。
解决方案
DNS Prefetch 是一种DNS预解析技术。是根据浏览器定义的规则,提前解析之后可能会用到的域名,使解析结果缓存到系统缓存中,缩短DNS解析时间,来提高网站的访问速度。
如何使用
-
自动解析器在加载一个html页面的时候,会自动获取当前页面所有a标签href属性中的域名,并且进行DNS预解析。
当浏览器遇到https页面,不会自动进行DNS预解析。
-
自动解析控制
当https页面开启自动解析时,要添加预解析需要如下配置,而http中不需要可以设置off关闭
<meta http-equiv="x-dns-prefetch-control" content="on"> -
手动解析
<link rel="dns-prefetch" href="">
DNS负载均衡
当请求量变大,并发用户多的时候,每次请求的资源都位于同一台机器上,那么这台机器随时可能会崩溃。这时就需要用DNS负载均衡技术,DNS负载均衡是通过DNS服务器实现的,是要配置多个IP地址,就可以把客户端的请求分散到不同的机器上去,使不同的客户端访问不同的服务器。而一个IP地址只能对应一个机器,所以多个IP是前提。
缺点:一个是无法区分服务器是否挂掉,就是不论服务器是否挂掉,DNS都会分配。另一个是不会关心请求是否被DNS缓存,这样可能会造成有的服务器压力很大,有的就会比较空闲。
ps:一般常见的负载均衡有两种:1.客户端与反向代理服务器之间的DNS负载均衡;2.反向代理服务器与应用服务器之间的负载均衡。常说的nginx负载均衡就属于第二种。
建立TCP请求连接
在静态资源占整个页面请求的90%以上时,只能性能提升10%,整体的性能体验改观就相当明显。
TCP通过“发送—应答—重传”来确保传输的可靠性,它是端到端进行传输的。在传输中是分段的,每次发送端只会发送若干段。在此过程,报文分段按照顺序进行发送,以便能够完整正确的组装。
浏览器和服务端TCP请求建立的过程,基于TCP/IP,全称Transmission Control Protocol/Internet Protocol,该协议由网络层的IP和传输层的TCP组成。
浏览器在DNS解析得到IP后,就开始建立TCP请求连接。
TCP通过三次握手建立连接,相对应的通过四次挥手断开连接。
这两个图就可以详细的表达这两种过程,这里也就不再详细的展开说明。
其中两个关键要素:1.网络传输链路建立;2.数据传输确认。
在网络传输中,并不是每条链路都能保持畅通,数据传输过程中,也是不可避免的会出现丢失或者被破坏的情况。
对于第二种情况,就需要优化数据大小,减少数据因传输丢失或者被破坏产生的重传,从而提高传输效率。
对于第一种情况,需要针对性的建立高速的专属网络通道,或者通过购买第三方内容网络服务来加速浏览器和服务器之间的网络通道优化。
前后端交互
静态资源使用 CDN
CDN提供就近访问的能力,消除了由于用户离机房的距离不一样带来的体验差异,CDN的优化对于提供高性能的用户体验起到了关键的作用。CDN访问比起传统访问,就是多了缓存服务器,如果缓存命中,就不会去源站再去获取,如果没有,那就需要向源站发起请求,获取数据后,就会在缓存服务器缓存一份数据。
CDN主要提供了6种能力
-
静态加速能力
通过本地化缓存加速能力提供就近访问的高性能访问架构,将用户访问的内容缓存在边缘节点上,消除由地域差异而导致的用户体验不一致。
-
卸载源站能力
大幅度减少源站的访问量和带宽占用,比如图片之类的静态资源占了网站所有请求的90%以上,而这个访问量是特别巨大的,CDN的存在就会大大减小了源站的压力,提高了网站的稳定性。
-
防攻击能力
大量CDN的存在,可以有效的将对网站的恶意攻击由中心化分散到CDN的边缘上,从而有效的阻止或者减小攻击造成的危害。
-
动态加速能力
通过资源缓存在CDN边缘节点上,让用户访问资源的网络距离变短,从而实现性能优化。
-
用户访问序列优化能力
-
页面内资源预取
获取页面的HTML后,CDN服务器会将资源提前预取到CDN的边缘节点上,可以避免从源站获取资源,减少性能损耗。
-
页面间资源预取
在用户访问当前网页时,将要访问页面的资源提前预取到CDN边缘节点上。
-
-
定制化模块开发能力
提供定制化的功能开发,例如边缘化的图片压缩,自适应图片下载等。
ps:边缘节点就是指的实际提供给用户就近连接,访问的服务器。
HTTP相关协议
HTTP是建立在传输层TCP协议之上的应用层协议,在TCP层面上存在长链接和短链接的区别。
长链接就是在客户端与服务端建立的TCP连接上,可以连续发送多个数据包,但需要双方发送心跳检查来维持这个连接。
短连接就是当客户端需要向服务端发送请求时,会建立TCP连接,当请求发送并收到响应后,则断开此连接。
在HTTP 1.0时,默认使用短连接,浏览器每次的HTTP操作就会建立一个TCP请求,任务结束就会断开。
在HTTP 1.1时,默认使用长链接,当一个网页打开操作完成时,所建立的TCP连接并不会断开,客户端后续的请求操作便会继续使用这个已经建立的连接。但长链接也不是永久保持,是由一个持续时间,可在服务器中进行配置。
在HTTP 2.0后,比起HTTP 1.1就多了以下新特性:
-
多路复用,无需多个TCP连接,因为其允许在单一的HTTP2连接上发起多重请求,因此可以不用依赖建立多个TCP连接。
-
二进制分帧,在应用层和传输层间,加入一个二进制分帧层,并且会将信息分割成更小的帧请求。
-
头部压缩,减小报文传输体积。
-
服务端推送,服务端可以在客户端发起请求前发送数据,换句话说,服务端可以对客户端的一个请求发送多个响应,这样可以实现服务端主动向客户端推送。
ps:使用 http2 的前提是必须是 https,而之前针对HTTP 1.1限制的优化方案也就不再需要了,如图片使用雪碧图。
浏览器缓存
在基于HTTP的前后端交互过程中,使用缓存可以使得性能显著提升。具体缓存策略分为两种:强缓存和协商缓存。
强缓存就是当浏览器判断出本地缓存未过期时,直接读取本地缓存,无须发起HTTP请求,此时转态为:200 from cache。
协商缓存则需要浏览器向服务器发起HTTP请求,来判断浏览器本地缓存的文件是否修改,若未修改则从缓存中读取,此时状态为:304。
在浏览器缓存中,强缓存优于协商缓存,若强缓存生效直接使用强缓存,若不生效再进行协商缓存的请求,由服务器判断是否使用缓存,如果都失效则重新想服务器发起请求获取资源。
下图可以说明浏览器缓存触发的逻辑:
关键渲染路径
从服务器获取到了要访问的页面后,就需要把资源文件进行解析,渲染出来,变成我们所看到的页面内容。
首先浏览器会通过解析HTML和CSS文件,来构建DOM树和CSSDOM树,两者是独立的树形结构,但DOM树是展示的文档内容,CSSDOM树是文档对象对应的样式规则,需要将两个对象模型合并为渲染树,这样浏览器才能把内容按照样式规则渲染到页面。
服务器返回HTTP请求后,浏览器开始接收数据,到最终在屏幕上显示内容需要完成以下步骤:
-
解析HTML的头部代码,下载头部代码中引用的资源文件
-
解析HTML代码和样式文件代码,这个过程会有两个树结构产生,就是DOM树和CSSDOM树。
-
通过遍历两个树结构,浏览器依次计算每个节点的大小,位置,颜色等样式,合并构造出渲染树。
-
根据渲染树完成绘制过程。
优化关键渲染路径就是要在这个解析,渲染过程尽可能的用更少的时间完成。
在这个页面解析过程中,浏览器会因为各种因素被阻断。
-
JS代码是会阻断DOM树的构造,因为JS代码是可能会修改DOM结构,所以必须等JS代码执行完毕,才会恢复DOM的构造。这个是由浏览器的安全解析策略决定的。
-
浏览器必须等待样式表加载完成,才会开始构建CSSDOM树。
-
还有一种情况,浏览器再解析HTML时遇到JS代码,而此时CSSDOM树还未构建完成,浏览就就会暂停脚本执行(JS会阻断DOM树构造,所以这里也会导致DOM树的构建被阻塞),直到CSSDOM的构建,才会重新恢复原来的解析。
可以发现,HTML中内联JS代码执行的危害不在于阻断DOM树的构建 。而是在于DOM树构建被阻塞的时间不仅仅只是JS代码运行的时间,还会加上样式资源文件下载和CSSDOM树的构建时间。
这个过程的优化就可以从以下几个方面着手:
-
优化HTML代码和结构,缩短HTML下载时间,加快解析速度。
-
优化CSS代码和结构,缩短CSS文件下载时间和解析时间。
-
合理放置JS代码,避免DOM树构建的阻塞。
小结
看了很多本性能优化相关的书籍,希望可以尽可能的全面的描述真个生命周期的过程,最后发现自己有太多没有接触的知识,也很难描述清楚一些知识点的内容,所以最后就把有关前端的知识体系拉出来,让大家对关键知识点的原理可以有个完整认识。
浙江大华技术股份有限公司-软研-智慧城市产品研发部招聘高级前端!!!!! 欢迎大家来聊,有意向可发送简历到 chen_zhen@dahuatech.com,加入我们,可以一起进步,一起聚餐,一起旅游,让我们从世界村的小伙伴变成大华村的小伙伴。