http缓存的设计原理和实际应用

作为前端开发,应该熟悉浏览器获取页面资源的过程,而http缓存就是对该过程进行性能优化。文章通过设计一个缓存方案来帮助理解http缓存原理,同时介绍了http缓存在各大网站的应用场景。

什么是http缓存

浏览器向服务器请求资源后,缓存到本地,下次加载页面时直接使用本地资源。http缓存是通过http头字段来控制的

为什么要有http缓存

对浏览器来说,用户每打开一个页面,先去请求服务器获取页面资源,再进行渲染。其中,网络请求是比较耗时的,如果页面没有更新,那么每次都去请求服务器是没有必要的。所以,就有了http缓存:把下载的资源保存起来,下次访问页面时直接读取本地数据,从而提高了页面访问速度。

对于服务器来说,需要对每次请求进行解析url,读取文件和返回响应。而服务器的处理能力是有上限的,所以如果能通过缓存减少不必要的请求,将会降低服务器的负载,让它可以做更多有意义的事情

综上所述,为了提高网页的访问速度,降低服务器的负载,http设计了缓存功能

怎么设计http缓存

1. 过期时间

首先,比较容易想到的方法就是:服务器在返回资源时指定过期时间浏览器拿到资源后存起来,后续请求如果资源未过期就返回缓存数据,不用请求服务器了

这是http1.0的方案,即通过Expires头字段指定过期时间。

Expires: Thu, 05 May 2022 08:13:07 GMT
复制代码

但是这里有一个问题:Expires是一个绝对时间,所以要求浏览器和服务器两者的系统时间必需保持同步

首先,Expires使用的是GMT时间,解决了时区问题。但是,如果浏览器和服务器的系统时间不一致,将会导致浏览器无法准确地判断过期时间。

为了解决这个问题,http1.1使用的是max-age,这是一个相对时间

cache-control: max-age=60
复制代码

max-age=60表示资源有效时间为60s,由浏览器自己计算过期时间,自然也就不存在系统时间同步问题了。

当expires和max-age同时存在时,会优先使用max-age。

2. 协商缓存

当浏览器缓存数据过期了,而服务器资源不一定有更新,这时候再去下载一次资源也是没有必要的。

此时要和服务器验证资源是否已更新,如果未更新就继续使用缓存,否则再去下载新的资源。

这就是http1.1中的协商缓存。

当浏览器缓存数据过期了,要和服务器确认资源是否更新,那么如何判断资源已更新呢?

常见的有以下两种方案:

  • 文件内容的hash值。
  • 文件最后更新时间。

分别对应http1.1中的协商缓存字段etag/ If-None-Match和last-modified/ If-Modified-Since。

服务器返回资源时会带上字段etag或last-modified。当浏览器缓存过期了,会带上If-None-Match或If-Modified-Since字段请求服务器验证资源是否更新。服务器判断资源有更新,就返回200和新资源;否则返回304

有了协商缓存,就能在浏览器缓存过期了,而服务器资源实际未更新的情况下避免重复下载。

当etag和last-modified同时存在时,浏览器会优先选择etag。相对而言,etag精度更高:etag只在文件内容变化时更新,而last-modified更新时不一定代表文件内容有更新。

过期时间和协商缓存是http缓存的核心内容。除此之外,cache-control还提供了其它指令,可以满足多种应用场景。

应用场景

注意:当我们刷新页面或第二次在地址栏按回车键,都会直接请求服务器。为了验证本地缓存未过期的情况,需要打开新的tab访问页面。

1. 缓存静态资源

Cache-Control:max-age=31536000
复制代码

应用场景:一般用来缓存更新频率低的资源。 像js/css/img可以设置一年,当文件需要及时更新时,只需要更改html中引用的文件名。(所以html一般需要保持较高的新鲜度)

示例:

第一次请求:返回200,max-age=31536000 和 Last-Modified/ETag。

# request
GET /script.js HTTP/1.1
​
# response
HTTP/1.1 200 OK
Content-Type: application/javascript
Last-Modified: Fri, 03 Jun 2022 08:24:43 GMT
ETag: "6299c54b-24"
Cache-Control: max-age=31536000
复制代码

第二次请求:返回200,显示数据来自缓存。

# request
GET /script.js HTTP/1.1 
​
# response
HTTP/1.1 200 OK
Content-Type: application/javascript
Last-Modified: Fri, 03 Jun 2022 08:24:43 GMT
ETag: "6299c54b-24"
Cache-Control: max-age=31536000
复制代码

image.png

2. 强制协商缓存

浏览器在使用缓存之前必需跟原始服务器协商验证。

# 方法一
Cache-Control: no-cache
# 方法二
Cache-Control: max-age=0, must-revalidate
复制代码

适用场景: 对数据实时性要求高,又能用缓存提高访问速度。

一般的html使用协商缓存保证实时性,对于并发量大的页面也可以设置较短的缓存有效时间。

为什么html页面实时性要求高呢?

如果html没有及时更新,将会导致页面引用的js/css等资源(缓存时间较长)无法及时更新。另外,如果涉及接口变更,前后端必须同步更新的情况,那么访问旧的html将会导致接口报错。

示例:

第一次请求:返回200和Last-Modified/ETag。

# request
GET /index.html HTTP/1.1
Cache-Control: max-age=0
​
# response
HTTP/1.1 200 OK
Content-Type: text/html
Last-Modified: Fri, 03 Jun 2022 07:32:28 GMT
ETag: "6299b90c-14f"
Cache-Control: no-cache
复制代码

第二次请求:请求带上Last-Modified/ETag,服务器验证资源未更新,返回304。

# request
GET /index.html HTTP/1.1
If-None-Match: "6299b90c-14f"
If-Modified-Since: Fri, 03 Jun 2022 07:32:28 GMT
​
# response
HTTP/1.1 304 Not Modified
Last-Modified: Fri, 03 Jun 2022 07:32:28 GMT
ETag: "6299b90c-14f"
Cache-Control: no-cache
复制代码

3. 禁止缓存

禁止浏览器缓存资源(响应)和不使用缓存(请求)。

Cache-Control: no-store
复制代码

适用场景:包含敏感信息等不希望缓存的场景。

示例:

第一次请求:返回200和no-store。

# request
GET /index.html HTTP/1.1
Cache-Control: max-age=0
​
# response
HTTP/1.1 200 OK
Content-Type: text/html
Last-Modified: Fri, 03 Jun 2022 08:25:15 GMT
ETag: "6299c56b-178"
Cache-Control: no-store
复制代码

第二次请求:请求服务器,返回200和新资源。

4. 代理服务器缓存控制

Cache-Control:public,max-age=600,s-maxage=60
复制代码

public表示浏览器和代理服务器都可以缓存资源;private只有浏览器可以缓存资源;s-maxage表示代理服务器缓存的有效时间。

http缓存除了存在浏览器(私有缓存),还可以存在代理服务器(公共缓存)。例如常见的CDN,即降低了网络延迟,还能降低服务器的负载

对于安全性要求高的资源,建议设置为Cache-Control:private,即禁用公共缓存。因为数据一旦被代理服务器缓存下来,就多了一份被攻击的风险。

各大网站缓存方案

1. B站

  • html:no-cache
  • JS文件:max-age=31536000(1年),文件命名带版本号或指纹信息,方便及时更新。
  • CSS文件:max-age=31536000,文件命名带版本号或指纹信息,方便及时更新。
  • 图片:max-age=31536000,文件命名带版本号或指纹信息,方便及时更新。
  • XHR请求: no-cache

2. 微信

  • html:public, max-age=500
  • JS文件:max-age=31536000(1年),文件命名带版本号或指纹信息,方便及时更新。
  • CSS文件:max-age=31536000,文件命名带版本号或指纹信息,方便及时更新。
  • 图片:max-age=31536000,文件命名带版本号或指纹信息,方便及时更新。
  • XHR请求:no-cache,must-revalidate

3. 淘宝

  • html:默认
  • js文件:max-age=2592000(一个月),s-maxage=86400;文件命名带版本号或指纹信息,方便及时更新。
  • CSS文件:max-age=2592000,s-maxage=3600;文件命名带版本号或指纹信息,方便及时更新。
  • 图片:max-age=15552000;文件命名带版本号或指纹信息,方便及时更新。

4. 新浪

  • html: max-age=60
  • js文件:max-age=31536000;max-age=14400;max-age=3600
  • CSS文件:public, max-age=14400
  • 图片:max-age=31536000(一年)

实现方式

1. HTML缓存

通过 meta标签 的http-equiv和content来设置报文头:Cache-Control和Expires。

<meta http-equiv="Expires" content="Mon, 20 Jul 2013 23:00:00 GMT" />
<meta http-equiv="Cache-Control" content="max-age=7200" />
复制代码

用meta标签的http-equiv属性来设置强缓存。用法简单,不需要服务器支持。适合更新频率低的页面。

2. nginx缓存

当nginx作为静态资源服务器时,通过配置http头保证浏览器缓存行为一致。

# html使用协商缓存
location ~.*.html$
{
    add_header Cache-Control no-cache;
    # 作用跟Cache-Control no-cache一致;兼容HTTP/1.0
    add_header Pragma no-cache;
}
​
# 对于更新频率低的,缓存有效时间可设置长一点。
location ~.*.(js|css|png|jpg)$
{
    expires  365d;
}
复制代码

3. webpack文件名hash

entry:{
    main: path.join(__dirname,'./main.js'),
    vendor: ['react', 'antd']
},
output:{
    path:path.join(__dirname,'./dist'),
    publicPath: '/dist/',
    filname: 'bundle.[contenthash].js'
}
复制代码

通过webpack打包,自动给文件名加上hash值。其中,contenthash表示hash值由文件内容计算得到,内容不同产生的contenthash值也不一样。

参考资料

MDN

HTTP 的缓存为什么这么设计?

HTTP缓存协议实战

前端缓存最佳实践

HTTP 缓存别再乱用了!推荐一个缓存设置的最佳姿势!

猜你喜欢

转载自juejin.im/post/7105314379620417572