分类
网站缓存按照存放的地点不同,可以分为客户端缓存、服务端缓存。
客户端有包括一下两种:
- 浏览器缓存
- 代理服务器缓存
1.客户端缓存
1.1 浏览器缓存
是最靠近用户的缓存,如果启用缓存,用户在访问同一个页面时,将不再从服务器下载页面,而是从本机的缓存目录中读取页面,然后再浏览器中展现这个页面。
浏览器缓存的控制,可以设置meta标签,可以设置数字,也可以设置时间,如下:
<Meta http-equiv=”Expires” Content=”3600″>
<Meta http-equiv=”Expires” Content=”Wed, 26 Feb 1997 08:21:57 GMT”>
HTTP头信息如下:
HTTP/1.1 200 OK
Date: Fri, 30 Oct 1998 13:19:41 GMT
Server: Apache/1.3.3 (Unix)
Cache-Control: max-age=3600, must-revalidate
Expires: Fri, 30 Oct 1998 14:19:41 GMT
Last-Modified: Mon, 29 Jun 1998 02:28:12 GMT
缓存流程
- 先读取内存缓存信息
- 读取硬盘缓存信息
- 重新发起网络请求,获取信息
常见的状态码
| 状态码 | 描述 |
|---|---|
| 200 from memory cache | 状态码是灰色的,从内存中读取之前已经加载过的资源,不请求服务器,页面关闭时,资源就会被内存释放,再次打开相同页面不会出现此类情况,在同一页面刷新才会出现。一般脚本、字体、图片会存在内存当中 |
| 200 from disk cache | 状态码是灰色的,从磁盘中读取之前已经加载过的资源,不请求服务器,页面关闭不会被释放,这部分资源存在电脑磁盘里,只有用户手动清除浏览器缓存的时候才会释放。一般非脚本会存在内存当中,如css等 |
| 200 | 从服务器下载最新资源 |
| 304 | 访问服务器,发现资源没有更新,使用本地资源。 |
缓存策略
1.强缓存
http 1.0
//不请求服务器,直接返回200
//通过 expires: Thu, 03 Jan 2019 11:43:04 GMT
//只要没有超过日期都取本地,
//存在问题 可能客户端日期手动调整,导致日期判断错误
res.setHeader('expires', 'Thu, 03 Jan 2019 11:43:04 GMT')
http 1.1
//使用 cache-control : 'max-age=5'
//相当于5秒后过期
res.setHeader('Cache-Control', 'max-age=5')
2.协商缓存
cache-control 参数设置
响应指令
| key | 描述 |
|---|---|
| public | 完全缓存(客户端+ 代理服务器) 即客户端可以永久缓存 |
| private | 只有客户端缓存(默认) 即客户端可以永久缓存 |
| no-cache | 使用协商缓存 |
| max-age=xxx | xxx秒后失效 |
| no-store | 完全不缓存 |
协商的方式
| key | 描述 |
|---|---|
| last-modified | 比较上次时间 |
| etag | 通过页面生成hash摘要对比 |
1.last-modified + if-modified-since使用
//每次页面返回都设置 res.setHeader('last-modified', new Date().toUTCString())
//通过 req.headers['if-modified-since'] 获取上次的时间与当前系统比较
//没有超过则返回 340 命中
// 过期返回数据 + 200
res.setHeader('Cache-Control', 'no-cache')
res.setHeader('last-modified', new Date().toUTCString())
//如果在3秒内命中
if (new Date(req.headers['if-modified-since']).getTime() + 3 * 1000 > Date.now()) {
console.log('协商缓存命中....')
res.statusCode = 304
res.end()
return
}
- etag + If-None-Match 使用
//每次页面返回都设置 res.setHeader('Etag',hash)
//通过 req.headers['If-None-Match'] 获取上次的Etag比较 是否一样
//没有超过则返回 340 命中
// 过期返回数据 + 200
res.setHeader('Cache-Control','no-cache')
//生成摘要
const crypto = require('crypto')
const hash = crypto.createHash('sha1').update(content).digest('hex')
//返回给客户端保存的指纹
res.setHeader('Etag',hash)
if(req.headers['If-None-Match'] === hash){ //对比摘要
console.log('协商缓存命中....')
res.statusCode = 304
res.end()
return
}
扩展知识:
'pragma':'no-cache'
'Cache-Control','no-cache'
//意思一样 pragma是http1.0历史产物
完整 学习例子
const http = require('http')
function updateTime() {
this.timmer = this.timmer || setInterval(() => this.time = new Date().toUTCString(), 4 * 1000) //没4秒刷新一次内容
return this.time
}
http.createServer((req, res) => {
const { url } = req
if ('/' === url) {
res.end(`
<html>
<!-- <meta http-equiv="Refresh" content="5" /> -->
Html Update Time: ${updateTime()}
<script src='main.js'></script>
</html>
`)
} else if (url === '/main.js') {
const content = `document.writeln('<br>JS 11 Update Time:${updateTime()}')`
// 强缓存(1) Expires 浏览器提示:200 from memory cache
// res.setHeader('Expires', new Date(Date.now() + 4 * 1000).toUTCString())
// 强缓存(2) Cache-Control 浏览器提示:200 from memory cache
// res.setHeader('Cache-Control', 'max-age=5')
// 协商缓存 Last Modified 浏览器提示:304
// res.setHeader('Cache-Control', 'no-cache')
// res.setHeader('last-modified', new Date().toUTCString())
// if (new Date(req.headers['if-modified-since']).getTime() + 3 * 1000 > Date.now()) {
// console.log('协商缓存命中....')
// res.statusCode = 304
// res.end()
// return
// }
// 协商缓存 Etag If-None-Match
res.setHeader('Cache-Control', 'no-cache')
const crypto = require('crypto');
const hash = crypto.createHash('sha1').update(content).digest('hex')
console.log('hash',hash)
res.setHeader('Etag', hash)
if(req.headers['if-none-match'] === hash ){
console.log('Etag协商缓存命中.....')
res.statusCode = 304
res.end()
return
}
res.statusCode = 200
res.end(content)
} else if (url === '/favicon.ico') {
res.setHeader('Cache-Control', 'max-age=100')
res.end('')
}
})
.listen(3000, () => {
console.log('Http Cache Test at:' + 3000)
})
同理ajax 跟<script src='main.js'></script> 引用采用一样的缓存策略
if ('/' === url) {
res.end(`
<html>
<!-- <meta http-equiv="Refresh" content="5" /> -->
Html Update Time: ${updateTime()}
<script src='main.js'></script>
//新增ajax测试
<script>
let xhr = new XMLHttpRequest()
xhr.onreadystatechange = () => {
if (xhr.readyState==4){
console.log('request ' + xhr.status + ' ' +xhr.responseText)
}
}
setInterval(() => {
xhr.open('GET', 'http://127.0.0.1:3000/main.js', true);
xhr.send()
},1000)
</script>
</html>
`)
}
1.2.网关或代理服务器缓存
网关或代理服务器缓存是将网页缓存中网关服务器上,多用户访问同一个页面时,将直接从网关服务器把页面传送给用户。
不过现在的网站为了保证用户访问到最新的内容,一般很少采用浏览器缓存,取而代之的是更加灵活的服务器缓存。
2.服务端缓存
服务端缓存分为:页面缓存、数据缓存、数据库缓存
3.页面缓存
缓存好处
- 提高加载速度
- 减轻服务器负担
- 减少客户端请求网络的流量
例子
nginx项目为什么发布后,用户访问项目会提示资源找不到?
- 由于使用现代脚手架打包的项目一般都会js和css都会带上xxxx+hash.js,xxxx+hash.css。资源文件一般没有问题,
- 主要问题再index.html。因为访问的地址一般是固定不变的,浏览器就会进行缓存策略。
- 如果nginx默认没有配置,就会使用默认的缓存策略 nginx配置代码
server {
listen 80;
server_name 域名;
root 文件目录;
index index.html;
location / { # 不加这一句,会出现nginx欢迎页面,无法正确加载资源
try_files $uri /index.html;
}
location ~ .*.(html)$ { # 对html文件限制缓存
add_header Cache-Control no-store; # 不缓存 或者no-cache 协商缓存
}
}