深入理解http缓存机制

前言

最近在看《图解http》的时候,对一个状态码比较印象深刻:304,书上对他的解释是:

客户端发送附带请求时,服务端允许请求访问资源,但因发送请求未满足条件的情况后,直接返回304 Not Modified(服务器资源未变,可直接使用客户端未过期的缓存)。304状态码返回时,不包含任何响应的主体部分。304,虽然被划分在3XX中,但是和重定向没关系。

字面解释有点绕口,简单讲就是当浏览器第二次GET请求服务器同一个内容时,服务器检测到你浏览器的缓存内容与请求的内容没有改变时,即返回304状态码,让你直接到浏览器找缓存资源来加载。

那么有以下几个问题:

  1. 浏览器既然知道自己有缓存内容,那么为什么还要服务器索取数据呢?
  2. 服务器怎么判断浏览器的内容和服务器的内容一致呢?

带着这两个问题,我们来探究304的奥妙之处。

为什么需要缓存?

学习一样东西的时候,首先要了解到为什么需要它,它给我们带来哪些便利。

如果有一项内容是用户需要反复获取的,假如每一次都通过网络获取,那么就做了很多重复的事情,对用户来说,等待是种煎熬,对服务器来说,宽带成本也高。因此,缓存和重用以前获取的资源的能力成为优化性能很关键的一个方面。

强制缓存

这是一种较为简单粗暴的缓存机制,它最大的表现就是:当浏览器缓存中的资源未过失效时间的时候,直接从浏览器缓存中获取,不会像服务器请求数据,也就是说请求被浏览器拦截了,要理解这种缓存机制,我们先来了解以下响应header中的两个字段

头部 含义
Expires HTTP 1.0 产物,表示资源失效日期
Cache-Control 操作缓存工作机制的指令
  • Expires

Expires的值为服务端返回的到期时间,即下一次请求时,请求时间小于服务端返回的到期时间,直接使用缓存数据。不过Expires是HTTP1.0的东西,现在默认浏览器均默认使用HTTP 1.1,所以它的作用基本忽略。

虽然过时,我们还是顺带提一提,网上找到一个这样的请求资源,可以看到,Expires的过期时间是2021年,所以在2021年,再次去访问这个数据,直接会在浏览器中的缓存数据中获取,不会在向服务器发送请求,返回200状态码。

image

  • Cache-Control

这个头部用来控制缓存机制,有以下几个选项

选项 含义
private 客户端可以缓存
public 客户端和代理服务器都可缓存
max-age=xxx 缓存的内容将在 xxx 秒后失效,当等于0时,就相当于下面的no-cache
no-cache 需要使用对比缓存来验证缓存数据
no-store 所有内容都不会缓存,强制缓存,对比缓存都不会触发

我们来举个例子,这是一个jquery的缓存,Cache-Control字段中含有max-age=xxx,说明在获取到这个资源的这一刻起,25920000秒内,再次发送请求的话,直接从浏览器缓存中获取。

image

强制缓存的最大特点就是,浏览器通过判断资源的过期信息,直接掌控请求的发送,所以这才叫“强制缓存”,这种缓存机制并不是完美的,不是那么的灵活,也就是不能准确判断出缓存内容和服务器内容是否完全一致,这个时候就需要一个更加灵活,操作性更强的缓存机制:对比缓存

对比(协商)缓存

对比缓存,顾名思义,需要进行比较判断是否可以使用缓存。浏览器第一次请求数据时,服务器会将缓存标识与数据一起返回给客户端,客户端将二者备份至缓存数据库中。当再次请求数据时,客户端将备份的缓存标识发送给服务器,服务器根据缓存标识进行判断,判断成功后,返回304状态码,通知客户端比较成功,可以使用缓存数据,如果比较不一致,那么就返回新内容和新缓存标识码。

先大概了解以下两个关键header:

头部 含义
Etag 资源的匹配信息
Last-Modified 资源的最后一次修改的时间

talk is cheap,show me the pic:

  • 第一次获取:

我们看到状态码是200,服务器不单单发送过来内容,Response中还附带了几个用于缓存的header,no-cache中的值是max-age=0,这就相当于设置no-cache,启动对比缓存,我们还注意到有两个header,分别是Etag和Last-Modified,那么这两个是起到什么作用呢,简单来说就是判断浏览器缓存内容和服务器的内容是否一致。我们带着这几个关键信息进行第二次内容获取

image

  • 第二次获取:

这次的状态码就是304,表示服务器判断内容没发生变化,通知浏览器使用浏览器中的缓存,服务器怎么知道内容没变呢?我们仔细观察第二次请求的Request中的几个特殊header:Etag和Last-Modified,浏览器请求数据的时候,还将第一次请求获得的Etag和Last-Modified一起发送给服务器,这个时候服务器通过对比Etag和Last-Modified判断出内容并没有发生变化,即返回304.

image

对比缓存最大的特点就是,第二次发送请求的时候会一如既往地向服务器发送请求,浏览并不会主动拦截(因为Cache-Control设置max-age=0),并且在发送的请求头中还附带了对比信息(Etag和Last-Modified),这样就能准确的判断内容有没有发生变化。

  • 疑问点:为什么同时需要Etag和Last-Modified?他们的作用不是一样的吗?

这个问题开始也困恼了我很久,后来通过查阅书籍发现,Last-Modified精确时间到“秒”,也就是说,如果服务器在这一秒当中,连续修改了两次内容(虽然几率很小),那么返回的Last-Modified是一样的,这就迷惑了浏览器了,Etag的生成是更具内容不同来定的,所以只要内容不同,Etag就一定不同,对于缓存来说更加精准。

总结

  • 对于强制缓存,服务器通知浏览器一个缓存时间,在缓存时间内,下次请求,直接用缓存,不在时间内,执行比较缓存策略。强制缓存简单粗暴,虽然很呆板,但是也有他的用武之处,比如说大部分jquery的cdn之类不怎么发生改变的内容 ,都是用强制缓存,这样对于浏览器来说,访问数据会更快。

  • 对于比较缓存,将缓存信息中的Etag和Last-Modified通过请求发送给服务器,由服务器校验,返回304状态码时,浏览器直接使用缓存,强制缓存更灵活,可操作性高,通常用来存放不定时修改的内容。