不要再问关于缓存的问题了

背景:
前端 er 需要关注的点,缓存
它在移动端上尤其严重,因为手机随时随地会缓存你的资源,要想清缓存,不像 PC 使用强制刷新,还要手动找到浏览器的缓存,有时候还要重启等
所以 用实践理解缓存机制 写下此文记录

为了对比理解本文会涉及到

  • DNS 缓存
  • CDN 缓存
  • 浏览器缓存 (HTTP 缓存)

先梳理以下 web 缓存的优缺点

缓存的优势

  • 减少网络延迟 加快页面打开速度
  • 降低服务器压力

缓存的缺点

  • 缓存没有清理机制(时间一长 当你不需要浏览之前的这些网页,他们就变成了无用文件)
  • 给开发带来困扰

DNS 缓存

什么是 DNS?

全称 Domain Name System 域名解析系统


万维网上作为域名和 IP 地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的 IP 数串。DNS 协议运行在 UDP 协议之上,使用端口号 53。


DNS 解析

简单的说,通过域名,最终得到该域名对应的 IP 地址的过程叫做域名解析(或主机名解析)

1
www.dnscache.com (域名)  - DNS解析 -> 11.222.33.444 (IP地址)

DNS 缓存

有 DNS 的地方,就有缓存。浏览器、操作系统、Local DNS、根域名服务器,它们都会对 DNS 结果做一定程度的缓存。


DNS 查询过程


  1. 首先搜索浏览器自身的 DNS 缓存,如果存在,则域名解析到此完成。
  2. 如果浏览器自身的缓存里面没有找到对应的条目,那么会尝试读取操作系统的 hosts 文件看是否存在对应的映射关系,如果存在,则域名解析到此完成。
  3. 如果本地 hosts 文件不存在映射关系,则查找本地 DNS 服务器 (ISP 服务器,或者自己手动设置的 DNS 服务器), 如果存在,域名到此解析完成。
  4. 如果本地 DNS 服务器还没找到的话,它就会向根服务器发出请求,进行递归查询。

CDN 缓存

什么是 CDN?

全称 Content Delivery Network, 即内容分发网络。
类似于火车站代售点 这样儿 乘客不用再去售票大厅去排队买票 减轻了售票大厅的压力(起到分流作用,减轻服务器负载压力)


用户在浏览网站的时候,CDN 会选择一个离用户最近的 CDN 边缘节点来响应用户的请求,这样海南移动用户的请求就不会千里迢迢跑到北京电信机房的服务器(假设源站部署在北京电信机房)上了。

CDN 缓存

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


CDN 边缘节点数据缓存机制

  • 当浏览器向 CDN 节点请求数据时,CDN 节点会判断缓存数据是否过期,
  • 未过期:直接将缓存数据返回给客户端;
  • 过期:CDN 节点向服务器发出回源请求,拉取最新数据同时更新本地缓存,并将最新数据返回给客户端。

CDN 服务商一般会提供基于文件后缀、目录多个维度来指定 CDN 缓存时间,为用户提供更精细化的缓存管理。


CDN 优势

  1. CDN 节点解决了跨运营商和跨地域访问的问题,访问延时大大降低。
  2. 大部分请求在 CDN 边缘节点完成,CDN 起到了分流作用,减轻了源服务器的负载。

浏览器缓存(HTTP 缓存)

对于一个数据请求来说,可以分为发起网络请求 后端处理 浏览器响应三个步骤
浏览器缓存可以 i 帮助我们在第一步和第三步中优化性能
比如我们可以直接使用缓存而不发起请求

盯着这张图
cache_detai

什么是浏览器缓存?

cachehttp
浏览器缓存其实就是浏览器保存通过 HTTP 获取的所有资源,是浏览器将网络资源存储在本地的一种行为。

我们从两个方面来看浏览器缓存

  • 缓存位置
  • 缓存策略

缓存的资源去哪里了(缓存位置)?

你可能会有疑问,浏览器存储了资源,那它把资源存储在哪里呢?

  • memory cache (存在内存)
1
2
MemoryCache顾名思义,就是将资源缓存到内存中,等待下次访问时不需要重新下载资源,而直接从内存中获取。Webkit早已支持memoryCache。
目前Webkit资源分成两类,一类是主资源,比如HTML页面,或者下载项,一类是派生资源,比如HTML页面中内嵌的图片或者脚本链接,分别对应代码中两个类:MainResourceLoader和SubresourceLoader。虽然Webkit支持memoryCache,但是也只是针对派生资源,它对应的类为CachedResource,用于保存原始数据(比如CSS,JS等),以及解码过的图片数据。
  • disk cache(存在磁盘)
1
DiskCache顾名思义,就是将资源缓存到磁盘中,等待下次访问时不需要重新下载资源,而直接从磁盘中获取,它的直接操作对象为CurlCacheManager。

huancunduibi

访问缓存优先级

  1. 先在内存中查找,如果有,直接加载。
  2. 如果内存中不存在,则在硬盘中查找,如果有直接加载。
  3. 如果硬盘中也没有,那么就进行网络请求。
  4. 请求获取的资源缓存到硬盘和内存。

浏览器缓存的分类(缓存策略)

强缓存

协商缓存

需要说明的是 浏览器会先判断是否命中强缓存

浏览器缓存的优点

  1. 减少了冗余的数据传输 节省了网费
  2. 减少了服务器的负担,大大提升了网站的性能
  3. 加快了客户端加载网页的速度

浏览器在第一次请求发生后,再次请求时:

  1. 验证是否命中强缓存,如果命中,就直接使用缓存了。
  2. 如果没有命中强缓存,就发请求到服务器检查是否命中协商缓存。
  3. 如果命中协商缓存,服务器会返回 304 告诉浏览器使用本地缓存。
  4. 否则,返回最新的资源。

强缓存

强缓存是利用 http 的返回头中的 Expires 或者 Cache-Control 两个字段来控制的,用来表示资源的缓存时间。

Expires:
该字段是 http1.0 时的规范,它的值为一个绝对时间的 GMT 格式的时间字符串,比如 Expires:Mon,18 Oct 2066 23:59:59 GMT。这个时间代表着这个资源的失效时间,在此时间之前,即命中缓存。这种方式有一个明显的缺点,由于失效时间是一个绝对时间,所以当服务器与客户端时间偏差较大时,就会导致缓存混乱。


Cache-Control:
Cache-Control 是 http1.1 时出现的 header 信息,主要是利用该字段的 max-age 值来进行判断,它是一个相对时间,例如 Cache-Control:max-age=3600,代表着资源的有效期是 3600 秒。cache-control 除了该字段外,还有下面几个比较常用的设置值:

  • no-cache:不使用本地缓存。需要使用缓存协商,先与服务器确认返回的响应是否被更改,如果之前的响应中存在 ETag,那么请求的时候会与服务端验证,如果资源未被更改,则可以避免重新下载。
  • no-store:直接禁止游览器缓存数据,每次用户请求该资源,都会向服务器发送一个请求,每次都会下载完整的资源。
  • public:可以被所有的用户缓存,包括终端用户和 CDN 等中间代理服务器。
  • private:只能被终端用户的浏览器缓存,不允许 CDN 等中继缓存服务器对其缓存。

Cache-Control 与 Expires 可以在服务端配置同时启用,同时启用的时候 Cache-Control 优先级高。

协商缓存

当强缓存没有命中的时候,浏览器会发送一个请求到服务器,服务器根据 header 中的部分信息来判断是否命中缓存。如果命中,则返回 304 ,告诉浏览器资源未更新,可使用本地的缓存。


header: Last-Modify/If-Modify-Since ETag/If-None-Match.

Last-Modify/If-Modify-Since
浏览器第一次请求一个资源的时候,服务器返回的 header 中会加上 Last-Modify,Last-modify 是一个时间标识该资源的最后修改时间。
当浏览器再次请求该资源时,request 的请求头中会包含 If-Modify-Since,该值为缓存之前返回的 Last-Modify。服务器收到 If-Modify-Since 后,根据资源的最后修改时间判断是否命中缓存。
如果命中缓存,则返回 304,并且不会返回资源内容,并且不会返回 Last-Modify。

缺点:

  1. 短时间内资源发生了改变 ,Last-Modified 并不会发生变化。
  2. 周期性变化 , 如果这个资源在一个周期内修改回原来的样子了,我们认为是可以使用缓存的,但是 Last-Modified 可不这样认为,因此便有了 ETag

ETag/If-None-Match
Last-Modify/If-Modify-Since 不同的是,Etag/If-None-Match 返回的是一个校验码。ETag 可以保证每一个资源是唯一的,资源变化都会导致 ETag 变化。服务器根据浏览器上送的 If-None-Match 值来判断是否命中缓存。
Last-Modified 不一样的是,当服务器返回 304 Not Modified 的响应时,由于 ETag 重新生成过,response header 中还会把这个 ETag 返回,即使这个 ETag 跟之前的没有变化。

Last-Modified 与 ETag 是可以一起使用的,服务器会优先验证 ETag,一致的情况下,才会继续比对 Last-Modified,最后才决定是否返回 304。


实践检验

我讨厌文字记忆,所以毫不犹豫选了理工科~~
talk is cheap , show me your code

⚠️ 实践过程控制台不要禁用缓存

利用 koa 启动 server 服务 port:8000

引入静态资源 加载前端模版 去内蒙的航拍 哈哈哈 有我

cache_one

如图可见 初次访问 正常加载模版页面 cache 与图片资源 1.jpeg

实现强缓存


  1. 服务端设置响应头 Cache-Control 资源有效期为 300 秒
1
2
3
4
5
6
7
app.use(async (ctx, next) => {

ctx.set({
'Cache-Control': 'max-age=300'
});
await next();
});
  1. 刷新页面

cache1
响应头的 Cache-Control 变成了 max-age=300

验证访问缓存的优先级:
第一次的网络请求,浏览器把图片资源缓存到了磁盘和内存里,根据约定 应该会先从内存中找资源

  1. 再次刷新页面

cache-----

确实是从内存获取的

4. 关掉页面再重新打开 (内存是存在进程中的,所以关闭该页面,内存中的资源也被释放掉了,磁盘中的资源是永久性的,所以还存在)
cache_disk

from disk cache 从磁盘中获取资源

5. 接下来 有效期 300 秒 后.
cachehuancunshixiao
缓存失效 重新向服务器载入资源


实现协商缓存
协商缓存本地测试直接拦截 url 给定 code

Cache-Control 取默认值 no-cache

1
2
3
4
5
6
7
8
9
10
11
12
13
app.use( async(ctx, next) => {
// // 强制缓存
// ctx.set({
// 'Cache-Control': 'max-age=300'
// });

// 协商缓存测试
if(ctx.url === '/imgs/1.jpeg'){
ctx.status = 304;
return;
}
await next();
});

xieshang

服务器返回 304 同时 size 变小了很多 因为只返回了必要信息

也可利用 现成的插件帮我们计算文件的 ETag
npm install koa-tag -D npm install koa-conditional-get -D
就不演示了

加载资源发生变化(比如换了张图片)ETag 改变 会导致协商缓存策略失效

然后 就行了. 😊

如果你想亲自体验

源码在这里

day day up