HTTP 的缓存机制,可以说这是前端工程师需要掌握的重要知识点之一。本文将针对 HTTP 缓存整体的流程做一个详细的讲解,争取做到大家读完整篇文章后,对缓存有一个整体的了解。

HTTP 缓存分为 2 种,一种是强缓存,另一种是协商缓存。主要作用是可以加快资源获取速度,提升用户体验,减少网络传输,缓解服务端的压力。这是缓存运作的一个整体流程图:

强缓存

不需要发送请求到服务端,直接读取浏览器本地缓存,在 Chrome 的 Network 中显示的 HTTP 状态码是 200 ,在 Chrome 中,强缓存又分为 Disk Cache (存放在硬盘中)和 Memory Cache (存放在内存中),存放的位置是由浏览器控制的。是否强缓存由 Expires、Cache-Control 和 Pragma 3 个 Header 属性共同来控制。

○ Expires

Expires 的值是一个 HTTP 日期,在浏览器发起请求时,会根据系统时间和 Expires 的值进行比较,如果系统时间超过了 Expires 的值,缓存失效。由于和系统时间进行比较,所以当系统时间和服务器时间不一致的时候,会有缓存有效期不准的问题。Expires 的优先级在三个 Header 属性中是最低的。

○ Cache-Control

Cache-Control 是 HTTP/1.1 中新增的属性,在请求头和响应头中都可以使用,常用的属性值如有:

  • max-age:单位是秒,缓存时间计算的方式是距离发起的时间的秒数,超过间隔的秒数缓存失效
  • no-cache:不使用强缓存,需要与服务器验证缓存是否新鲜
  • no-store:禁止使用缓存(包括协商缓存),每次都向服务器请求最新的资源
  • private:专用于个人的缓存,中间代理、CDN 等不能缓存此响应
  • public:响应可以被中间代理、CDN 等缓存
  • must-revalidate:在缓存过期前可以使用,过期后必须向服务器验证

○ Pragma

Pragma 只有一个属性值,就是 no-cache ,效果和 Cache-Control 中的 no-cache 一致,不使用强缓存,需要与服务器验证缓存是否新鲜,在 3 个头部属性中的优先级最高。

本地通过 express 起一个服务来验证强缓存的 3 个属性,代码如下:

const express = require('express');
const app = express();
var options = {
etag: false, // 禁用协商缓存
lastModified: false, // 禁用协商缓存
setHeaders: (res, path, stat) => {
res.set('Cache-Control', 'max-age=10'); // 强缓存超时时间为10秒
},
};
app.use(express.static((__dirname + '/public'), options));
app.listen(3000);
复制代码

第一次加载,页面会向服务器请求数据,并在 Response Header 中添加 Cache-Control ,过期时间为 10 秒。

第二次加载,Date 头属性未更新,可以看到浏览器直接使用了强缓存,实际没有发送请求。

过了 10 秒的超时时间之后,再次请求资源:

当 Pragma 和 Cache-Control 同时存在的时候,Pragma 的优先级高于 Cache-Control。

协商缓存

当浏览器的强缓存失效的时候或者请求头中设置了不走强缓存,并且在请求头中设置了If-Modified-Since 或者 If-None-Match 的时候,会将这两个属性值到服务端去验证是否命中协商缓存,如果命中了协商缓存,会返回 304 状态,加载浏览器缓存,并且响应头会设置 Last-Modified 或者 ETag 属性。

○ ETag/If-None-Match

ETag/If-None-Match 的值是一串 hash 码,代表的是一个资源的标识符,当服务端的文件变化的时候,它的 hash码会随之改变,通过请求头中的 If-None-Match 和当前文件的 hash 值进行比较,如果相等则表示命中协商缓存。ETag 又有强弱校验之分,如果 hash 码是以 "W/" 开头的一串字符串,说明此时协商缓存的校验是弱校验的,只有服务器上的文件差异(根据 ETag 计算方式来决定)达到能够触发 hash 值后缀变化的时候,才会真正地请求资源,否则返回 304 并加载浏览器缓存。

○ Last-Modified/If-Modified-Since

Last-Modified/If-Modified-Since 的值代表的是文件的最后修改时间,第一次请求服务端会把资源的最后修改时间放到 Last-Modified 响应头中,第二次发起请求的时候,请求头会带上上一次响应头中的 Last-Modified 的时间,并放到 If-Modified-Since 请求头属性中,服务端根据文件最后一次修改时间和 If-Modified-Since 的值进行比较,如果相等,返回 304 ,并加载浏览器缓存。

本地通过 express 起一个服务来验证协商缓存,代码如下:

const express = require('express');
const app = express();
var options = {
etag: true, // 开启协商缓存
lastModified: true, // 开启协商缓存
setHeaders: (res, path, stat) => {
res.set({
'Cache-Control': 'max-age=00', // 浏览器不走强缓存
'Pragma': 'no-cache', // 浏览器不走强缓存
});
},
};
app.use(express.static((__dirname + '/public'), options));
app.listen(3001);
复制代码

第一次请求资源:

第二次请求资源,服务端根据请求头中的 If-Modified-Since 和 If-None-Match 验证文件是否修改。

我们再来验证一下 ETag 在强校验的情况下,只增加一行空格,hash 值如何变化,在代码中,我采用的是对文件进行 MD5 加密来计算其 hash 值。

注:只是为了演示用,实际计算不是通过 MD5 加密的,Apache 默认通过 FileEtag 中 FileEtag INode Mtime Size 的配置自动生成 ETag,用户可以通过自定义的方式来修改文件生成 ETag 的方式。

为了保证 lastModified 不影响缓存,我把通过 Last-Modified/If-Modified-Since 请求头删除了,源码如下:

const express = require('express');
const CryptoJS = require('crypto-js/crypto-js');
const fs = require('fs');
const app = express();
var options = {
etag: true, // 只通过Etag来判断
lastModified: false, // 关闭另一种协商缓存
setHeaders: (res, path, stat) => {
const data = fs.readFileSync(path, 'utf-8'); // 读取文件
const hash = CryptoJS.MD5((JSON.stringify(data))); // MD5加密
res.set({
'Cache-Control': 'max-age=00', // 浏览器不走强缓存
'Pragma': 'no-cache', // 浏览器不走强缓存
'ETag': hash, // 手动设置Etag值为MD5加密后的hash值
});
},
};
app.use(express.static((__dirname + '/public'), options));
app.listen(4000); // 使用新端口号,否则上面验证的协商缓存会一直存在
复制代码

第一次和第二次请求如下:

然后我修改了 test.js ,增加一个空格后再删除一个空格,保持文件内容不变,但文件的修改时间改变,发起第三次请求,由于我生成 ETag 的方式是通过对文件内容进行 MD5 加密生成,所以虽然修改时间变化了,但请求依然返回了 304 ,读取浏览器缓存。

ETag/If-None-Match 的出现主要解决了 Last-Modified/If-Modified-Since 所解决不了的问题:

  • 如果文件的修改频率在秒级以下,Last-Modified/If-Modified-Since 会错误地返回 304
  • 如果文件被修改了,但是内容没有任何变化的时候,Last-Modified/If-Modified-Since 会错误地返回 304 ,上面的例子就说明了这个问题

总结

在实际使用场景中,比如政采云的官网。图片、不常变化的 JS 等静态资源都会使用缓存来提高页面的加载速度。例如政采云首页的顶部导航栏,埋点 SDK 等等。

在文章的最后,我们再次回到这张流程图,这张图涵盖了 HTTP 缓存的整体流程,大家对整体流程熟悉后,也可以自己动手通过 Node 来验证下 HTTP 缓存。

图解 HTTP 缓存的更多相关文章

  1. 图解CPU缓存一致性问题

    产生背景 CPU的读取速度比内存的快,一个快一个慢,就会有矛盾,就会有人想要解决这个矛盾,所以就提出多级缓存来解决,如下图所示. L1级缓存:分为数据域和程序域. L2级缓存:二级缓存.  L3级缓存 ...

  2. 图解:HTTP 范围请求,助力断点续传、多线程下载的核心原理

    题图:by Charles Loyer 一.序 Hi,大家好,我是承香墨影! HTTP 协议在网络知识中占据了重要的地位,HTTP 协议最基础的就是请求和响应的报文,而报文又是由报文头(Header) ...

  3. (十二)mybatis 查询缓存

    目录 什么是查询缓存 图解查询缓存 一级缓存 二级缓存 禁用二级缓存 刷新缓存 二级缓存应用场景 二级缓存局限性 什么是查询缓存 mybatis 在查询数据的时候,会将数据存储起来,下次再次查询相同的 ...

  4. 图解 Redis | 不多说了,这就是 RDB 快照

    大家好,我是小林. 虽说 Redis 是内存数据库. 但是它为数据的持久化提供了两个技术,分别是「 AOF 日志和 RDB 快照」. 这两种技术都会用各用一个日志文件来记录信息,但是记录的内容是不同的 ...

  5. 图解 Redis | 差点崩溃了,还好有主从复制

    大家好,我是小林哥,又来图解 Redis 啦. 我在前两篇已经给大家图解了 AOF 和 RDB,这两个持久化技术保证了即使在服务器重启的情况下也不会丢失数据(或少量损失). 不过,由于数据都是存储在一 ...

  6. 精选腾讯技术干货200+篇,云加社区全年沙龙PPT免费下载!

    2019年已经过去,小编为大家整理了这一年以来云加社区发布的 200多篇腾讯干货,点击文章标题即可跳转到原文,请速速收藏哦~ 看腾讯技术: 腾讯成本优化黑科技:整机CPU利用率最高提升至90%: 腾讯 ...

  7. 图解 | 聊聊 MyBatis 缓存

    首发公众号-悟空聊架构:图解 | 聊聊 MyBatis 缓存 你好,我是悟空. 本文主要内容如下: 一.MyBatis 缓存中的常用概念 MyBatis 缓存:它用来优化 SQL 数据库查询的,但是可 ...

  8. ArcGIS Server建立缓存(切图)原理解析[图解] (转载)

    GoogleMap ,VirtualEarth ,YahooMap 等,目前所有的WebGIS都使用了缓存机制 以提高地图访问速度.原理都是将地图设定为多个比例尺,对于每个比例尺提前将地图分成若干小图 ...

  9. 图解 HTTP 的缓存机制 | 实用 HTTP

    题图:by @joewakeford 一.序 Hi,大家好,我是承香墨影! HTTP 协议在网络知识中占据了重要的地位,HTTP 协议最基础的就是请求和响应的报文头(Header),大多数 Http ...

  10. 图解缓存淘汰算法二之LFU

    1.概念分析 LFU(Least Frequently Used)即最近最不常用.从名字上来分析,这是一个基于访问频率的算法.与LRU不同,LRU是基于时间的,会将时间上最不常访问的数据淘汰;LFU为 ...

随机推荐

  1. 重写Nacos服务发现逻辑动态修改远程服务IP地址

    背景 还是先说下做这个的背景,开发环境上了K8S,所有的微服务都注册在K8S内的Nacos,注册地址为K8S内部虚拟IP,K8S内的服务之间相互调用没有问题,但是本机开发联调调用其他微服务就访问不到. ...

  2. vue-test --------ref

    <template> <div ref="contain">{{content}}</div> <button @click=" ...

  3. Bert-vits2-v2.2新版本本地训练推理整合包(原神八重神子英文模型miko)

    近日,Bert-vits2-v2.2如约更新,该新版本v2.2主要把Emotion 模型换用CLAP多模态模型,推理支持输入text prompt提示词和audio prompt提示语音来进行引导风格 ...

  4. 【内核】ELF 文件执行流程

    # ELF 文件分类 Linux中,ELF文件全称为:Executable and Linkable Format,主要有三种形式,分别是: 可执行文件 动态库文件(共享文件 .so) 目标文件(可重 ...

  5. 在arm架构的银河麒麟系统部署Redis

    以下是在arm架构的银河麒麟系统上部署Redis的详细步骤: 1. 创建文件夹 首先,在合适的位置创建必要的文件夹.在本例中,我们将创建/opt/redis和/usr/src/redis两个文件夹. ...

  6. ModuleNotFoundError: No module named '_pytest.resultlog'

    新环境运行Python时报错 ModuleNotFoundError: No module named '_pytest.resultlog' 上网查,看到解决方法  https://www.cnbl ...

  7. Spring Boot 2.x 到 3.2 的全面升级指南

    Spring Framework 是一种流行的开源企业级框架,用于创建在 Java Virtual Machine (JVM) 上运行的独立.生产级应用程序.而Spring Boot 是一个工具,可以 ...

  8. shopify主题模板速度优化

    前两天一位新客户说他的shopify店铺加载速度很慢,首页完全加载需要 5~6 秒甚至更高,问ytkah有没办法帮忙优化一下.shopify网站速度优化要看具体用了什么模板,有什么功能,哪些可以改哪些 ...

  9. 从零玩转Docker之docker-compose快捷部署中间件-dockercompose2

    title: 从零玩转Docker之docker-compose快捷部署中间件 date: 2023-04-04 17:35:18.035 updated: 2023-05-13 23:08:09.5 ...

  10. Rust实现线段树和懒标记

    参考各家代码,用Rust实现了线段树和懒标记. 由于使用了泛型,很多操作都要用闭包自定义实现. 看代码. // 线段树定义 pub struct SegmentTree<T: Clone> ...