HTTP Cache

2022-03-08  本文已影响0人  小丸子啦啦啦呀

缓存是一个很大的话题,毕竟缓存作为一种性能优化的常规手段,在许多地方都有应用到。本文主要讨论web领域下浏览器与代理缓存。

浏览器缓存级别

L1: service worker
L2: memory cache
L3: disk cache
L4: push cache
L5: http request
L5-1: if-modified-since
L5-2: if-none-match
L5-3: max-age
L5-4: expires

从L1至L5,每一个层级都像一个漏斗,筛出那些可以从缓存中返回数据的请求,通过这种方式,可以极大减轻服务端的压力,同时也提升了响应速度。

memory cache & disk cache

这两种缓存方式,由浏览器自己全权管理。至于浏览器具体如何来管理的,我在网上没有找到对应的资料,只找到了二者的特性与区别。

我们知道,不管是什么缓存位置,容量不可能是无限大的,所以说不可避免地需要清除掉一些文件。对于disk cache,除了浏览器自身可能存在一些回收策略,作为浏览器用户也可以手动清除,右键刷新按钮,点击“Empty cache and hard reload”,这张页面的disk cache内容就消失了。

expires in response entity

这种缓存策略简单直接,试想以下过程:

  1. 源服务器告诉缓存服务器资源该资源可以用到什么时候(GMT,格林尼治时间)
  2. 客户端发请求的时候,请求头中带有Date(报文创建的时间日期,也是GMT格式),缓存服务器拿这个Date和原服务器给的Expires相比较,发现还在时间内, 就返回回去,如果发现超时了,则会问源服务器要最新的资源。

这里有一个尚未搞明白的问题:Date可以通过程序修改,可能造成时间比对失败,所以我认为这应该不允许通过程序修改才对?为什么要这样设计呢?

max-age in common header

max-age是cache-control的一个指令。cache-control是用来控制缓存策略的最重要的一个header。


image.png

为什么请求头和相应头中都包含max-age?且都代表相应的最大age值?

在客户端和源服务器之间存在一个缓存服务器。

根据以上信息,可以推测出可能出现三种情况:

那么,什么情况下,客户端和服务端针对同一个资源所设置的max-age不一样呢?
经过我翻看多个网页的network面板,没有发现同时设置的,并且max-age全部都是在response header中设置,而且出现了很多max-age=31516000s,也就是 1 year。

仔细想想,哪怕一起设置也不会造成bug。服务端面向多个客户端,它不可能仅为某一个客户端来考虑,而是根据资源的更新频率来判断max-age设置为多少。客户端设置max-age时有两种情况:

  1. 比服务器的max-age更大,可能出于某种原因必须设置大一些,如果拿到了过期的,自己负责;
  2. 比服务器的max-age更小,这种情况不会有问题,只是找缓存服务器拿的更频繁。

同时可以推出,缓存服务器肯定记录了收到没个文件的时间,要不然它没有办法计算文件是否超时。

age in common head

首部字段 Age 能告知知客户端,源服务器在多久前创建了响应。
若创建该响应的服务器是缓存服务器,Age 值是指缓存后的响应再此发起认证到认证完成的时间值。
-----《HTTP图解》

age是用来标记资源的年龄的,也就是说从它诞生到现在,过去了多长时间。试想一下过程:

  1. 客户端请求A;
  2. 缓存服务器发现没有存,向服务器要;
  3. 服务器给了,并告诉缓存服务器5秒之内可以直接用(max-age。
    接下来有两种情况:

这里我也有一个没搞明白的问题:客户端拿到这个age有什么用?用来判断本地缓存的文件有没有超时?可是我拿到age的时候,响应都拿到了还有什么意义?

no-store和no-cache有什么区别?

在缓存请求指令中:

在缓存响应指令中:

从字面意思上很容易把no-cache误解成为不缓存,但事实上no-cache代表不 缓存过期的资源,缓存会向源服务器进行有效期确认后处理资源,也许称为 do-not-serve-from-cache-without-revalidation 更合适。no-store 才是真正地不进行缓存,请读者注意区别理解
----《HTTP图解》

last-modified in response entity & if-modified-since in request header

这套逻辑其实很简单,试想一下过程:

  1. 客户端发送请求,缓存服务器发现没有,去找原服务器;
  2. 原服务器返回了资源并且告诉请求方,last-mofied时间是GMTA;
  3. 客户端下次再请求这个资源的时候就会带上if-modified-since=GMAT,然后服务器通过检查GMTA之后文件有没有变化,便可以知道是否过时。
    确实很简单,但是只能控制到秒级别,所以不够精确。

eTag in response header & if-none-match request header

eTag, 可以简单地理解为服务端给一个response编码了,得到了一个字符串,这个字符串可以唯一标示这个response。

if-none-match:


屏幕快照 2022-03-08 上午10.35.06.png

if-None-Match & if-Match

我在看网上的博客的时候,还看到一个if-Match头,不知道它和if-None-match的联系和区别,以及使用场景,于是我有开始查找资料。

屏幕快照 2022-03-08 上午10.41.49.png

那么,什么情况下用if-match, 什么情况下用if-none-match?

If-Match is most often used with state-changing methods (e.g., POST,
PUT, DELETE) to prevent accidental overwrites when multiple user
agents might be acting in parallel on the same resource (i.e., to revent the "lost update" problem). It can also be used with safe
methods to abort a request if the selected representation does not
match one already stored (or partially stored) from a prior request.

发送产生副作用的请求,如UPDATE时,如果同时存在很多个客户端都在请求修改同一份数据,但是每个客户端都不知道有可能别人先于自己把数据改了,而正好修改这份数据时依赖改之前的数据的。
所以说带上if-match,保证只有在没改的情况下,才真正执行请求。
这时候用的是强e-tag.

If-None-Match is primarily used in conditional GET requests to enable
efficient updates of cached information with a minimum amount of
transaction overhead. When a client desires to update one or more
stored responses that have entity-tags, the client SHOULD generate an
If-None-Match header field containing a list of those entity-tags
when making a GET request; this allows recipient servers to send a
304 (Not Modified) response to indicate when one of those stored
responses matches the selected representation.

发送GET请求的时候,带上if-None-Match, 让服务端来判断文件有没有被变化。如果匹配不上则说明变化了,那么要返回最新的文件,否则命中缓存,返回304.

他们两可以同时出现在一个报文中吗?
可以同时出现,但是只有一个起作用,取决于Method是什么。

The If-Match header field can be ignored by caches and intermediaries
because it is not applicable to a stored response.

场景适配

变化频繁的用强制缓存,指定max-age,比如需要从数据库读取的请求;变化不频繁的用协商缓存,比如静态html.,js文件

工作中用到的缓存思想

closure

使用闭包缓存住一个response。

cachedFectch = (function(){
  let cache;
  return function(url, params){
     if(!cache){
        cache = fetch(url, params)
     }
     return cache;
  }
})()

Rxjs shareReplay。

在Angular项目中,试过用shareReplay来缓存住一个response。

class Component{
  data$ = this.dataService.getData().pipe(shareReplay(1));

  getData(){
      return this.data$
  }
}

参考文献

  1. https://www.rfc-editor.org/rfc/rfc7232
  2. 《图解HTTP》
  3. https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Caching
上一篇 下一篇

猜你喜欢

热点阅读