浏览器缓存(Browser Caching)是为了节约网络的资源加速浏览,浏览器在用户磁盘上对最近请求过的文档进行存储,当访问者再次请求这个页面时,浏览器就可以从本地磁盘显示文档,这样就可以加速页面的阅览。

缓存资源

缓存资源分为 memory cache(内存缓存)disk cache(磁盘缓存)

memory cache

不访问服务器,直接读缓存,从内存中读取缓存。此时的数据时缓存到内存中的,当kill进程后,也就是浏览器关闭以后,数据将不存在。但是这种方式只能缓存派生资源

image.png

我们经常看到的返回200显示from memory cache,其实就是取得是内存里的缓存

disk cache

不访问服务器,直接读缓存,从磁盘中读取缓存,当kill进程时,数据还是存在。这种方式也只能缓存派生资源

image.png

当我们关闭页面,在缓存还未过期的时候访问的一些访问过的派生资源时,就显示200 from disk cache其实就是磁盘缓存

三级缓存原理 (访问缓存优先级)

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

后面会有例子来证明优先级是否正确


失效策略分类

按照失效策略分类,就包括两种 强缓存协商缓存

强缓存

强缓存主要包括 expires 和 cache-control
对比来加深理解

区别 expires cache-control
http版本 1.0 1.1
时间 绝对时间 相对时间
优先级 低于cache-control 高于expires
例子 expires:Mar, 06 Apr 2020 10:47:02 GMT Cache-Control:max-age=3600
代表意义(例子为例) 资源的失效时间,在此字段值之前则命中缓存 资源的有效期是 3600 秒
缺点 由于失效时间是一个绝对时间,因当服务器与客户端时间偏差较大时,就会导致缓存混乱 无法兼容HTTP1.0

实践

通过实践来看看是否符合原理

用koa2的脚手架创建一个项目

koa2 -e koa2-learn (koa2-learn代表项目名称)

创建完成之后在public文件夹里新建一个html以及在images放一个图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- public/index.html -->

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>前端缓存</title>
<style>
.web-cache img {
display: block;
width: 100%;
}
</style>
</head>
<body>
<div class="web-cache"><img src="./images/unique.jpg" /></div>
</body>
</html>

在app.js文件中设置cache-control

注意:新添加的代码块必须得在koa-static引用之前才会生效

1
2
3
4
5
6
7
8
9
10
11
12
// 新添加 --start
app.use(async (ctx, next) => {
// 设置响应头Cache-Control 设置资源有效期为300秒
ctx.set({
'Cache-Control': 'max-age=300',
})
await next()
})
/// 新添加 --end

app.use(require('koa-static')(__dirname + '/public'))

将项目跑起来看看效果

image.png

可以发现设置的cache-Control生效了,第一次请求浏览器会把图片存进了磁盘(disk cache)和内存(memory cache)中。


取内存资源

紧接着我们来刷新一下,根据三级缓存原理,我们会先在内存中找资源
image.png
发现图片是直接从内存中取的,符合


取磁盘资源

memory cache在关闭进程的时候会释放掉其中的资源,现在我们关闭浏览器,重新打开看看当内存没有该图片的时,是否会取磁盘内存里的图片

image.png
发现图片取了磁盘内存,符合


验证cache-control

我们设置的缓存有效期是300s,当过了300s后我们重新刷新一下,来验证一下缓存是否失效

image.png
可以发现缓存失效了,跟我们第一次请求的时候一模一样

协商缓存

协商缓存就是通过服务器来判断缓存是否可用,服务器则根据header的信息( Last-Modify/If-Modify-Since ETag/If-None-Match)来判断是否命中协商缓存,如果命中,则返回 304 ,告诉浏览器资源未更新,可使用本地的缓存。

Last-Modify/If-Modify-Since

浏览器第一次请求一个资源的时候,服务器返回的 header 中会加上 Last-ModifyLast-modify 是一个时间标识该资源的最后修改时间。

当浏览器再次请求该资源时,request 的请求头中会包含If-Modify-Since,该值为缓存之前返回的 Last-Modify。服务器收到 If-Modify-Since 后,根据资源的最后修改时间判断是否命中缓存。

如果命中缓存,则返回 304,并且不会返回资源内容,并且不会返回 Last-Modify。

缺点:短时间内资源发生了改变,Last-Modified 并不会发生变化。

ETag/If-None-Match

Last-Modify/If-Modify-Since 不同的是,Etag/If-None-Match 返回的是一个校验码。ETag 可以保证每一个资源是唯一的,资源变化都会导致 ETag 变化。

web服务器收到请求后发现有头If-None-Match则与ETahe进行对比,相同则返回304不同则返回200

常见问题

  1. Etag与Last-Modified有什么区别?

    Etag更倾向于标识资源是否有变更,而Last-Modified更倾向于含有时间状态的数据

  2. 如果Etag和Last-Modified同时存在,服务器会先检测哪一个?

    服务器会先检测Etag再去检测Last-Modified,因为Etag发生改变的话那么资源的内容一定发生了变化,而Last-Modified发生了变化资源内容不一定发生改变

Etag实践

安装下面两个依赖

yarn add koa-conditional-get , yarn add koa-etag

1
2
3
4
5
6
7
const conditional = require('koa-conditional-get')
const etag = require('koa-etag')

app.use(conditional())
app.use(etag())

app.use(require('koa-static')(__dirname + '/public'))

看一下第一次请求的时候效果

image.png


发现已经有etag的值了,现在我们来刷新看看

image.png
当我们再次请求的时候Request Headers上的If-None-Match已经带上了Etag的值了


接下来我们将一个新的图片跟现有图片重命名覆盖掉,刷新请求

image.png
发现协商缓存失效了,Request Headers上的If-None-Match还是上一张图片的Etag值,与Response Headers返回来的Etag不相等所以重新请求新资源返回200。