Q1: 为什么要引入缓存
A:
S1 减少 冗余的数据传输,节约网络带宽
S2 减少 服务器的负担
S3 提高 客户端的 加载速度
Q2 如何设置缓存
A:
S1 对于不常变化的资源,可以设置 "强缓存"
- 服务器设置 响应头字段 "Cache-Control: max-age=N"
- 服务器设置 响应头字段 "Expires: Thu, 10 Nov 2017 08:45:11 GMT`
- Cache-control 的优先级高于 Expires
- 强制缓存的状态码为 200
// node代码
res.setHeader( 'cache-control', 'max-age=N' )
res.setHeader( 'Expires', new Date( Date.now() + N * 1000 ).toGMTString() )
/**
cache-control的相关注意点
1. max-age 是 "生存时间",时间的计算起点是 响应报文的创建时刻(即 Date字段,也就是离开服务器的时刻),而不是 客户端收到报文的时刻,也就是说 包含了在链路传输过程中 所有节点所停留的时间
2. max-age的时长单位为 秒
3. "max-age=0" 一般表示 不使用缓存,请求最新数据,类似于 "no-cache"的效果
4. 客户端设置 请求头字段 "Cache-Control: max-age=xxxx"
- 浏览器 "刷新"时,会在请求头自动加上 Cache-Control: max-age=0;
- Ctrl+F5 的 "强制刷新",会发送 "Cache-Control: no-cache"
cache-control其他值的含义
1. public: 资源允许被 中间服务器缓存
2. private: 资源不允许 被中间代理服务器缓存
3. no-store: 禁止使用缓存,每一次都要重新请求数据
4. no-cache: 可以缓存,但在使用缓存前 要先去服务器验证是否过期,过期了就得重新获取资源
5. must-revalidate: 如果缓存不过期就可以继续使用;但过期了 就必须去服务器重新验证
**/
用一张图表示为:
S1.2 "强缓存"有以下缺点
- 如果缓存的文件A 内容发生了变化 + 缓存还未过期 时,资源A 无法获取到最新内容;
- 在 "强缓存"时间到期时,有可能A的内容并未发生变化,这时候其实也希望能使用 "缓存机制"
所以除了"强缓存", HTTP 还引入了 "协商缓存"机制
S2 设置 "协商缓存"
- 协商缓存: 由服务器根据 资源内容是否发生变化 来判断 缓存是否失效
Last-Modified & If-Modified-Since: Mon, 10 Nov 2018 09:10:11 GMT: 记录 资源最后一次被修改的时间Etag & If-None-Match: 文件内容 标识摘要- Etag 的优先级高于 Last-Modified
- 协商缓存的状态码为 304
// node代码1- 协商缓存
const crypto = require('crypto')
const hash = (value) => {
return crypto.createHash('md5').update(value).digest('base64')
}
res.setHeader('cache-control','max-age=5')
fs.stat(filePath, (err, statObj) => {
if (err) {
res.statusCode = 404;
return res.end('Not Found')
}
let ctime = statObj.ctime.toGMTString()
res.setHeader('Last-Modified',ctime) // 标识文件的 最后修改时间
let since = req.headers['if-modified-since']
// 根据特定的标识组成md5 比如文件的大小
let md5 = hash( fs.readFileSync(filePath) )
res.setHeader('Etag',md5)
const ifNoneMatch = req.headers['if-none-match']
// 同时满足内容和修改时间都未变化
if( ifNoneMatch == md5 && since === ctime ) {
res.statusCode = 304
return res.end()
}
// .......
})
// 代码2- md5示例
const crypto = require('crypto')
let hash = crypto.createHash('md5').update('hello world').digest('base64')
console.log(hash)
/**
Last-Modified & If-Modified-Since 的缺点
1. 当资源内容撤销修改时,文件内容实质没有发生变化,但还是会 更新Last-Modified值 ==>
内容没有变,但是修改时间变化了
2. 时间单位最低是秒, 不能识别1s内的资源内容 发生了变化, 所以 不会更新Last-Modified值 ==> 1s之内的变化 是监控不到的
MD5的特点
1. md5是摘要算法,而不是加密算法(加密后能解密回来的 才是加密算法)
2.摘要的内容如果相同,那么摘要的结果就一样(即自变量只有摘要内容,算法是公开通用的)
3.md5是非可逆的(雪崩效应),不能反解(但是可以通过暴力碰撞出 摘要内容)
ETag的特点
1. 强ETag 要求资源在字节级别必须完全相符;
弱ETag 在值前有个“W/”标记,只要求资源在语义上没有变化,但内部可能会有部分发生了改变(如 HTML 里的标签顺序调整,或者多了几个空格)
Q5 什么是代理 和 代理缓存
A:
S1 代理,是指 一个服务器本身不生产内容,而是处于中间位置,转发上下游的请求和响应
S2 通过代理,可以在这个 "承上启下"的环节,设置以下功能:
- 负载均衡:把外部流量 合理地分散到多台源服务器,以提高整体效率和性能;
- 健康检查:使用 "心跳" 等机制监控后端服务器,发现有故障就及时 "踢出"集群;
- 安全防护:保护 被代理的后端服务器,限制IP地址或流量,抵御网络攻击和过载;
- 数据过滤:拦截上下行的数据,任意指定策略 修改请求或者响应;
- 内容缓存:暂存/复用服务器响应
S3 代理相关头字段
- 通用字段via:报文经过一个代理节点,代理服务器就会把自身信息 追加到 字段末尾;
- X-Forwarded-For / X-Real-IP:获取到 客户端的 真实IP地址;
- 专门的"代理协议" 可以在不改动原始报文的情况下 传递客户端的真实IP
S4 代理作为服务器 ==> 向客户端转发响应时的 缓存设置:
- s-maxage:只限定缓存 在代理上能够 生存多久;
- must-revalidate:只要过期就必须回 源服务器验证;
- proxy-revalidate:只要求代理的缓存过期后必须验证,客户端不必回源
- no-transform:代理专用属性, 禁止代理对资源做 转化处理
用图理解为:
S5 代理作为客户端 ==> 接收源服务器响应的 缓存设置:
- max-stale:如果代理上的缓存过期了也可以接受,但不能过期太多(x秒内);
- min-fresh: 缓存必须有效
- only-if-cached:表示只接受代理缓存的数据,不接受源服务器的响应
用图理解为: