浏览器缓存是如何提升网站访问速度的

1,463 阅读8分钟

提升速度,降低负载

浏览器访问一个页面时,会请求加载HTML、CSS和JS等静态资源,并把这些内容渲染到屏幕上。

对浏览器来说,如果页面没有更新,每次都去请求服务器是没有必要的。所以,把下载的资源缓存起来,下次访问时直接读取本地数据,可以大大提高页面访问速度。

对于服务器来说,每个请求到达之后都会进行url解析,读取文件和返回数据等一系列操作,这都会有CPU和内存开销。因此,通过缓存减少不必要的请求,可以降低服务器的负载

综上所述,为了提高网站的访问速度,降低服务器的负载,就有了浏览器缓存。

那么,浏览器缓存是怎么实现的呢?

过期时间

服务器在返回资源时指定过期时间,浏览器收到响应后把数据存起来。下一次请求时如果资源未过期,则返回本地缓存数据。

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

Expires: Thu, 05 May 2022 08:13:07 GMT

由于Expires是一个绝对时间,所以要求浏览器和服务器的系统时间必需保持同步。否则,将会导致浏览器无法准确地判断过期时间。

为了解决这个问题,HTTP1.1使用的是相对时间,即max-age头字段。

cache-control: max-age=60

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

当expires和max-age同时存在时,max-age优先级更高

协商缓存

当缓存数据过期了,服务器资源未更新时,浏览器是没有必要请求新资源的。所以,为了进一步提升缓存效果,HTTP1.1设计了协商缓存:当缓存数据过期了,浏览器要跟服务器验证资源是否已更新,如果资源更新了再去请求新的数据。

那么,服务器如何验证资源是否已更新呢?

HTTP1.1提供了两种方案:

  • 文件内容的hash值:对应头字段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更新时不一定代表文件内容有更新。

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

静态资源缓存优化

Cache-Control:max-age=31536000

对js/css/img等更新频率低的资源,可以设置一个过期时间(通常为一年)。 如果资源需要紧急更新,只需要更新html中引用的文件名。(所以html一般需要保持较高的新鲜度)

示例:

第一次请求:返回200,max-age=31536000 。

# request
GET /script.js HTTP/1.1
​
# response
HTTP/1.1 200 OK
Content-Type: application/javascript
Cache-Control: max-age=31536000

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

image.png

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

HTML协商缓存优化

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

承接上文,HTML作为Web应用的主要载体,要求实时更新

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

示例:

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

# request
GET /index.html HTTP/1.1

# 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

第二次请求:请求带上If-None-Match/If-Modified-Since,服务器验证资源未更新,返回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

敏感数据禁止缓存

Cache-Control: no-store

禁止浏览器缓存资源(响应)和不使用缓存(请求)。适用于包含敏感信息等不希望缓存的场景。

示例:

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

# request
GET /index.html HTTP/1.1

# response
HTTP/1.1 200 OK
Content-Type: text/html
Cache-Control: no-store

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

代理服务器缓存控制

Cache-Control:public,max-age=600,s-maxage=60

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

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

各大网站缓存方案

B站

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

微信

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

淘宝

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

新浪

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

常见配置方案

  • 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属性来设置强缓存。用法简单,不需要服务器支持。适合更新频率低的页面。

  • 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;
}
  • 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值也不一样。

总结

为了提高网站的访问速度,降低服务器的负载,就有了浏览器缓存。

浏览器缓存的核心是过期时间和协商缓存:服务器在返回资源时指定过期时间,浏览器收到响应后把数据存起来。下一次请求时如果资源未过期,则返回本地缓存数据;当缓存数据过期了,浏览器要跟服务器验证资源是否已更新,如果资源更新了再去请求新的数据,否则返回304。

浏览器缓存有很多的实际应用场景,例如:静态资源缓存优化、HTML协商缓存优化、敏感数据禁止缓存、代理服务器缓存控制。最后介绍了常见的配置方案,包括HTML、nginx和webpack等。

参考资料

MDN

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

HTTP缓存协议实战

前端缓存最佳实践

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