HTTP缓存机制及实践

HTTP缓存机制及实践

Tags
http
缓存
description
详解HTTP缓存机制
更新时间
Last updated January 28, 2022
 
notion image
 

什么是HTTP缓存

 
  • HTTP缓存是指,当使用HTTP进行通信时,WEB资源(HTML页面,JS脚本,数据等)在客户端和服务端之间的副本
  • 缓存以URL为根据,当一个具有相同的URL的请求来到的时候,会根据缓存机制决定是使用之前保存的副本来响应请求还是向源服务器再次发送请求
 

缓存的好处

  • 减少网络延迟,加快页面打开的速度
  • 减少网络宽带消耗
  • 减轻服务器负担
 

缓存的坏处

  • 用户可能无法获取到最新的资源
  • 需要对不同资源采取不同的缓存机制,增加系统的复杂性

缓存机制

 

HTTP缓存的两个阶段

 
完整浏览器缓存阶段分为两个阶段: 强缓存和协商缓存,强缓存只发生在浏览器中,协商缓存发生在浏览器和服务器之间
 

本地缓存阶段

 
在浏览器发出请求之前,会先在缓存中查看缓存是否可用,如果可用在不发送请求,直接使用本地缓存,否则进入协商缓存阶段
 

协商缓存阶段

 
浏览器向服务器发送请求,服务器会判断是否命中协商缓存,如果命中则会返回304响应,但不会返回真正的数据,此时浏览器依然会使用本地缓存,如果没有命中,则直接从服务器加载资源。
 

实践准备

创建目录

mkdir http-cache-test cd http-cache-test\ touch app.js npm init

搭建http服务器(node)

 
// app.js const http = require('http'); const server = http.createServer(function (request, response) { console.log('有人访问了服务器'); response.write('Hello, World'); response.end(); }) server.listen(3030, '0.0.0.0');
 
 
notion image

与缓存有关的HTTP字段

本地缓存阶段 - 查看缓存是否可用

名称
值/示例
描述
Mon Apr 26 2021 16:45:01 GMT+0800
告诉浏览器在过期时间之前会使用副本
no-cache
告诉浏览器忽略缓存副本,每次都要向浏览器发起请求
no-store
告诉浏览器不要缓存资源,每次都向服务器请求最新数据
max-age=[秒]
设置缓存的有效时长,单位为s
public
任何途径的缓存者都可以保存副本(包括本地缓存或者代理服务器)
private
只有客户端(浏览器可以缓存副本)
 
  • Expires在Cache-Control之前使用,但是Expires存在客户端和服务器时间不一致的问题
  • Cache-Control的max-age可以替代Expires的功能,而且没有缺陷,所以当两者都出现时Cache-Control优先
  • 即使没有设置这两个字段浏览器也会有默认的缓存行为,但是与浏览器本身优化策略有关
 

实践

 
使用Expires缓存数据
const server = http.createServer(function (request, response) { console.log('有人访问了服务器'); response.setHeader('Expires', new Date('2021-12-31').toString()) response.write('Hello, World' + Math.random().toFixed(2) * 100); response.end(); })
 
 
notion image
 
notion image
可以看到数据确实被缓存了,证据是Status Code 的状态为200 OK(from disk cache)
 
使用Cache-Control缓存数据
 
const server = http.createServer(function (request, response) { console.log('有人访问了服务器'); response.setHeader('Cache-Control', 'max-age=300') response.write('Hello, World' + Math.random().toFixed(2) * 100); response.end(); })
 
 
notion image
 
notion image
可以看到数据也被缓存在了浏览器,证据是Status Code 的状态为200 OK(from disk cache)
 
使浏览器不使用本地缓存
const server = http.createServer(function (request, response) { console.log('有人访问了服务器'); response.setHeader('Cache-Control', 'no-cache') response.write('Hello, World' + Math.random().toFixed(2) * 100); response.end(); })
 
notion image
 
notion image
 
no-cache和no-store的区别
 
  • no-cache会缓存资源,但是不会马上使用资源,而是会先询问服务器,看是否使用本地资源
  • no-store不会缓存本地资源,每次都使用服务器资源
 

协商缓存阶段 - 查看是否使用缓存

名称
值/示例
类型
描述
50b1c1d4f775c65
响应
告知浏览器资源在服务器的唯一标识符
50b1c1d4f775c65
请求
告知服务器缓存之前的ETag值是多少,用于服务器判断是否返回资源
Fri Dec 31 2021 08:00:00 GMT
响应
告知浏览器该资源最近一次修改的时间
Fri Dec 31 2021 08:00:00 GMT
请求
告知服务器该缓存之前的Last-Modified是多少,用于服务器判断是否返回资源
可以看到四个HTTP字段是一一对应的关系,ETag和If-None-Match相对应,Last-Modified与If-Modified-Since对应。
且这两对的功能相似,ETag相比Last-Modified的好处是,由于Last-Modified的最小单位是s,所以当一秒内资源多次变化后,浏览器获取到的可能不是最准确的,而ETag能保证只要资源变化就能被识别
 
当缓存中的响应头带有ETag或者Last-Modified时,浏览器会在请求头中自动加入If-None-Match或If-Modified-Since。这类请求被称为"条件式请求",其请求结果,甚至请求成功的状态,都会随着验证器与受影响资源的比较结果的变化而变化。
 

实践

 
使用ETag进行协商缓存
const server = http.createServer(function (request, response) { console.log('有人访问了服务器'); response.setHeader('Cache-Control', 'no-cache') response.setHeader('ETag', '123') if (request.headers['if-none-match']) { // 检查if-none-match的值 response.statusCode = 304; response.end(); return } response.write('Hello, World' + Math.random().toFixed(2) * 100); response.end(); })
 
 
notion image
 
notion image
 
可以看到,在第二次请求中,浏览器自动带上了If-None-Match 字段且值与第一次请求的ETag 相同,此时 服务器应返回304,表示资源没有被修改,可以使用本地缓存。
 
使用Last-Modified进行协商缓存
const server = http.createServer(function (request, response) { console.log('有人访问了服务器'); response.setHeader('Cache-Control', 'no-cache') response.setHeader('Last-Modified', new Date().toString()) if (request.headers['if-modified-since']) { // 检查if-none-match的值 response.statusCode = 304; response.end(); return } response.write('Hello, World' + Math.random().toFixed(2) * 100); response.end(); })
 
notion image
notion image
 
 

流程总结

notion image

使用场景

 
  1. 当完全不想采用缓存时,应该使用Cache-Control: no-store
  1. 当希望尽量采用强缓存时,应该使用 Cache-Control: max-age=[较长的时间]
  1. 当希望资源有一定的缓存时间,但是一段时间后需要获取资源的,应该使用Cache-Control: max-age=[较短的时间] 并配合ETagLast-Modified
  1. 希望资源每次都进行协商缓存的,应该使用Cache-Control: no-cache 或者Cache-Control: max-age=0 配合ETag 或者Last-Modified 使用
 
 
实际项目中,方案1的应用基本上看不到,对比其他,方案1没有任何优势。在方案2和方案3的选择中,我们会对资源作区分。
  • 对于img,css,js,fonts等非html资源,我们可以直接考虑方案2,并且max-age配置的时间可以尽可能久,类似于缓存规则案例中,cache-control: max-age=31535000配置365天的缓存,需要注意的是,这样配置并不代表这些资源就一定一年不变,其根本原因在于目前前端构建工具在静态资源中都会加入戳的概念(例如,webpack中的[hash],gulp中的gulp-rev),每次修改均会改变文件名或增加query参数,本质上改变了请求的地址,也就不存在缓存更新的问题。
  • 对于html资源,我们建议根据项目的更新频度来确定采用哪套方案。html作为前端资源的入口文件,一旦被强缓存,那么相关的js,css,img等均无法更新。对于高频维护的业务类项目,建议采用方案4,或是方案3但max-age设置一个较小值,例如3600,一小时过期。对于一些活动项目,上线后不会进行较大改动,建议采用方案3,不过max-age也不要设置过大,否则一旦出现bug或是未知问题,用户无法及时更新。
 

刷新与缓存

  • 当 F5 刷新网页时,跳过强缓存,但是会检查协商缓存;
    • notion image
  • 当 Ctrl + F5 强制刷新页面时,直接从服务器加载,跳过强缓存和协商缓存
    • notion image
 
 
 

cdn缓存

cdn缓存是一种服务端缓存,CDN服务商将源站的资源缓存到遍布全国的高性能加速节点上,当用户访问相应的业务资源时,用户会被调度至最接近的节点最近的节点ip返回给用户,在web性能优化中,它主要起到了,缓解源站压力,优化不同用户的访问速度与体验的作用。
 
 

源站与回源

源站就是真正存储源文件的服务器,回源指的是不使用OSS而返回源服务器查找资源
对于OSS存储服务来说,回源指的就是不使用CDN而使用OSS存储中的源文件。
 
 

源站资源响应头

  • OSS配置
notion image
在这里可以单独为一个资源配置其HTTP响应头,利用Cache-Control或者Expires去控制过期时间
  • CDN配置
notion image
在CDN中同样能配置缓存规则。
 

缓存规则

与http缓存规则不同的是,这个规则并不是规范性的,而是由cdn服务商来制定,以阿里云CDN为例,其缓存逻辑如下图
 
notion image
 
 

实践

 
普通的获取一个资源
  1. 访问一个资源http://resources.wecareroom.com/assets2/test/cache.png
notion image
可以看到CDN已经默认采用了ETag和Last-Modified的协商缓存资源,但是没有Cache-Control
2. 再次访问该资源
notion image
由于ETagLast-Modified 的存在,浏览器向CDN发起请求,进行协商缓存阶段,由于CDN和源站中资源没有修改,所以返回304
 
3. 修改源站OSS中的资源
 
4. 再次请求资源
 
notion image
可以看到状态依然是304,这是由于CDN设置了png,jpg,json的文件有一年的缓存期,在一年内都不会请求源站的资源。除非我们用刷新预热的方式去清除缓存。
 
使资源每次都采用源站的资源
 
  1. 添加源站资源的HTTP的响应头Cache-Control: no-cache 并刷新预热,然后再次请求资源
 
notion image
可以看到Cache-Control被写入了HTTP响应头
 
2. 再次修改资源,并再次请求
notion image
可以看到这次依然是200的访问,且访问的已经是一个新的文件了,ETag发生了变化。
 

Nginx代理层缓存

 
Nginx作为代理同样可以提供缓存,作为代理服务器缓存,减轻后端服务器的压力
 
未完待续...
 
 
 
 
 
 
 
 
 
 
彻底弄懂浏览器缓存策略
浏览器缓存策略对于前端开发同学来说不陌生,大家都有一定的了解,但如果没有系统的归纳总结,可能三言两语很难说明白,甚至说错,尤其在面试过程中感触颇深,很多候选人对这类基础知识竟然都是一知半解,说出几个概念就没了,所以重新归纳总结下,温故而知新。 Web 缓存是指一个 Web 资源(如 html 页面,图片,js,数据等)存在于 Web 服务器和客户端(浏览器)之间的副本。 缓存会根据进来的请求保存输出内容的副本;当下一个请求来到的时候,如果是相同的 URL,缓存会根据缓存机制决定是直接使用副本响应访问请求,还是向源服务器再次发送请求。 减少网络延迟,加快页面打开速度 减少网络带宽消耗 降低服务器压力 ... 简化的流程如下 根据什么规则缓存 新鲜度(过期机制):也就是缓存副本有效期。一个缓存副本必须满足以下条件,浏览器会认为它是有效的,足够新的: 含有完整的过期时间控制头信息(HTTP 协议报头),并且仍在有效期内; 浏览器已经使用过这个缓存副本,并且在一个会话中已经检查过新鲜度; 校验值(验证机制):服务器返回资源的时候有时在控制头信息带上这个资源的实体标签 Etag(Entity Tag),它可以用来作为浏览器再次请求过程的校验标识。如果发现校验标识不匹配,说明资源已经被修改或过期,浏览器需求重新获取资源内容。 HTTP 缓存的两个阶段 浏览器缓存一般分为两类:强缓存(也称本地缓存)和协商缓存(也称弱缓存)。 本地缓存阶段 浏览器发送请求前,会先去缓存里查看是否命中强缓存,如果命中,则直接从缓存中读取资源,不会发送请求到服务器。否则,进入下一步。 协商缓存阶段 当强缓存没有命中时,浏览器一定会向服务器发起请求。服务器会根据 Request Header 中的一些字段来判断是否命中协商缓存。如果命中,服务器会返回 304 响应,但是不会携带任何响应实体,只是告诉浏览器可以直接从浏览器缓存中获取这个资源。如果本地缓存和协商缓存都没有命中,则从直接从服务器加载资源。 启用&关闭缓存 按照本地缓存阶段和协商缓存阶段分类: 使用 HTML Meta 标签    Web 开发者可以在 HTML 页面的节点中加入标签,如下: 上述代码的作用是告诉浏览器当前页面不被缓存,事实上这种禁用缓存的形式用处很有限: a.
彻底弄懂浏览器缓存策略