• 精选
  • 会员

1. 网络传输性能优化

2018年12月11日  来源:程序猿猩球 作者: 提供人:lilitaoger0......

在开始介绍网络传输性能优化这项工作之前,我们需要了解浏览器处理用户请求的过程,那么就必须奉上这幅神图了:

网站性能优化实战——从12.67s到1.06s的故事

这是navigation timing监测指标图,从图中我们可以看出,浏览器在得到用户请求之后,经历了下面这些阶段:重定向→拉取缓存→DNS查询→建立TCP链接→发起请求→接收响应→处理HTML元素→元素加载完成。不着急,我们将对其中的细节一步步展开讨论:

1.1.浏览器缓存

我们都知道,浏览器在向服务器发起请求前,会先查询本地是否有相同的文件,如果有,就会直接拉取本地缓存,这和我们在后台部属的Redis、Memcache类似,都是起到了中间缓冲的作用,我们先看看浏览器处理缓存的策略:

网站性能优化实战——从12.67s到1.06s的故事

因为网上的图片太笼统了,而且我翻过很多讲缓存的文章,很少有将状态码还有什么时候将缓存存放在内存(memory)中什么时候将缓存在硬盘中(disk)系统地整理出来,所以我自己绘制了一张浏览器缓存机制流程图,结合这张图再更深入地说明浏览器的缓存机制。

这里我们可以使用chrome devtools里的network面板查看网络传输的相关信息:

(这里需要特别注意,在我们进行缓存调试时,需要去除network面板顶部的Disable cache 勾选项,否则浏览器将始终不会从缓存中拉取数据)

网站性能优化实战——从12.67s到1.06s的故事

浏览器默认的缓存是放在内存内的,但我们知道,内存里的缓存会因为进程的结束或者说浏览器的关闭而被清除,而存在硬盘里的缓存才能够被长期保留下去。很多时候,我们在network面板中各请求的size项里,会看到两种不同的状态:from memory cache 和 from disk cache,前者指缓存来自内存,后者指缓存来自硬盘。而控制缓存存放位置的,不是别人,就是我们在服务器上设置的Etag字段。在浏览器接收到服务器响应后,会检测响应头部(Header),如果有Etag字段,那么浏览器就会将本次缓存写入硬盘中。

之所以拉取缓存会出现200、304两种不同的状态码,取决于浏览器是否有向服务器发起验证请求。 只有向服务器发起验证请求并确认缓存未被更新,才会返回304状态码。

这里我以nginx为例,谈谈如何配置缓存:

首先,我们先进入nginx的配置文档

$ vim nginxPath/conf/nginx.conf

在配置文档内插入如下两项:

etag on; //开启etag验证

expires 7d; //设置缓存过期时间为7天

打开我们的网站,在chrome devtools的network面板中观察我们的请求资源,如果在响应头部看见Etag和Expires字段,就说明我们的缓存配置成功了。

网站性能优化实战——从12.67s到1.06s的故事

【!!!特别注意!!!】在我们配置缓存时一定要切记,浏览器在处理用户请求时,如果命中强缓存,浏览器会直接拉取本地缓存,不会与服务器发生任何通信,也就是说,如果我们在服务器端更新了文件,并不会被浏览器得知,就无法替换失效的缓存。所以我们在构建阶段,需要为我们的静态资源添加md5 hash后缀,避免资源更新而引起的前后端文件无法同步的问题。

1.2.资源打包压缩

我们之前所作的浏览器缓存工作,只有在用户第二次访问我们的页面才能起到效果,如果要在用户首次打开页面就实现优良的性能,必须对资源进行优化。我们常将网络性能优化措施归结为三大方面:减少请求数、减小请求资源体积、提升网络传输速率。现在,让我们逐个击破:

结合前端工程化思想,我们在对上线文件进行自动化打包编译时,通常都需要打包工具的协助,这里我推荐webpack,我通常都使用Gulp和Grunt来编译node,Parcel太新,而且webpack也一直在自身的特性上向Parcel靠拢。

网站性能优化实战——从12.67s到1.06s的故事

在对webpack进行上线配置时,我们要特别注意以下几点:

①JS压缩:(这点应该算是耳熟能详了,就不多介绍了)

new webpack.optimize.UglifyJsPlugin()

②HTML压缩:

new HtmlWebpackPlugin({ template: __dirname + 'iews/index.html', // new 一个这个插件的实例,并传入相关的参数 

filename: '../index.html', minify: { removeComments: true, collapseWhitespace: true, removeRedundantAttributes: true, useShortDoctype: true, removeEmptyAttributes: true, removeStyleLinkTypeAttributes: true, keepClosingSlash: true, minifyJS: true, minifyCSS: true, minifyURLs: true, }, chunksSortMode: 'dependency' })

我们在使用html-webpack-plugin 自动化注入JS、CSS打包HTML文件时,很少会为其添加配置项,这里我给出样例,大家直接复制就行。

PS:这里有一个技巧,在我们书写HTML元素的src 或 href 属性时,可以省略协议部分,这样也能简单起到节省资源的目的。

③提取公共资源:

new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', filename: 'scripts/commonendor-[hash:5].js' })

PS:这里是webpack3的语法,在webpack4中已作更改,希望大家注意

④提取css并压缩:

在使用webpack的过程中,我们通常会以模块的形式引入css文件(webpack的思想不就是万物皆模块嘛),但是在上线的时候,我们还需要将这些css提取出来,并且压缩,这些看似复杂的过程只需要简单的几行配置就行:

(PS:我们需要用到extract-text-webpack-plugin ,所以还得大家自行npm install)

const ExtractTextPlugin = require('extract-text-webpack-plugin')module: { rules: [..., { test: /.css$/, use: ExtractTextPlugin.extract({ fallback: 'style-loader', use: { loader: 'css-loader', options: { minimize: true } } }) }] }

⑤使用webpack3的新特性:ModuleConcatenationPlugin

new webpack.optimize.ModuleConcatenationPlugin()

如果你能按照上述五点将webpack上线配置完整配置出来,基本能将文件资源体积压缩到极致了,如有疏漏,还希望大家能加以补充。

给大家上一份我的webpack上线配置文档,欢迎参考:

//webpack.pro.jsconst webpack = require('webpack')const HtmlWebpackPlugin = require('html-webpack-plugin')const ExtractTextPlugin = require('extract-text-webpack-plugin')const CleanWebpackPlugin = require('clean-webpack-plugin')const CopyWebpackPlugin = require('copy-webpack-plugin')module.exports = { entry: __dirname + '/public/scripts/index.js', output: { path: __dirname + '/build/static', // 打包后的文件存放的地方 filename: 'scripts/[name]-[hash:5].js' // 打包后输出文件的文件名,带有md5 hash戳 }, resolve: { extensions: ['.jsx', '.js'] }, module: { rules: [{ test: /(.jsx|.js)$/, use: { loader: 'babel-loader' }, exclude: /node_modules/ // 不进行编译的目录 }, { test: /.css$/, use: ExtractTextPlugin.extract({ fallback: 'style-loader', use: { loader: 'css-loader', options: { minimize: true } } }) }] }, plugins: [ new HtmlWebpackPlugin({ template: __dirname + '/views/index.html', filename: '../index.html', minify: { removeComments: true, collapseWhitespace: true, removeRedundantAttributes: true, useShortDoctype: true, removeEmptyAttributes: true, removeStyleLinkTypeAttributes: true, keepClosingSlash: true, minifyJS: true, minifyCSS: true, minifyURLs: true, }, chunksSortMode: 'dependency' }), new ExtractTextPlugin('styles/style-[hash:5].css'), new CleanWebpackPlugin('build/*', { root: __dirname, verbose: true, dry: false }), new webpack.optimize.UglifyJsPlugin(), new CopyWebpackPlugin([{ from: __dirname + '/public/images', to: __dirname + '/build/static/images' }, { from: __dirname + '/public/scripts/vector.js', to: __dirname + '/build/static/scripts/vector.js' }]), new webpack.optimize.ModuleConcatenationPlugin(), new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', filename: 'scripts/common/vendor-[hash:5].js' }) ]}

最后,我们还应该在服务器上开启Gzip传输压缩,它能将我们的文本类文件体积压缩至原先的四分之一,效果立竿见影,还是切换到我们的nginx配置文档,添加如下两项配置项目:

gzip on;gzip_types text/plain application/javascriptapplication/x-javascripttext/css application/xml text/javascriptapplication/x-httpd-php application/vnd.ms-fontobject font/ttf font/opentype font/x-woff image/svg+xml;

如果你在网站请求的响应头里看到这样的字段,那么就说明咱们的Gzip压缩配置成功啦:

网站性能优化实战——从12.67s到1.06s的故事

【!!!特别注意!!!】不要对图片文件进行Gzip压缩!不要对图片文件进行Gzip压缩!不要对图片文件进行Gzip压缩!我只会告诉你效果适得其反,至于具体原因,还得考虑服务器压缩过程中的CPU占用还有压缩率等指标,对图片进行压缩不但会占用后台大量资源,压缩效果其实并不可观,可以说是“弊大于利”,所以请在gzip_types 把图片的相关项去掉。针对图片的相关处理,我们接下来会更加具体地介绍。

1.3.图片资源优化

刚刚我们介绍了资源打包压缩,只是停留在了代码层面,而在我们实际开发中,真正占用了大量网络传输资源的,并不是这些文件,而是图片,如果你对图片进行了优化工作,你能立刻看见明显的效果。

1.3.1.不要在HTML里缩放图像

很多开发者可能会有这样的错觉(其实我曾经也是这样),比如我们会为了方便在一个200?200的图片容器内直接使用一张400?400的图片,我们甚至认为这样能让用户觉得图片更加清晰,其实不然,在普通的显示器上,用户并不会感到缩放后的大图更加清晰,但这一切却导致网页加速速度下降,同时照成带宽浪费,你可能不知道,一张200KB的图片和2M的图片的传输时间会是200ms和12s的差距(亲身经历,深受其害(┬_┬))。所以,当你需要用多大的图片时,就在服务器上准备好多大的图片,尽量固定图片尺寸。

1.3.2.使用雪碧图(CSS Sprite)

雪碧图的概念大家一定在开发中经常听见,其实雪碧图是减小请求数的示范性代表。而且很奇妙的是,多张图片拼在一块后,总体积会比之前所有图片的体积之和小(你可以亲自试试)。这里给大家推荐一个自动化生成雪碧图的工具:www.toptal.com/developers/… (图片来自官网首页)

网站性能优化实战——从12.67s到1.06s的故事

只要你添加相关资源文件,他就会自动帮你生成雪碧图以及对应的CSS样式,你要做的,只是download和copy。

1.3.3.使用字体图标(iconfont)

不论是压缩后的图片,还是雪碧图,终归还是图片,只要是图片,就还是会占用大量网络传输资源。但是字体图标的出现,却让前端开发者看到了另外一个神奇的世界。

我最喜欢用的是阿里矢量图标库(网址:www.iconfont.cn/ ) ,里面有大量的矢量图资源,而且你只需要像在淘宝采购一样把他们添加至购物车就能把它们带回家,整理完资源后还能自动生成CDN链接,可以说是完美的一条龙服务了。(图片来自官网首页)

网站性能优化实战——从12.67s到1.06s的故事

图片能做的很多事情,矢量图都能作,而且它只是往HTML里插入字符和CSS样式而已,和图片请求比起来,在网络传输资源的占用上它们完全不在一个数量级,如果你的项目里有大量的小图标,就用矢量图吧。

1.3.4.使用WebP

WebP格式,是谷歌公司开发的一种旨在加快图片加载速度的图片格式。图片压缩体积大约只有JPEG的2/3,并能节省大量的服务器带宽资源和数据空间。Facebook、Ebay等知名网站已经开始测试并使用WebP格式。

我们可以使用官网提供的Linux命令行工具对项目中的图片进行WebP编码,也可以使用我们的线上服务,这里我推荐叉拍云(网址:www.upyun.com/webp )。但是在实际的上线工作中,我们还是得编写Shell脚本使用命令行工具进行批量编码,不过测试阶段我们用线上服务就足够了,方便快捷。(图片来自叉拍云官网)

网站性能优化实战——从12.67s到1.06s的故事

1.4.网络传输性能检测工具——Page Speed

除了network版块,其实chrome还为我们准备好了一款监测网络传输性能的插件——Page Speed,咱们的文章封面,就是用的Page Speed的官方宣传图(因为我觉得这张图再合适不过了)。我们只需要通过下面步骤安装,就可以在chrome devtools里找到它了:chrome菜单→更多工具→拓展程序→chrome网上应用商店→搜索pagespeed后安转即可。(PS:使用chrome应用商店需要翻墙,怎么翻墙我就不便多说了)

这就是Page Speed的功能界面:

网站性能优化实战——从12.67s到1.06s的故事

我们只需要打开待测试的网页,然后点击Page Speed里的 Start analyzing按钮,它就会自动帮我们测试网络传输性能了,这是我的网站测试结果:

网站性能优化实战——从12.67s到1.06s的故事

Page Speed最人性化的地方,便是它会对测试网站的性能瓶颈提出完整的建议,我们可以根据它的提示进行优化工作。这里我的网站已经优化到最好指标了(??????)??,Page Speed Score表示你的性能测试得分,100/100表示已经没有需要优化的地方。

优化完毕后再使用chorme devtools的network版块测量一下我们网页的白屏时间还有首屏时间,是不是得到了很大的提升?

1.5.使用CDN

Last but not least,

再好的性能优化实例,也必须在CDN的支撑下才能到达极致。

如果我们在Linux下使用命令$ traceroute targetIp 或者在Windows下使用批处理 > tracert targetIp,都可以定位用户与目标计算机之间经过的所有路由器,不言而喻,用户和服务器之间距离越远,经过的路由器越多,延迟也就越高。使用CDN的目的之一便是解决这一问题,当然不仅仅如此,CDN还可以分担IDC压力。

当然,凭着我们单个人的资金实力(除非你是王思聪)是必定搭建不起来CDN的,不过我们可以使用各大企业提供的服务,诸如腾讯云等,配置也十分简单,这里就请大家自行去推敲啦。

其实我们的CDN域名一般是和我们的网站主域名不同的,大家可以看看淘宝、腾讯的官方网站,看看他们存放静态资源的CDN域名,都是和主域名不一样的。为什么要这么做?主要有两个原因:[内容摘选自:bbs.aliyun.com/simple/t116… ]

①便于CDN业务独立,能够独立配置缓存。为了降低web压力,CDN系统会遵循Cache-Control和Expires HTTP头标准对改请求返回的内容进行缓存,便于后面的请求不在回源,起到加速功能。而传统CDN(Web与CDN共用域名)的方式,需要对不同类型的文件设置相应的Cache规则或者遵循后端的HTTP头,但这样难以发挥CDN的最大优势,因为动态请求回源的概率非常之大,如果访客与源站的线路并不慢,通过CDN的请求未必快于直接请求源站的。 大型网站为了提升web性能到极致,通常缓存头设置比较大,像谷歌JS设置一年缓存,百度首页logo设置十年缓存,如果将静态元素抽取出来,就可以很方便的对所有静态元素部署规则,而不用考虑动态请求。减少规则的条数可以提升CDN的效率。

②抛开无用cookie,减小带宽占用。我们都知道HTTP协议每次发送请求都会自动带上该域名及父级域名下的cookie,但对于CSS,JS还有图片资源,这些cookie是没用的,反而会浪费访客带宽和服务器入带宽。而我们的主站,为了保持会话或者做其他缓存,都会存放着大量的cookie,所以如果将CDN与主站域名分离,就能解决这一问题。

不过这样一来,新的问题就出现了:CDN域名与主站域名不同,DNS解析CDN域名还需要花费额外的时间,增加网络延迟。不过这难不住我们伟大的程序员前辈,DNS Prefetch闪亮登场。

如果大家翻看大型网站的HTML源代码,都会在头部发现这样的link链接:(这里以淘宝首页为例)

网站性能优化实战——从12.67s到1.06s的故事

这就是DNS Prefetch。DNS Prefetch是一种DNS预解析技术,当我们浏览网页时,浏览器会在加载网页时对网页中的域名进行预解析并缓存,这样在浏览器加载网页中的链接时,就无需进行DNS解析,减少用户的等待时间,提高用户体验。DNS Prefetch现已被主流浏览器支持,大多数浏览器针对DNS解析都进行了优化,典型的一次DNS解析会耗费20~120ms,减少DNS解析时间和次数是个很好的优化措施。这里附上一张Can I use it官网上的DNS Prefetch支持情况图:

网站性能优化实战——从12.67s到1.06s的故事

缓存 / 压缩 / 提取公共资源

如涉及版权,请著作权人与本网站联系,删除或支付费用事宜。

0000