AlexZ33 / lessions

自己练习的各种demo和课程
12 stars 2 forks source link

性能分析与优化 #51

Open AlexZ33 opened 4 years ago

AlexZ33 commented 4 years ago

必读文章

首屏时间从12.67s到1.06s,我是如何做到的?

一文读懂前端缓存

关于前端性能优化一些总结

服务器性能分析与测试

用户追踪

前端性能分析与优化

AlexZ33 commented 4 years ago

指标

image

AlexZ33 commented 4 years ago

使用Chrome开发工具优化Web应用 前端优化不完全指南

AlexZ33 commented 4 years ago

听云network

如下监控指标:

总下载时间:96.758s 首屏时间:96.758s 网络层时间:96.382s DNS时间:0.133s
建立连接时间:0.040s SSL握手时间:0.143s 重定向时间:-- 发出请求时间:--
首包时间:0.041s 内容下载时间:96.389s 总下载字节数:290.974KB 下载速度:3.019KB/s
AlexZ33 commented 4 years ago

网络传输性能优化

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

这是 navigation timing 监测指标图,从图中我们可以看出,浏览器在得到用户请求之后,经历了下面这些阶段:

重定向→拉取缓存→DNS 查询→建立 TCP 链接→发起请求→接收响应→处理 HTML 元素→元素加载完成

1.1 浏览器缓存

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

image

思考一个问题: 浏览器的缓存机制。 关于状态码 , 什么时候将缓存存放在内存(memory)中? 什么时候缓存在硬盘中(disk) 使用 chrome devtools 里的 network 面板查看网络传输的相关信息:

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

image

浏览器默认的缓存是放在内存内的,但我们知道,内存里的缓存会因为进程的结束或者说浏览器的关闭而被清除,而存在硬盘里的缓存才能够被长期保留下去。很多时候,我们在 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 字段,就说明我们的缓存配置成功了。 image

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

1.2 资源打包压缩

我们之前所作的浏览器缓存工作,只有在用户第二次访问我们的页面才能起到效果,如果要在用户首次打开页面就实现优良的性能,必须对资源进行优化。我们常将网络性能优化措施归结为三大方面:

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

image

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

1.JS 压缩:

optimization: {
        minimizer: [
            new UglifyJsPlugin({
                cache: true,
                parallel: true,
                sourceMap: true // set to true if you want JS source maps
            }),
            ...Plugins
        ]
    }

2.HTML 压缩:

new HtmlWebpackPlugin({
            template: __dirname + '/views/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 文件时,很少会为其添加配置项,这里我给出样例,大家直接复制就行。据悉,在 Webpack5 中,html-webpack-plugin的功能会像common-chunk-plugin那样,被集成到 webpack 内部,这样我们就不需要再 install 额外的插件了。

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

3. 提取公共资源:

splitChunks: {
      cacheGroups: {
        vendor: { // 抽离第三方插件
          test: /node_modules/, // 指定是 node_modules 下的第三方包
          chunks: 'initial',
          name: 'common/vendor', // 打包后的文件名,任意命名    
          priority: 10 // 设置优先级,防止和自定义的公共代码提取时被覆盖,不进行打包
        },
        utils: { // 抽离自定义公共代码
          test: /\.js$/,
          chunks: 'initial',
          name: 'common/utils',
          minSize: 0 // 只要超出 0 字节就生成一个新包
        }
      }
    }

4. 提取 css 并压缩:

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

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

const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module: {
        rules: [..., {
            test: /\.css$/,
            exclude: /node_modules/,
            use: [
                _mode === 'development' ? 'style-loader' : MiniCssExtractPlugin.loader, {
                    loader: 'css-loader',
                    options: {
                        importLoaders: 1
                    }
                }, {
                    loader: 'postcss-loader',
                    options: {
                        ident: 'postcss'
                    }
                }
            ]
        }]
    }

我这里配置预处理器 postcss,但是我把相关配置提取到了单独的文件 postcss.config.js 里了,其中 cssnano 是一款很不错的 CSS 优化插件。

5. 将 webpack 开发环境修改为生产环境:

在使用 webpack 打包项目时,它常常会引入一些调试代码,以作相关调试,我们在上线时不需要这部分内容,通过配置剔除:

devtool: 'false'

如果你能按照上述六点将 webpack 上线配置完整配置出来,基本能将文件资源体积压缩到极致了

最后,我们还应该在服务器上开启 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 压缩! 我只会告诉你效果适得其反,至于具体原因,还得考虑服务器压缩过程中的 CPU 占用还有压缩率等指标,对图片进行压缩不但会占用后台大量资源,压缩效果其实并不可观,可以说是“弊大于利”,所以请在gzip_types 把图片的相关项去掉。针对图片的相关处理,我们接下来会更加具体地介绍。

1.3 图片资源优化

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

1.3.1 不要在 HTML 里缩放图像

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

1.3.2 使用雪碧图(CSS Sprite)

雪碧图的概念大家一定在生活中经常听见,其实雪碧图是减小请求数的显著运用。而且很奇妙的是,多张图片聘在一块后,总体积会比之前所有图片的体积之和小(你可以亲自试试)。这里给大家推荐一个自动化生成雪碧图的工具:https://www.toptal.com/developers/css/sprite-generator(图片来自官网首页

image

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

其实我们在工程中还有更为自动的方法,便是一款雪碧图生成插件webpack-spritesmith。首先,先简单介绍一下使用插件生成雪碧图的思路:

首先,我们会把我们所需要的小图标放置在一个文件夹内以便于管理:

(这里的 @2x 图片是为了适配视网膜二倍屏的图片资源,webpack-spritesmith内有专门为适配多倍屏提供的配置项,稍候将会讲到)

image

然后,我们需要插件去读取这个文件夹内的所有图片资源文件,以文件夹名称为图片名称生成一张雪碧图到指定位置,并且输出能够正确使用这些雪碧图的 CSS 文件。 image

image

如今,webpack-spritesmith这款插件能实现我们想要的一切,先奉上配置内容:

image

具体可参照webpack-spritesmith官方文档: https://www.npmjs.com/package/webpack-spritesmith

执行 webpack 之后,就会在开发目录里生成上面两张图的结果,我们可以看看common.css里面的内容:

image

我们可以看到,所有我们之前放在 common 文件夹里的图片资源都自动地生成了相应的样式,这些都不需要我们手动处理,webpack-spritesmith这款插件就已经帮我们完成了!

1.3.3 使用字体图标(iconfont)

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

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

image

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

但如果我们做的是公司或者团队的项目,需要使用到许多自定义的字体图标,可爱的设计小姐姐们只是丢给你了几份.svg图片,你又该如何去做呢?

其实也很简单,阿里矢量图标库就提供了上传本地 SVG 资源的功能,这里另外推荐一个网站——icomoon。icomoon 这个网站也为我们提供了将 SVG 图片自动转化成 CSS 样式的功能。(图片来自 icomoon 首页)

image

我们可以点击 Import Icons 按钮导入我们本地的 SVG 资源,然后选中他们,接下来生成 CSS 的事情,就交给 icomoon 吧,具体的操作,就和阿里矢量图标库类同了

1.3.4 使用 WebP

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

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

image

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

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

(PS:使用 chrome 应用商店需要翻墙,怎么翻墙我就不便多说了)

这就是 Page Speed 的功能界面:

image

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

image

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 的,不过我们可以使用各大企业提供的服务,诸如腾讯云等,配置也十分简单,这里就请大家自行去推敲啦。