文章目录
  1. 1. 课程目标
  2. 2. 课程复盘
    1. 2.1. 1. 网站效能基础概念
    2. 2.2. 2. 前端效能分析
    3. 2.3. 3. 减少 Requests 数量和大小
      1. 2.3.1. 本机如何跑 production 模式?
    4. 2.4. 4. HTTP 最佳化
      1. 2.4.1. HTTP 压缩
      2. 2.4.2. HTTP 缓存
      3. 2.4.3. HTTP/2
    5. 2.5. 5. 关键渲染路径
      1. 2.5.1. 如何量测?
      2. 2.5.2. JavaScript 加载最佳化

课程目标

  • Part 1: 前言
    • 网站效能基础概念
  • Part 2: 前端效能
    • 前端效能分析
    • 减少 Requests 数量和大小
    • HTTP 最佳化
    • 关键渲染路径
    • CDN
  • Part 3: 后端效能
    • 项目准备
    • 后端效能分析
    • 避免 N+1 SQL 查询
    • ActiveRecord 优化技巧
    • 数据库 SQL 优化
    • 计数缓存 (Counter Cache)
    • 改进 Render Partial 的效能
    • 数据库索引
    • 后端缓存和延展性(Scalability)

课程复盘

1. 网站效能基础概念

  • KISSmetric 研究指出,网页的加载时间超过4秒,网页的跳出率就会增加 25%。
  • 网站效能也会影响 SEO,搜寻引擎 Google 会针对慢速的网站降低权重。
  • 顺道一提,没加密的 HTTP 网站也会降低 SEO 权重,正式上线的网站建议要上 HTTPS 安全连线

网站效能可以分成3个部分来看:

  • 后端效能,也就是网站服务器运行代码(Ruby/Rails/数据库)所需的时间
  • 前端效能,包括网络传输的时间,以及浏览器运行代码(HTML/CSS/Javascript)渲染出画面所需的时间
  • 网络传输,网络传输时间取决用户距离服务器的距离(光速和光纤的物理限制),以及用户上网的品质。

本机开发时,Rails 是 development 模式,效能会比较差。部署在服务器上会用 production 模式,效能才会好的。这是因为本机开发时会一直改代码,所以每次 Request 都会重新编译过修改的代码。

  • 前端效能的优化,就是希望用户尽量只下载必要的资源、尽量提早让用户看到画面,而不需要等待全部的资源都下载执行完成才能看到画面。

  • 后端效能的 Response Time 通常是毫秒等级来看的,基本上如果不犯错,就算不做什么优化,也可以在 250ms 左右完成,如果真的数据量或计算量很大,需要到几秒以上,这时候也应该用异步处理

  • 因此在后端效能还OK的情况下,我们会更着重要前端效能的改进。前端效能的 Page Load Time 的是几秒等级

2. 前端效能分析

科学化量测前端效能的工具:

  • Chrome 除错器提供了详尽的报表,最基本我们可以观察 Network,其中的 Load 就是 Page Load Time 页面加载时间。

  • Chrome 也提供了进阶的 Performance 详细报告,包括解析 HTML、CSS、JavaScript 各花了多少时间

  • Google 也提供了一些分析工具,并提供分数报告——线上分析工具 PageSpeed Insights

  • PageSpeed Insights 的评分标准,可以作为前端性能优化的参考。

  • Chrome 外挂 Lighthouse

  • 由于开发者的网络线速度通常蛮快的,不能反应实际用户的连线情况,特别是用手机行动上网的情境,因此 Chrome 有提供模拟限速的功能。在截图中的 No throttling 的地方,我们可以将速度模拟成行动 3G 网络。另外在测试的时候,也需要把浏览器的缓存功能关闭。

3. 减少 Requests 数量和大小

要完整加载一个网页,除了 HTML 之外,还会需要加载 CSS、JavaScript 和图片等资源。每一次的 HTTP Request 都需要耗费时间,因此减少 Requests 的请求数量,以及减少这些档案的大小,就是一个最佳化的方向。

Rails 在本机开发时,CSS 和 JavaScript 是分开加载的:

在部署上 Production 环境时,Asset Pipeline 会执行 rake assets:precompile 将 CSS 和 Javascript 进行合并压缩,变成这个样子:

CSS 压缩是透过 sass-rails gem,而 JavaScript 压缩是透过 uglifier gem

本机如何跑 production 模式?

也因为这个原因,在本机用 development 模式进行前端效能测试是不准的。如果想在本机跑 production 模式的话,可以这样做:

  1. 修改 config/database.yml 设定 production 模式要用哪一个数据库,例如把 production 改成用 development 模式的数据库,例如
1
2
3
4
5
6
7
8
development:
<<: *default
database: db/development.sqlite3
production:
<<: *default
- database: db/production.sqlite3
+ database: db/development.sqlite3
  1. 执行 bundle exec rake assets:precompile,这会在本机执行 Asset Pipleline 的编译
  2. 暂时修改 config/environments/production.rb 打开 public_file_server,这会允许 Rails 服务器可以回传静态档案(在正式部署的服务器上,静态档案会由 Nginx 处理,所以默认是关闭的)
config/environments/production.rb
1
2
- config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
+ config.public_file_server.enabled = true
  1. 改用 rails s -e production 来启动 Rails,就会是用 production 模式
  2. Asset Pipleline 的编译出来的档案放在 public/assets 下,这些档案是不需要 commit 的,实验完之后需要砍掉。执行 bundle exec rake assets:clobber 或 rm -rf public/assets 可以砍掉这个目录。

不过在 production 模式下,修改任何 Ruby 代码,都需要重启 Rails。修改任何前端代码,都需要重新编译 Asset Pipleline,是很麻烦的。

4. HTTP 最佳化

HTTP 通讯协议本身,也有一些可以提速的功能:

HTTP 压缩

网站服务器和浏览器之间,支援了 gzip 压缩,在部署教程中,就有设定 Nginx 打开这个功能:

1
2
3
4
5
6
7
8
gzip on;
gzip_disable "msie6";
gzip_comp_level 5;
gzip_min_length 256;
gzip_proxied any;
gzip_vary on;
gzip_types application/atom+xml application/javascript application/x-javascript application/json application/rss+xml application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/svg+xml image/x-icon text/css text/xml text/plain text/javascript text/x-component;

一旦启用这个功能,Nginx 就会针对这些纯文字的档案,进行 gzip 压缩之后再传给浏览器,在 HTTP Response 标头上会标明这个有 gzip 压缩,那么浏览器就会知道要进行解压缩


档案有没有压缩差别很大,截图中 JavaScript 压缩前是 1.1 MB,压缩后是 322KB,CSS 压缩前是 142KB,压缩后是 22.7KB。所以这个功能务必在服务器上要开启。

图片不需要 gzip 压缩,因为图片本身已经是压缩过后的格式(PNG或JPG)

HTTP 缓存

在 HTTP 标头中,也有定义一些缓存功能。我们之前在部署教程中,在 Nginx 服务器有这样的设定:

1
2
3
4
5
6
location ~ ^/assets/ {
expires 1y;
add_header Cache-Control public;
add_header ETag "";
break;
}

这会针对 /assets/ 目录下的静态档案,在 HTTP Response 加入以下的 Headers:

  • cache-control: public 指定这个资源是可以被浏览器缓存
  • cache-control: max-age=31536000 和 expire 告诉浏览器这个资源可以被缓存多久

于是浏览器就知道这些资源可以被缓存,当你重新整理页面,或是浏览下一页的时候:

这些资源就从浏览器的缓存拿出来,而不需要发出 HTTP Requests 到服务器。

为什么可以将缓存时间拉到一年这么久呢? 这是因为 Rails asset pipleline 的 digest 功能会修改档名加上编码。每次部署上线进行 rake assets:precompile 编译时,如果 CSS/JS/图片 内容有变动的话,那这个档名就会变,浏览器就会下载新的档案。因此我们可以放心告诉浏览器这些资源可以被缓存,而不用担心用户继续使用旧的档案。

HTTP/2

HTTP/2 是 2015 年制定的最新 HTTP 标准,语意和功能都与 HTTP 1.1 是相容的,主要是改善加载网页的效能,详细请参考ihower老师之前写的一篇文章 更快更安全: 每个网站都应该升级到 HTTP/2

HTTP/2 在图片很多的场景,使用 HTTP/2 会改善非常多。不过 HTTP/2 要求 HTTPS,这是部署上会比较麻烦的地方。

HTTPS 安全宣导:自 2017 年 10 月起,如果用户使用 Chrome (62版) 时,在使用 HTTP 的网页表单中输入文字,Chrome 将显示「不安全」警告

5. 关键渲染路径

除了网页加载时间(Page Load Time)之外,另一个前端效能注重的数据是首次渲染页面的时间

浏览器渲染画面(Browser Rendering)的过程:

  • 浏览器下载 HTML
  • 浏览器针对 HTML 上的外部资源(CSS/JavaScript/图片)发送 HTTP Requests 去获取,在 HTTP 1.1 时代只能同时抓取六个资源、在 HTTP/2 之后则可以同时平行抓取。
  • 同一时间浏览器也从 HTML 源码上到下开始解析进行渲染:
    • 用 HTML 建立 Document Object Model (DOM)
    • 用 CSS 建立 CSS Object Model (CSSOM),如果 CSS 还没下载完成,会等待
    • 执行 JavaScript 来操作 DOM 和 CSSOM,如果 JavaScript 还没下载完成,会等待
    • 建立 Render Tree
    • 计算每个元素在画面上的位置 Layout
    • 实际画上(Paint)每一画素(pixels)

这个过程其实可以是一个渐进的过程,我们希望尽快让用户看到有个画面,也就是去缩短首次渲染页面的时间,而不是完全的空白画面。因此希望找出所谓的关键渲染路径(Critical Rendering Path):

这张图出自 Google 文档,上图是经过关键渲染路径优化的版本、下图是没有经过优化的版本。你可以看到经过优化的版本,用户可以更早就看到画面,在感受上可以更早就开始阅读和操作网页。

这要怎么办到呢? 关键就在思考首次渲染页面时,只加载必要的 CSS,以及延后 JavaScript 加载。这是因为显示画面只需要 HTML/CSS,暂时不需要 JavaScript

如何量测?

用 Chrome 除错器的 Performance 功能,可以观察加载的看时间轴:

首先限速一下,这样比较好观察效果:

点到 Performance tab,打开 Screenshots,按下重新整理就会开始量测:

这是怎么办到的,最重要的技巧是延后 JavaScript 的加载

JavaScript 加载最佳化

JavaScript 的加载对浏览器来说是 rendering blocking 的,当浏览器在 里面看到

文章目录
  1. 1. 课程目标
  2. 2. 课程复盘
    1. 2.1. 1. 网站效能基础概念
    2. 2.2. 2. 前端效能分析
    3. 2.3. 3. 减少 Requests 数量和大小
      1. 2.3.1. 本机如何跑 production 模式?
    4. 2.4. 4. HTTP 最佳化
      1. 2.4.1. HTTP 压缩
      2. 2.4.2. HTTP 缓存
      3. 2.4.3. HTTP/2
    5. 2.5. 5. 关键渲染路径
      1. 2.5.1. 如何量测?
      2. 2.5.2. JavaScript 加载最佳化