什么是HTTP缓存缓存的好处缓存的坏处缓存机制HTTP缓存的两个阶段本地缓存阶段协商缓存阶段实践准备创建目录搭建http服务器(node)与缓存有关的HTTP字段本地缓存阶段 - 查看缓存是否可用实践协商缓存阶段 - 查看是否使用缓存 实践流程总结使用场景刷新与缓存cdn缓存源站与回源 源站资源响应头缓存规则 实践Nginx代理层缓存
什么是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');
与缓存有关的HTTP字段
本地缓存阶段 - 查看缓存是否可用
名称
值/示例
描述
- 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(); })
可以看到数据确实被缓存了,证据是
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(); })
可以看到数据也被缓存在了浏览器,证据是
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(); })
no-cache和no-store的区别
- no-cache会缓存资源,但是不会马上使用资源,而是会先询问服务器,看是否使用本地资源
- no-store不会缓存本地资源,每次都使用服务器资源
协商缓存阶段 - 查看是否使用缓存
名称
值/示例
类型
描述
可以看到四个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(); })
可以看到,在第二次请求中,浏览器自动带上了
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(); })
流程总结
使用场景
- 当完全不想采用缓存时,应该使用
Cache-Control: no-store
- 当希望尽量采用强缓存时,应该使用
Cache-Control: max-age=[较长的时间]
- 当希望资源有一定的缓存时间,但是一段时间后需要获取资源的,应该使用
Cache-Control: max-age=[较短的时间]
并配合ETag
和Last-Modified
。
- 希望资源每次都进行协商缓存的,应该使用
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 刷新网页时,跳过强缓存,但是会检查协商缓存;
- 当 Ctrl + F5 强制刷新页面时,直接从服务器加载,跳过强缓存和协商缓存
cdn缓存
cdn缓存是一种服务端缓存,CDN服务商将源站的资源缓存到遍布全国的高性能加速节点上,当用户访问相应的业务资源时,用户会被调度至最接近的节点最近的节点ip返回给用户,在web性能优化中,它主要起到了,缓解源站压力,优化不同用户的访问速度与体验的作用。
源站与回源
源站就是真正存储源文件的服务器,回源指的是不使用OSS而返回源服务器查找资源。
对于OSS存储服务来说,回源指的就是不使用CDN而使用OSS存储中的源文件。
源站资源响应头
- OSS配置
在这里可以单独为一个资源配置其HTTP响应头,利用Cache-Control或者Expires去控制过期时间
- CDN配置
在CDN中同样能配置缓存规则。
缓存规则
与http缓存规则不同的是,这个规则并不是规范性的,而是由cdn服务商来制定,以阿里云CDN为例,其缓存逻辑如下图
实践
普通的获取一个资源
- 访问一个资源
http://resources.wecareroom.com/assets2/test/cache.png
可以看到CDN已经默认采用了ETag和Last-Modified的协商缓存资源,但是没有Cache-Control
2. 再次访问该资源
由于
ETag
和Last-Modified
的存在,浏览器向CDN发起请求,进行协商缓存阶段,由于CDN和源站中资源没有修改,所以返回3043. 修改源站OSS中的资源
4. 再次请求资源
可以看到状态依然是304,这是由于CDN设置了png,jpg,json的文件有一年的缓存期,在一年内都不会请求源站的资源。除非我们用刷新预热的方式去清除缓存。
使资源每次都采用源站的资源
- 添加源站资源的HTTP的响应头
Cache-Control: no-cache
并刷新预热,然后再次请求资源
可以看到Cache-Control被写入了HTTP响应头
2. 再次修改资源,并再次请求
可以看到这次依然是200的访问,且访问的已经是一个新的文件了,
ETag
发生了变化。Nginx代理层缓存
Nginx作为代理同样可以提供缓存,作为代理服务器缓存,减轻后端服务器的压力
未完待续...