# http缓存机制
http缓存机制的主要手段有两种:
* 缓存存储:状态码为200 from cache/from store cache/from distory cache
* 缓存对比:状态码为304 Not Modified
## 解释
* 200 OK (from cache) 是浏览器没有跟服务器确认,直接用了浏览器缓存
* 304 Not Modified 是浏览器和服务器多确认了一次缓存有效性,再用的缓存。
它们都是在设置了缓存的情况下触发的。
为什么有的缓存是 200 OK (from cache),有的缓存却是 304 Not Modified 呢?
这是因为设置不同,请求方式不同,导致结果不同。
## 两者触发的时机有什么区别呢?
200 OK (from cache)
* 直接点击链接访问
* 输入网址按回车访问
* 扫描二维码
304 Not Modified
* F5刷新页面时触发(注意:Ctrl+F5强制刷新是200 OK不是304哦)
* 设置了长缓存、但 Entity Tags 没有移除时触发
# 机制一:缓存对比(304 Not Modified)
## 重要属性
* Last-Modified http1.0时期属性 现在仍在使用
* ETag(Entity Tag) http1.1时期新加属性 ,使用inode+mtime(以下有解释)来计算。根据实体内容生成的一段hash字符串(类似于MD5或者SHA1之后的结果),可以标识资源的状态。 当资源发送改变时,ETag也随之发生变化。
>名词解释:
>* inode :包含文件的元信息,包括以下内容
>>文件的字节数、文件拥有者的User ID、文件的Group ID
>>文件的读、写、执行权限
>>文件的时间戳,共有三个:ctime指inode上一次变动的时间;mtime指文件内容上一次变动的时间;atime指文件上一次打开的时间。
>>链接数,即有多少文件名指向这个inode、 文件数据block的位置
>* mtime:指文件内容上一次变动的时间
## ETag
### 为什么http1.1又新推出ETag?
原因是`Last-Modified`存在以下的缺点:
* 某些服务器不能精确得到文件的最后修改时间, 这样就无法通过最后修改时间来判断文件是否更新了。
* 某些文件的修改非常频繁,在秒以下的时间内进行修改. Last-Modified只能精确到秒。
* 一些文件的最后修改时间改变了,但是内容并未改变。 我们不希望客户端认为这个文件修改了。
### ETag有哪些问题?
ETag也有它自己的问题,所以经常在使用中会关闭ETag。举例来说,同一个文件在不同物理机上的inode是不同的,这就导致了在分布式的Web系统中,当访问落在不同的物理机上时会返回不同的ETag,进而导致304失效,降级为200请求。解决办法也有从ETag算法中剥离inode,只是使用mtime,但是这样实际上和Last-Modified就一样了。当然你也可以额外的做一些改进,使ETag对静态资源的算法也是通过hash计算的。不过由于一般我们会使用CDN技术,因此集群部署中的ETag问题并不会造成太大的影响,所以折腾的人也不太是很多。
![](https://box.kancloud.cn/1aa818c3e634d688bfe87196bad3a67c_1729x834.png)
![](https://box.kancloud.cn/06e8fe3567b3e25a9948d2bbf379d4fd_1729x602.png)
## 辨别条件请求
在客户端向服务端发送http请求时,若返回状态码为304 Not Modified 则表明此次请求为条件请求。这就表明了客户端中所请求资源的缓存仍然是有效的,也就是说该资源从上次缓存到现在并没有被修改过。条件请求可以在确保客户端的资源是最新的同时避免因每次都请求完整资源给服务器带来的性能问题。
在请求头中有两个请求参数:If-Modified-Since 和 If-None-Match
当客户端缓存了目标资源但不确定该缓存资源是否是最新版本的时候, 就会发送一个条件请求。在进行条件请求时,客户端会提供给服务器一个If-Modified-Since请求头,其值为服务器上次返回响应头中Last-Modified值,还会提供一个If-None-Match请求头,值为服务器上次返回的ETag响应头的值。
服务器会读取到这两个请求头中的值,判断出客户端缓存的资源是否是最新的,如果是的话,服务器就会返回HTTP/304 Not Modified响应头, 但没有响应体.客户端收到304响应后,就会从本地缓存中读取对应的资源。 所以:当访问资源出现304访问的情况下其实就是先在本地缓存了访问的资源。
另一种情况是,如果服务器认为客户端缓存的资源已经过期了,那么服务器就会返回HTTP/200 OK响应,响应体就是该资源当前最新的内容.客户端收到200响应后,就会用新的响应体覆盖掉旧的缓存资源.只有在客户端缓存了对应资源且该资源的响应头中包含了Last-Modified或ETag的情况下,才可能发送条件请求.如果这两个头都不存在,则必须无条件(unconditionally)请求该资源,服务器也就必须返回完整的资源数据。
## 产生304的流程
1. 浏览器首次请求服务器
2. 服务器返回状态码200,同时在响应头中添加:`Last-Modified(或ETag)`,浏览器获取到这两个请求头后将其缓存下来
3. 浏览器再次请求服务器,就会将缓存的值放到请求头中:`Last-Modified`的值放在`If-Modified-Since`请求头中;`ETag`的值放在`If-None-Match`请求头中
4. 服务器根据上面的两个请求头判断资源是否有修改,有修改返回200,未修改返回304
## 为什么要使用条件请求
当用户访问一个网页时,条件请求可以加速网页的打开时间(因为可以省去传输整个响应体的时间),但仍然会有网络延迟,因为浏览器还是得为每个资源生成一条条件请求,并且等到服务器返回HTTP/304响应,才能读取缓存来显示网页.更理想的情况是,服务器在响应上指定Cache-Control或Expires指令,这样客户端就能知道该资源的可用时间为多长,也就能跳过条件请求的步骤,直接使用缓存中的资源了.可是,即使服务器提供了这些信息,在下列情况下仍然需要使用条件请求:
• 在超过服务器指定的过期时间之后
• 如果用户执行了刷新操作的话
在上节给出的图片中,请求头中包含了一个Pragma: no-cache.这是由于用户使用F5刷新了网页.如果用户按下了CTRL-F5 (有时称之为“强刷-hard refresh”),你会发现浏览器省略了If-Modified-Since和If-None-Match请求头,也就是无条件的请求页面中的每个资源.
## 避免条件请求
通常来说,缓存是个好东西.如果你想提高自己网站的访问速度,缓存是必须要考虑的.可是在调试的时候,有时候需要阻止缓存,这样才能确保你所访问到的资源是最新的.
你也许会有个疑问:“如果不改变网站内容,我怎么才能让浏览器不返回304而返回一个包含响应体的HTTP/200响应呢?”
你可以在浏览器控制台设置取消缓存即可。以Chrome为例:
![](https://box.kancloud.cn/1c7abd9f02db8a2f58a7654d444809e9_1729x324.png)
如果选中了 Disable cache,则请求资源时,请求头中的Cache-Control为no-cache,表明不使用缓存,则会直接获取服务器资源。另外,若没选中Disable cache,Cache-Control有二种情况:
1. `max-age > 0` 时直接从游览器缓存中提取
2. `max-age<= 0` 时向服务器发送http请求,该资源是否有修改有的话返回200 ,无的话返回304.
# 机制二:缓存存储(200 from cache)
## 重要属性
* Pragma : no-cache http1.0时期的属性 为了兼容会使用
* Expires:0 http1.0时期的属性
* Cache-Control http1.1 中加入的新属性,它有以下常用参
* Public/Private 私有缓存/共有缓存
* no-cache:不建议使用本地缓存,但仍然会缓存到本地
* no-store:不会在客户端缓存任何数据
* max-age:比较特殊,是一个混合属性,替代了Expires的过期时间
举个栗子:如果要设置客户端不缓存,并兼容http1.0的方式可以这样写:
```
Pragma : no-cache
Expires:0
Cache-Control:no-store
```
等价于
```
Pragma : no-cache // Pragma为了兼容http1.0
Cache-Control:max-age=0 // 去掉了Expires属性(下面名词解释会说到为什么被去掉),合并到max-age中
```
> 名词解释:
**私有缓存**:《HTTP权威指南》里面讲到了私有缓存的一种就是在浏览器里面输入 about:cache 可以查看自己浏览器缓存的内容,会给出一个显示了缓存内容“磁盘缓存统计”页面,这个可以看看还挺有意思,能展示不少信息
**Expires**:过时期限值,GMT格式,是Web服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。不过Expires 是HTTP 1.0的东西,现在默认浏览器均默认使用HTTP 1.1,所以它的作用基本忽略。Expires 的一个缺点就是,返回的到期时间是服务器端的时间,这样存在一个问题,如果客户端的时间与服务器的时间相差很大(比如时钟不同步,或者跨时区),那么误差就很大,所以在HTTP 1.1版开始,使用Cache-Control: max-age=秒替代。
# 200 vs 304
![](https://box.kancloud.cn/690796844f250ba37db24a4754617cad_1729x1186.png)
## 缓存命中速度
缓存命中 > 缓存再验证成功 > 缓存未命中 = 缓存再验证失败
## 缓存命中优先级
Cache-Control http1.1 > Expires > Pragma http1.0来决定是否 (200 from cache)
## 缓存再验证成功
根据Last-Modified http1.0 和 ETaghttp1.1 来验证是否返回 (304 Not Modified) 两者都有,就必须同时验证,并且两者都满足才会返回304;