HTTP缓存

Posted by Young Ken on 2018-11-27

HTTP缓存

HTTP缓存的必要性

现在假设客户端想要加载一份文档,这个文档在Web服务器上,假设这个文档大小是10M,客户端需要经常加载这个文档,那么每次都需要10M的字节流从Web服务器上流到客户端,很多人就想了,这样每次的10M数据是不是没有必要每次都从Web服务器上取,把这个文档存储在客户端本地就好了,这样不但节省了流量也节省了时间(本地加载更快),于是HTTP缓存技术就诞生了。

上面 的例子有个局限性,因为互联网环境还是比较复杂的,上面的例子只假设了有客户端和服务器,其实中间还有很多的缓存代理(代理的意思就是既能发送请求也能接收响应)。现在假设一个爆炸新闻报出来,这有时候好多人都访问这个新闻,我们将用两个图表示有缓存和没有缓存的区别。

没有缓存代理,客户端会直接方法服务器,这样服务器会面对全部的压力。

有缓存代理如果缓存命中直接返回给客户端,如果没有命中再去请求服务器,这样服务器的压力就变小了。

缓存的概念

命中率

命中率是0到1之间,现在假设有一个文档,如果每个请求都命中了这文档那么命中率是1,也就是100%。但是现在有个问题,比如现在服务器上有两个文档,一个是1M,另一个是1G。即使1M的文档命中率很高,1G文档的命中率很低,但是1G文档一次不命中就需要1G的流量在客户端和服务端传输,1G的文档对整个网络的影响更大,这样人们更喜欢用字节命中率来表示作为衡量标准。字节命中率指的是命中字节占全部字节的比。

私有缓存

就指的客户端的缓存。

共有缓存

指的是缓存代理服务器。

验证缓存过期

上面说了很多,大家都指的,缓存一定会过期的,因为文档总是有被修改的时候,一旦无文档被修改了就需要客户端请求新的文档,HTTP是怎么做到知道文档过期的呢。

客户端发起请求,先验证是否有缓存,没有缓存就要读取服务器,从服务器获取然后缓存起来,返回给客户端。如果有缓存验证这个缓存时候是没有过期的,如果没有过期就直接返回给客户端,如果过期了就要从新和服务器进行验证,如果没有变化更新过期时间,如果有变化就从新从服务器获取在进行缓存,最后返回到客户端。

控制缓存的HEAD

Expires

这个数HTTP Response Header中的首部,主要表达的意思就是文档在这个日期之前是没有过期的,都是可以使用的。

1
Expires: Wed, 28 Nov 2018 05:54:34 GMT

但是这个有一个大的问题,因为这个Expires是服务器给的时间,真正发请求是客户端,我把我电脑的日期修改成1980年,那么我本地缓存的文档很难过期了。因为这原因,Expires不经常用。

Cache-Control: max-age

Expires在HTTTP / 1.0中就出现了,但是它存在时间的问题,在HTTTP / 1.1的HTTP Response Header中引入了新的首部,Cache-Control: max-ag=100,这个首部后会添加一个时间,这个时间的意思是这个相应之后的100秒内,都会返回缓存文档,在100之后会从新验证是否有变化。

1
Cache-Control: max-age=31536000

那么问题来了,我在相应头中都上面的两个过期时间都设置了,那个起作用呢?

在HTTP /1.1中Cache-Control: max-age会覆盖Expires

验证缓存过期的那个图中我们发现,如一个缓存过期了,会去服务器验证,这个缓存是否有变化,如没有变化我们将告诉客户端,你修改一些过期时间Cache-Control: max-age,继续用。我们怎么去验证这这个文档是否发生了变化呢?

If-Modified-Since 和 Last-Modified

Last-Modified是一个响应首部,意思是服务器文档最后修改的日期。

If-Modified-Since是一个请求首部,它的意思是这时间和服务器的文档最后修改时间是否一样,如果一样返回304(Not Modified),如果不同就从新返回文档,并且状态是200

1
Last-Modified: Fri, 12 May 2017 20:45:00 GMT
1
2
GET /file
If-Modified-Since: Fri, 12 May 2017 20:45:00 GMT

他们两个经常配合着使用,怎么用呢?那就很简单了,我现在发现这个文档过期了,我需要和服务器验证这文档当前时候还能用,我就拿上次服务器响应Last-Modified这首部的时间,比如是这个Fri, 12 May 2017 20:45:00 GMT,放到If-Modified-Since之后,也会是是发送这个请求If-Modified-Since: Fri, 12 May 2017 20:45:00 GMT,服务器判断这个文档的最后的修改时间等于If-Modified-Since首部给我的时间,我就给你返回200,并且告诉客户端Cache-Control: max-age新的过期时间。

上面有一个很致命的问题,我们判断是文档的最后修改时间,当我打开一个文档的时候,什么都没有做,然后又保存了,这个时候最后修改时间变化了,而文档内容没发生变化,为了解决这个问题我们引入了新的头部。

Etag 和If-None-Match

Etag是一个响应首部,可以认为是文档生成的一个指纹,可以这样认为。只有文档内容发生变化的时候指纹才发生变化。

If-None-Match是一个请求首部,后面是Etag首部的指纹。

那么和上面If-Modified-Since 和 Last-Modified是一样的我用一张图说明一下。

如果请求的tag和服务器的相同就返回304

截止到这里,我们知道怎么和服务器验证这个文档是否过期,但是我不想用缓存怎么处理?下面需要学习的新的首部了。

Cache-Control: no-store

Cache-Control: no-store是一个响应首部,它的意思就是我们不想用任何缓存。

cache-control: no-cache

Cache-Control: no-cache是一个响应首部,这首部的意思可不是不用缓存,这个首部理解起来稍微麻烦一点点。

Cache-Control: no-cache 很类似 Cache-Control: max-age=0,这个是什么意思呢?我们详细解释一下。no-cache是缓存数据,而且max-age是0。

举个例子,现在HTTP响应中设置了no-cache,客户端会这个文档存储在本地,下次客户端请求的时候,直接用If-Not-Modified去后台验证,如果返回发现没有变化,服务端直接返回304,发生变化在更新。这样能做到节省流量的目的。

但是no-store就不一样了,这个首部根本就不管有没有本地缓存,我每次都从新从服务器请求。