Skip to content

22 加载速度: 提升网页首字节时间的问题

你好,我是三桥。

TTFB指标的优化并不是在性能出现问题时才需要考虑。想要提升用户体验,我们就要考虑用更少的时间触达用户,让用户更快地访问网页。

优化TTFB指标并没有统一的优化标准和优化方案。不过我可以给你提供4个方向的优化建议,你可以根据实际情况选择最合适的策略。

这四个优化方向包括通用方案、体验优化、服务器处理和资源缓存。

通用方案

优化TTFB指标的通用方案包括三种,分别是CDN加速、采用HTTP2协议、优化资源大小。

  • 使用CDN节点加速TTFB

我们在上节课已经多次提到了关于采用CDN缓存前端资源,包括HTML主文档。

CDN是一种分布式网络系统,能够加速互联网上内容的传输和交付。在全球部署部署大量的服务器节点,并将网站的静态资源缓存到这些节点上,用户就能够更快、更可靠地访问网页了。

因此,采用CDN缓存加速是最常见、最普遍的方案,也是所有前端项目必须要执行的策略。你可以通过下图来了解CDN的功能。

图片

从图中我们可以看到,当用户访问前端应用时,首先会访问到离他们最近的CDN节点。如果CDN节点已经缓存前端应用资源,它将直接响应用户。如果没有,就从源服务器请求资源并将其缓存。

CDN节点的缓存机制默认是被动的,只有用户主动触发时,CDN节点才会缓存资源。这种被动机制对SPA单应用来说,会对更新机制带来一定的影响。

什么意思呢?我们知道,SPA应用的版本更新主要是JavaScript和CSS文件的更新。每次版本发布只是更新源服务器的节点,而CDN节点的更新需要时间。在此期间,如果用户仍在使用旧版本的前端资源,他们访问的功能就有可能会出问题。

解决这个问题的有一个方法是利用CDN服务商提供的主动更新能力。每次发布新版本后,我们可以通过某种方式主动发送请求,通知CDN节点更新前端资源。这样,就能快速地将最新版本资源文件提供给用户。

下面的流程图展示了被动缓存和主动缓存的过程。

图片

  1. 采用HTTP2协议

HTTP2是HTTP协议的关键版本,目的是提升网站性能和用户体验,它的主要特点是多路复用,这也是常见的前端面试题之一。

多路复用的主要功能是允许浏览器在单个TCP链接上同时发送多个请求和响应。这解决了HTTP1.x时代的头阻塞问题,提高了资源请求的并发性能。

使用HTTP2协议可以加快前端资源的访问速度,尤其对SPA单页应用来说,非常有效。

通常,大型前端应用都会采用HTTP2来保证访问速度。但实际上,我发现很多前端应用并未开启HTTP2协议,这也是导致用户访问页面时经常出现长时间等待状态的原因之一。

由于HTTP2涉及的网络配置和服务配置较多,前端同学可能无法完全把控启动该协议,需要和运维同学或服务器维护同学一起配合才能完成。因此,我们就不再展开该话题。

  1. 优化前端资源大小

前端同学都知道,前端资源越小,访问速度越快。因此,优化策略主要是采用Gzip压缩文件和JavaScript文件拆分和动态加载两种。

关于这方面的优化有两点需要注意。

首先,我遇到过设计师提供的素材图片,其中有几张超过了5M,如果这些图片被用作网页的可视区域,就会出现非常慢的加载速度。

因此,前端同学在开发时需要与设计师保持良好的沟通,约定好素材的大小限制,确保资源的可用性。

其次,有些资源是通过后台配置的,如运营图片、音频、视频。如果后台没有做好大小限制,运营同学可能会不小心上传了超大的图片文件,那么就可能引发请求时间的阻塞。

总的来说,控制好前端资源文件的大小,可以有效地提升用户体验。

体验优化

在TTFB指标中,有两个策略能提升用户体验,分别是使用Web Worker、预解析和预连接。

  1. 采用Web Worker

如果我们无法避免请求过多或耗时过长,那么主要原因通常是用户的网络环境和服务器之间的距离过长。要解决这类问题,就需要从避免因为时间长而阻塞进程的方向去思考方案。其中,Web Worker就是其中一种优化策略。

Web Worker是在浏览器中运行JavaScript代码的后台线程,它的执行耗时操作不会阻塞主线程。不过,Web Worker本身不支持直接进行网络请求,只能通过与主线程的通信来实现子进程接口请求。

例如以下参考代码。

// 主线程代码

// 创建 Web Worker
const worker = new Worker('worker.js');

// 向 Web Worker 发送消息
worker.postMessage('https://time.geekbang.org/serv/v1/column/articles');

// 接收来自 Web Worker 的消息
worker.onmessage = function(event) {
  console.log('Received data from Web Worker:', event.data);
};

// worker.js(Web Worker文件)
self.addEventListener('message', function(event) {
  const url = event.data;

  fetch(url)
    .then(response => response.json())
    .then(data => {
        // 将获取的数据发送回主线程
      self.postMessage(data);
    })
    .catch(err => {
      console.error('Error fetching data:', err);
    });
});

在上面的代码示例中,主线程通过创建Web Woker对象,并发送消息来触发网络请求。当Web Worker对象收到消息后,使用Fetch API发起网络请求,并将获取的数据通过 postMessage() 方法发送回主线程。主线程通过增加监听Web Worker的消息事件,并在接收数据后进行下一步逻辑处理。

这种优化策略一般用在密集型任务、大量数据处理、复杂计算任务以及处理大型文件操作等场景下。总之,对于本身需要较长时间的资源或接口请求,我们可以考虑使用Web Worker来提高页面的响应性能。

  1. 预解析和预连接

DNS解析是性能优化的关键步骤,每个前端资源请求前都需要先通过DNS解析获取对应的IP地址,这是一个开销较大的过程。

如果一个前端页面包含许多不同域名的资源链接,我们可以使用DNS预解析的方式提前获取IP地址,从而缩短其他资源请求的响应时间。

以极客官网为例,只要在HTML的 head 标签内配置指定域名就能实现,参考代码如下。

<link rel="dns-prefetch" href="https://time.geekbang.org" />
<link rel="dns-prefetch" href="https://static001.geekbang.org" />

此外,浏览器还提供了预连接功能,可以让浏览器提前建立到指定域名的链接,从而加快页面加载速度。

以极客官网为例,只需要在HTML的标签内配置特定的域名就能实现。参考代码如下。

<link rel="preconnect" href="https://static001.geekbang.org">

预解析和预连接的主要目标是加快前端资源的加载速度,而不是针对HTML主文档。虽然它们可以加快加载速度,但有时反而会影响性能,所以不能盲目使用。

服务器处理

关于服务器处理,实际上主要集中在服务器渲染的前端应用。主要利用Node.js的能力实现。

我们前面讨论过,服务端主要的任务是读取接口数据和生成HTML,因此优化方向是减少这类操作的时间。主要有5个方面。

  1. 优化接口请求

有些前端同学可能没有注意到,在Node.js上发起请求比用户在浏览器发起接口请求的处理速度快很多。所以,第一个优化方案是提前将部分接口数据放到服务器端运行,这样就能加快前端页面显示的速度。

另外,由于Node.js和后端服务都在服务器运行,如果两者都上云了,那么完全可以采用内网通信,这样就可以减少两个服务之间的通信时间。所以,第二个优化方案是在Node.js上尽可能使用内网地址的请求接口。

  1. 使用缓存服务

有些数据,例如配置,通常是通过数据库读取,变更频率很低。我们可以适当地利用Redis将数据缓存起来。这样,Node.js读取数据时,可以快速地从缓存中取出,减少和数据库的通信过程。

  1. 根据实际情况调整服务器硬件

由于涉及大量服务器渲染的工作,前端同学需要时刻关注服务器的CPU和内存使用情况,根据峰值动态调整服务器配置。

  1. 减少重定向

我们在前面的课程中提到过,在某个项目中使用过重定向的功能来满足业务需求,最终导致了用户访问时长过长。所以,第四个优化建议是尽量避免使用重定向功能。

  1. 构建时生成静态页

通常,具有服务器渲染能力的前端框架都提供了提前生成静态页面的能力。对于不经常变化的前端页面,可以在项目构建时提前预生成静态页面,避免用户访问时再经过服务器处理。

资源缓存

还有一种特殊的架构场景,就是利用单页面应用程序(SPA)的能力,实现一套伪静态的Web App,并嵌套在原生App中打开。

虽然这种Web App看起来很接近原生App,但因为SPA还是网页,依然会有请求主文档和请求前端资源的过程。这就意味着用户首先会看到一个白色的屏幕,然后才能看到前端的内容。

要解决这种场景,我们可以利用App的原生能力,提前将HTML主文档、JavaScript和CSS文件下载到本地。用户访问时,直接打开本地主文档。

这种策略能够有效地避免网络传输的延迟,让用户更快地打开主文档,执行JavaScript脚本并生成页面内容。

我们将会在下节课中详细讲解这种方案的实现原理,欢迎继续学习。

总结

好,总结一下。在这节课我们一共提出了4个方向来优化TTFB指标,每个方向都对应着不同的解决方案。

在这些优化方案里,使用CDN节点加速的方案基本上能够解决90%以上的TTFB性能问题,开启HTTP2协议和优化前端资源大小也是非常重要的方法。

至于服务器渲染的前端应用,其优化目标是以减少Node.js服务层运行时间。对于这种情况,最重要的方法是尽可能使用缓存服务、减少重定向。

相信学会这些优化方案之后,你一定能在面试时能够提出一套性能优化解决方案,这也是通往全栈工程师最佳的学习方法。

下节课,我们将继续学习和优化TTFB指标密切相关的主题:如何优化APP内前端白屏时间。

思考题

这节课,我们围绕着TTFB指标,探讨了多个优化方案。现在给你布置一道思考题。

在你参与过的前端项目中,TTFB指标最少的时间是多少?为什么我们无法把主文档的TTFB时间降到为0?

欢迎你在留言区和我交流。如果觉得有所收获,也可以把课程分享给更多的朋友一起学习。我们下节课见!