Web前端很多优化原则都是从如何提升网络通讯效率的角度提出的,但是这些原则使用的时候还是有很多陷阱在里面,如果我们不能深入理解这些优化原则背后所隐藏的技术原理,很有可能掉进这些陷阱里,最终没有达到最佳的预期效果,今天我在这里分析下浏览器和服务端通讯的一些细节问题,希望通过分析这些细节问题,能给大家一个启迪,能更好的理解这些优化原则背后的隐秘,最终能更好的运用这些原则。
网站的通讯技术是构建在http协议上,http协议底层通讯手段使用的是tcp/ip协议,但是tcp通讯协议在建立连接和断开连接这两个动作上是非常消耗通讯性能的,这主要是因为tcp/ip协议在连接建立时候的三次握手机制和断开连接时候的四次挥手机制所致,我们来看看下面的图形:
图中中间被红色标记的方块就是tcp/ip协议在建立连接时候需要发送三次报文才能确认连接是否建立成功,中间四个蓝色的方框就是说明tcp/ip协议在断开连接时候要发四次报文才能确定连接最终被断开,而一个具体的http请求和响应也就发送两次报文,这也就说明如果浏览器每次和服务端的交互都要新建和关闭一个tcp/ip连接,那么浏览器和服务器之间就要往返9次报文通讯,而真正用来处理用户请求的报文确只有其中的两次,换句话说这样的一个请求大概会有80%左右的性能都不是用来处理业务需求,等于是损失了80%左右的性能,当然这个比率是9次报文交互的数据大小一致情况下得出的,如果用户业务请求和响应的数据量比较大,那么建立连接和断开连接的性能损失占比会降低,不过就算占比降低了那也是在请求处理本身的时间变的更慢的基础上的降低,要是浏览器和服务器之间的距离特别大,那么多出来的7次报文交换的效率问题就更加严重了,不管怎样,tcp/ip的三次握手机制和四次挥手机制只要发生都会对网络请求效率产生重大影响。
为了解决这个报文交互次数过多的问题,http协议本身也发生了改变,那就是http开始采用了长连接,使用长连接后网站只需要开启一个长连接,在用户关闭浏览器关闭之前浏览器里的网页都会复用这个长连接。不过http协议的1.0版本默认是不启用长连接的,所以在使用http协议1.0版本时候我就得手动的打开长连接,这个方法就是在http头里设置Connection: Keep-Alive,而http1.1版本里长连接是默认打开的,所以不需要我们手动的设置,而且时下的浏览器几乎都支持http1.1协议,因此大多时候情况下我们是没有必要手动去打开长连接的。
虽然http协议采用长连接后可以减少网站通讯时候三次握手和四次挥手的次数,但是长连接建立起来后需要浏览器和服务器长时间维护,这本身会消耗浏览器和服务器的性能,特别是服务器端长时间维护长连接本身还会损坏服务器处理并发的能力,所以早期浏览器会限制http1.1开启连接的数量,例如ie7这个古董浏览器,它准许http1.1最多开启2个长连接,而http1.0因为默认使用短连接它默认可以开启4个,下面有张图可以说明,如下所示:
提升浏览器加载效率的手段除了提升每个连接的传输效率外,其实还有一种方式,这个方式就是使用多个连接进行并行加载,这个等于几个人联合起来一起完成一个任务,那么效率肯定就比一个人高,而页面加载时候很符合使用并发加载的场景,例如我们让页面里的图片并行加载肯定会比一个个加载图片的效率要高多了。回到浏览器支持的连接数的问题,由于早期浏览器在http1.0和http1.1连接数的差异,某些网站例如维基百科这样的网站,它的静态资源特别多,为了充分发挥并发的优势,它将存放这些静态资源的服务器采用http1.0协议,这样就能并行加载更多的静态资源,因为这个并行加载的总体效率提升相比tcp/ip握手和挥手的损失要高的多,不过现在这个手法已经起不到什么作用了,因为新版的浏览器已经把两种版本的http协议支持的连接数调整一致了,因为长连接可以复用链路,因此使用长连接的效率会比非长连接更好。
上面连接数也是有一个限制的,这个限制就是必须是在同一个域名下,如果一个页面某些静态资源放在不同域名下面,那么这个做法就可以增加页面里的并发数量,例如我们把一些不是经常变化的静态资源例如图片、外部的css文件以及javascript文件单独放置在一个静态资源服务器上,静态资源服务器对外的url地址和页面本身的url地址不在同一个域名下,那么页面本身的并发加载连接数就会增加一倍,不过这也就意味着浏览器端要维护的长连接数会变得更多,雅虎工程师曾经总结过一个页面里合理的域名数量,那就是两个,这个结论的提出已经过去了好多年了,现在的浏览器和服务器的性能已经今非昔比了,这个跨域数量应该可以增加点,不过我个人认为一个页面的里包含的域名数量还是不要太多,其实如果我们web前端优化手段使用得当,两个不同域名就足够用了,多了价值不大,除非你网站情况是在特殊,例如你看看现在浏览器本身支持的连接数量已经很高了,大部分都是6,ie9甚至还达到了10,翻个倍就有12和20个连接数,我们在翻个倍就是24和40个,这个数字看起来就很恐怖了,一个计算机支持这么多并发,假如你在浏览器还打开个网站也是这么干的,那么浏览器的并发数多的实在太吓人了,我估计到时计算机本身就跑不动了,所以10多个连接数很够用了,你合理发挥下这些连接数网站的性能就能有很大提升,再说了一个网站并发连接数太多那本身就说明了你在减少http个数这个手段没有运用好。
回到web前端优化的手段,我们如果把这些手段再仔细分析下就会发现很多手段使用都是在同步请求这个场景下进行了,当然这些手段在合适情况下也能作用于异步加载场景,但是异步加载场景发生并发加载之前需要一个单线程的异步加载,这个单线程的异步加载就和分布式系统里的单点故障有点像了,它很有可能是整个流程的软肋所在,所以合理使用同步请求还能让异步操作性能更加优秀做好准备。上面我讲到浏览器在同一个域名下最多可以开启多少个连接数,但是从事web前端开发的人都能感觉到,我们做页面开发时候其实是没法控制这个连接数的,那么问题来了,这么多连接到底是在什么条件下被开启的呢?这个问题非常有意思的,我们来看下面的瀑布图:
从上面的瀑布图我们发现,并行下载的是图片,这个推而广之要是我们看见某些网站的网页做过并发优化处理的设计,我们就会发现并发的资源都是纯静态的资源,那么这个并发连接数跟我们页面的设计存在一个怎样的关系呢?首先我们总结一下页面里的静态资源,在页面里静态资源有html,如果html里面有内联的css代码和javascript代码,那么这些代码也会归属于html,除了html外还有外部的css文件、外部的javascript文件和页面里使用到的图片,那么这些要素怎样会促发页面的并行加载了,换个说法这些要素又是如何促使浏览器同时打开更多连接呢?
首先我们要明确一个问题,浏览器之所以可以打开更多连接数,让这么多连接并行执行是有个前提的,这个前提就是这些资源是不是被并行加载的,例如像外部css文件,图片这样的资源,这些资源下载完毕后马上就可以使用,因为它们下载完毕后没有逻辑性问题要处理因此下载完毕后就可以直接拿来使用,因此它们并行加载不会影响到页面的展示问题,这个情况如果碰到javascript就有点麻烦了,外部javascript代码是包含逻辑在里面,而且有些逻辑很有可能会影响页面的展示,所以javascript下载完毕后,浏览器就得马上执行,所以我们就会看到这样的瀑布图,如下图所示:
上面的空白区就是浏览器在执行javascript代码所要花费的时间。浏览器开启多少个连接是浏览器自发的行为,这个自发行为主要出于提升浏览器并发下载效率的角度出发的。由于现在浏览器的连接基本都是采取的是http1.1协议,也就是使用的长连接,那么连接建立后这个连接就会长期维护,如果这个长连接是单独的静态资源服务器上的长连接,这个问题倒没什么,如果这个长连接放在主域名下面,问题就来了,主域名在页面初始化加载时候会用来下载html,如果我们为提高并发下载效率,让这个主域名下还放置其他的静态资源,那么可能会导致浏览器和主域名的服务器下维护更多的长连接,而页面后续操作基本是使用ajax来操作的,而ajax往往只会复用其中一个长连接,那么其他多余的长连接等于要空转了,这个空转还需要消耗浏览器和服务器的系统资源,所以我们发现主域名下的请求资源类型一定要认真加以控制,能迁移到单独的静态资源服务器上的一定要进行迁移,尽量让主域名下处理的请求都是包含业务逻辑的请求,这样就可以有效提升系统资源的使用率。这个问题进一步思考下去,我们就会发现如果服务端的业务应用服务器之前放置一个反向代理,反向代理都是使用静态资源服务器,而静态资源服务器对并发的承载能力是远超业务应用服务器,如果主域名下我们不小心放置了太多静态资源,要是后台使用了反向代理,那么反向代理也可以减轻这种长连接所造成的计算资源损失。
上面这些场景都是在浏览器同步请求下进行了,那么换到异步请求这个并行加载静态资源的手段还有效吗?回答这个问题前,我们首先要想想异步加载会导致新的静态资源被加载吗?这个当然可能,特别是在前端MVC的场景下,我们会把模板技术放到浏览器端完成,这个时候有些html模板一开始可能会包含在javascript代码里,作为一个变量存储下来,而这个模板里很有可能包含好多新的图片被使用,当ajax从服务端获取数据后,解析了这个模板,然后我们把构造好的模板加入到页面的DOM结构里,浏览器重新渲染页面时候看到很多新图片需要加载,就有可能会开启多个连接进行并行加载来提升资源加载效率,如果碰到通过ajax技术动态加载外部CSS文件,那么这个并行加载情况就会更加突出了,因为css文件里很有可能包含大量的图片资源,如果我们把不变的静态资源都放置在了单独的静态资源服务器,那么这个并行加载就不会在主域名下打开更多长连接,由此可见,将静态资源使用单独的域名的静态资源服务器处理的好处非常之多。
现在http2.0协议还在起草之中,http2.0如果落地将会给web前端优化技术产生重大影响,http2.0打算在一个页面里只使用一个tcp/Ip连接,不过http2.0会在这个连接上进行链路复用,也就是让一个连接上也能做到并行操作,让连接的利用率更高,如果http2.0落地后,web前端里那些用于减少http连接数的手段都会失去市场了,因为协议本身就能处理好并发的问题了,到时像外部css文件,外部javascript文件,css sprite技术说不定就要成为历史了。
看来本主题又写不完了,下篇接着写吧,今天是元宵节,这里我祝大家节日快乐。