前端性能优化之缓存利用

Posted 程序员的修仙之路

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端性能优化之缓存利用相关的知识,希望对你有一定的参考价值。

前言

缓存种类大致有以下几种:

  1. CDN缓存

  2. DNS缓存

  3. 客户端缓存(无需发送请求的memory cache, disk cache,需要发请求验证的Etag,Last-Modified304)

  4. Service Worker与缓存

  5. 离线缓存

下面就分别讨论下这几种缓存

CDN缓存

CDN是个什么概念,相信大家也都清楚就不赘述了。直接聊一下他的优势:

  • CDN节点解决了跨运营商和跨地域访问的问题,访问延时大大降低;

  • 大部分请求在CDN边缘节点完成,CDN起到了分流作用,减轻了源站的负载。

关于CDN缓存 ,在浏览器本地缓存失效后,浏览器会向CDN边缘节点发起请求。类似浏览器缓存,CDN边缘节点也存在着一套缓存机制。CDN边缘节点缓存策略因服务商不同而不同,但一般都会遵循http标准协议,通过http响应头中的 Cache-control: max-age 的字段来设置CDN边缘节点数据缓存时间。

当客户端向CDN节点请求数据时,CDN节点会判断缓存数据是否过期,若缓存数据并没有过期,则直接将缓存数据返回给客户端;否则,CDN节点就会向源站发出回源请求,从源站拉取最新数据,更新本地缓存,并将最新数据返回给客户端。 CDN服务商一般会提供基于文件后缀、目录多个维度来指定CDN缓存时间,为用户提供更精细化的缓存管理。

CDN缓存刷新

CDN边缘节点对开发者是透明的,相比于浏览器Ctrl+F5的强制刷新来使浏览器本地缓存失效,开发者可以通过CDN服务商提供的“刷新缓存”接口来达到清理CDN边缘节点缓存的目的。这样开发者在更新数据后,可以使用“刷新缓存”功能来强制CDN节点上的数据缓存过期,保证客户端在访问时,拉取到最新的数据。

DNS缓存

DNS(Domain Name System): 负责将域名URL转化为服务器主机IP。

TTL(Time To Live):表示查找返回的DNS记录包含的一个存活时间。

TTL过期则这个DNS记录将被抛弃。浏览器DNS缓存也有自己的过期时间,这个时间是独立于本机DNS缓存的,相对也比较短,例如chrome只有1分钟左右。

DNS性能优化最佳实践

当客户端的DNS缓存为空时,DNS查找的数量与Web页面中唯一主机名的数量相等。所以减少唯一主机名的数量就可以减少DNS查找的数量。

但是问题来了,有时候需要多设置主机数量,来增加DNS的负载均衡,因此减少DNS查找和增加主机数量形成了矛盾关系,经过实战DNS设置2-4个主机名是最佳的。更多负载均衡可以用其他方式实现,例如用nginx做负载均衡!

浏览器缓存策略之客户端缓存

1、Cache-control: max-age

假设你的站点有引用一个脚本文件,你非常确认这个脚本文件内容多年不变。那么自然希望浏览器把这个脚本缓存起来,不用每一次都请求服务器,然后服务器再返回相同的内容。这样能够节省带宽开销并且提升性能。

此时你只需要设置文件返回的HTTP头中的Cache-Control设置为:

Cache-Control: max-age=31536000

虽然是五十年不变,但是标准中规定max-age值最大不能超过一年,又因为是以秒为单位,所以值为31536000。

但是,如果这一年中的某一天你发现脚本内容必须要更改了怎么办?很简单,改变请求的文件名就好了。

Expires

在 Response Headers里设置 Expires也可以避免浏览器和服务器发送请求,直到超过设置的时间。

Expires Sat, 16 May 2019 23:25:36 GMT

Cache-Control 和 Expires 均遵循三级缓存原理。

//三级缓存原理
1. 先去内存看,如果有,直接加载
2. 如果内存没有,择取硬盘获取,如果有直接加载
3. 如果硬盘也没有,那么就进行网络请求
4. 加载到的资源缓存到硬盘和内存

Last-Modified 304协商缓存

服务器为了通知浏览器当前文件的版本,会发送一个上次修改时间的标签,例如:

Last-Modified:Sat, 16 May 2019 23:30:52 GMT

如下图:

假如是304协商缓存,验证步骤如下:

  1. 浏览器:Hello,我需要 summer.min.js这个文件,如果是在 Last-Modified:Sat, 16 May 2019 23:30:52 GMT 之后修改过的,请发给我。

  2. 服务器:(检查文件的修改时间)

  3. 服务器:Hey,这个文件在那个时间之后没有被修改过,你已经有最新的版本了。

  4. 浏览器:太好了,那我就显示给用户了。

Etag

上面截图中也圈出来了,其实Etag和304类似,但是级别比 Last-Modified 高一些。

请求过程如下:

  1. 浏览器:Hey,我需要zhangfengfeng的 summer.css这个文件,有没有不匹配"61213-1762a-50bf790757204"这个串的

  2. 服务器:(检查ETag…)

  3. 服务器:Hey,我这里的版本也是"61213-1762a-50bf790757204",你已经是最新的版本了

  4. 浏览器:好,那就可以使用本地缓存了

Service Worker与缓存及离线缓存

随着Service Worker(以下简称SW)的普及和规范,我们可以使用SW提供的缓存接口替代HTTP缓存。当然SW的功能是强大的,除了缓存功能,还能够使用它来实现离线、数据同步、后台编译等等。

一个标配版的sw缓存工代代码应该有以下的片段:

const version = '2';

self.addEventListener('install', event => {
event.waitUntil(
caches.open(`static-${version}`)
.then(cache => cache.addAll([
'/styles.css',
'/script.js'
]))
);
});

self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => response || fetch(event.request))
);
});

网络请求首先到达的是SW脚本中,如果未命中再转发给HTTP缓存。

这段代码的意思是,在SW的install阶段我们将script.js和styles.css放入缓存中;而在请求发起的fetch阶段,通过资源的URL去缓存内查找匹配,成功后立刻返回,否则走正常的网络请求流程。

但是在install阶段的资源内容又是哪里来的?仍然是从HTTP缓存中。这样SW缓存机制又有可能随着HTTP缓存陷入了之前所说的版本不一致的困境中。

既然我们借助SW重写了缓存机制,所以也不想再受牵制于旧的HTTP缓存。解决办法是让SW中的请求必须向服务端验证:

self.addEventListener('install', event => {
event.waitUntil(
caches.open(`static-${version}`)
.then(cache => cache.addAll([
new Request('/styles.css', { cache: 'no-cache' }),
new Request('/script.js', { cache: 'no-cache' })
]))
);
});

目前并非所有的浏览器都支持cache选项的配置。但这个不是太大问题,我们可以通过添加随机数来保证每次请求的URL都不相同,间接的使得缓存失效:

self.addEventListener('install', event => {
event.waitUntil(
caches.open(`static-${version}`)
.then(cache => Promise.all(
[
'/styles.css',
'/script.js'
].map(url => {
// cache-bust using a random query string
return fetch(`${url}?${Math.random()}`).then(response => {
// fail on 404, 500 etc
if (!response.ok) throw Error('Not ok');
return cache.put(url, response);
})
})
))
);
});


以上是关于前端性能优化之缓存利用的主要内容,如果未能解决你的问题,请参考以下文章

前端性能优化之http缓存

一文带你了解前端性能优化之HTTP缓存系列

前端性能优化之HTTP缓存策略

前端性能优化之----静态文件客户端离线缓存_20191110

前端性能优化之----静态文件客户端离线缓存_20191110

前端性能优化 浏览器缓存技术