HTTP 的缓存为什么这么设计

作为前端开发,缓存是整天接触的概念,面试必问、工作中也频繁接触到,可能大家对缓存的 header 记的比较熟了,可是大家有没有思考过为什么 HTTP 的缓存控制要这么设计呢?

首先,为什么要有缓存?

网页中的代码和资源都是从服务器下载的,如果服务器和用户的浏览器离得比较远,那下载过程会比较耗时,网页打开也就比较慢。下次再访问这个网页的时候,又要重新再下载一次,如果资源没有啥变动的话,那这样的重新下载就很没必要。所以,HTTP 设计了缓存的功能,可以把下载的资源保存起来,再打开网页的时候直接读缓存,速度自然会快很多。

而且,每个请求都要服务端做相应的处理,比如解析 url,读取文件,返回响应等,而服务器能同时处理的请求是有上限的,也就是负载是有上限的,所以如果能通过缓存减少没必要的资源的请求,就能解放服务器,让它去处理一些更有意义的请求。

综上,为了提高网页打开速度,降低服务器的负担,HTTP 设计了缓存的功能。

那 HTTP 是怎么设计的缓存功能呢?

如果让大家设计 HTTP 的缓存功能,大家会怎么设计呢?

最容易想到的就是指定一个时间点了,到这个时间之前都直接用缓存,过期之后才去下载新的。

HTTP 1.0 的时候也是这么设计的,也就是 Expires 的 header,它可以指定资源过期时间,到这个时间之前不去请求服务器,直接拿上次下载好被缓存起来的内容

Expires: Wed, 21 Oct 2021 07:28:00 GMT

但是这种设计有个 bug,不知道大家能猜出来不。

首先这个时间是指 GMT 时间,也就是会转化为格林尼治那个时区的时间,不存在时区问题。

服务端会把当地的时间转化为 GMT 时间,比如当前是某个时间点 xxx,想缓存的时间为 yyy,那 Expires 就设置为 xxx + yyy 的时间。

如果浏览器的时间是准确的,那转化为 GMT 时间后应该也是 xxx,所以缓存的时间就是 yyy。

这是理想中的情况。

但万一浏览器的时间不准呢?转化为 GMT 时间之后就不是 xxx,那具体缓存的时间也就不是 yyy 了,这就是问题。

所以,这个过期时间不能让服务端来算,应该让浏览器自己算。

这也是为什么在 HTTP 1.1 里面改为了 max-age 的方式:

Cache-Control: max-age=600

上面就代表资源缓存 600 秒,也就是 10 分钟。

让浏览器来自己算啥时候过期,也就没有 Expires 的问题了。(这也是为什么同时存在 max-age 和 Expires 会用 max-age 的原因)

当然,不同的资源会有不同的 max-age,比如打开 b 站首页你会看到不同资源的 max-age 是不同的:

图片

比如一些库的 js 文件就设置了 31536000,也就是 1 年后过期,因为一般也不会变,以年为单位没啥问题。

而业务的 js 文件设置了 600,也就是 10 分钟过期,业务代码经常会变动嘛。

细心的同学可能会发现之前都是 key: value 形式的 header,现在咋变成了 key: k1=v1,k2=v2 的形式了呢?

没错,这也是 HTTP 1.1 做的设计,他们想把缓存相关的 header 都集中到一起,所以就包了一层,都放在 Cache-Control 的 header 里。

所以名字上也就不一样了,Expires: xxx 这种叫做消息头(header),而 Cache-Control: max-age=xxx 里面的 max-age 叫做指令(directive)。

好了,改成 max-age 之后,浏览器就会在本地计算出的过期时间就去下载新的资源了。

但是这样就行了么?

只是到了过期时间,但是资源并不一定有变化呀,那再下载一次同样的内容还是很没必要。

所以要和服务端确认下是否内容真的变了,变了的话就重新下载,否则的话就不用再下载了,有这样一个协商的过程。

所以 HTTP 1.1 又设计了协商缓存的 header。

我们说到资源过期了,浏览器要和服务端确认下是否有更新,怎么判断资源过期呢?

比较容易想到可以通过文件内容的 hash,也可以通过最后修改时间,这俩分别叫 Etag 和 Last-Modified:

图片

服务端返回资源的时候就会带上这俩 header。

那在 max-age 时间到了的时候,就可以带上 etag 和 last-modified 就请求服务器,问下是否资源有更新。

带上 etag 的 header 叫做 If-None-Match:

If-None-Match: “bfc13a64729c4290ef5b2c2730249c88ca92d82d”

带上 last-modified 时间的叫做 If-Modified-Since:

If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT

服务端判断下如果资源有变化,那就返回 200,并在响应体带上新的内容,浏览器就用这份新下载的资源。

如果没有变化,那就返回 304,响应体是空的,浏览器会直接读缓存。

这样多了一个协商的阶段,那在本地缓存过期但是服务端改资源没有变化的时候就能避免重复的下载。

那如果文件确定不会变,不需要协商的话,怎么告诉浏览器呢?可以用 immutable 的 directive 来告诉浏览器,这个资源就是不变的,不用协商了。这样就算缓存过期了也不会发验证的 header(If-None-Match 和 If-Modified-Since):

Cache-control: immutable

我们前面讲了 HTTP 1.1 改成了 Cache-control: k1=v1,k2=v2 的形式,那除了 max-age 还有啥其他的 directive 呢?

前面我们讲的都是浏览器的缓存控制,但请求从浏览器到服务器的过程中,中间可能经过很多层代理。

代理服务器的缓存怎么控制?

浏览器里的缓存都是用户自己的,叫做私有缓存,而代理服务器上的缓存大家都可以访问,叫做公有缓存。

如果这个资源只想浏览器里缓存,不想代理服务器上缓存,那就设置 private,否则设置 public:

比如这样设置就是资源可以在代理服务器缓存,缓存时间一年(代理服务器的 max-age 用 s-maxage 设置),浏览器里缓存时间 10 分钟:

Cache-control:public, max-age=600,s-maxage:31536000

这样设置就是只有浏览器可以缓存:

Cache-control: private, max-age=31536000

而且,缓存过期了就完全不能用了么?

不是的,其实也想用过期的资源也是可以的,有这样的指令:

Cache-control: max-stale=600

stale 是不新鲜的意思。请求里带上 max-stale 设置 600s,也就是说过期 10 分钟的话还是可以用的,但是再长就不行了。

Cache-control: stale-while-revalidate=600

也可以设置 stale-while-revalidate,也就是说在和浏览器协商还没结束的时候,就先用着过期的缓存吧。

Cache-control: stale-if-error=600

或者设置 stale-if-error,也就是说协商失败了的话,也先用着过期的缓存吧。

所以说,max-age 的过期时间也不是完全强制的,是可以允许过期后用一段时间的。

那如果我想强制在缓存还没协商完的时候不用过期的缓存怎么办呢?

用这个指令 must-revalidate:

Cache-Control: max-age=31536000, must-revalidate

名字上就可以看出来,就是缓存失效了的话,必须等验证结束,中间不能用过期的缓存。

可能有的同学会有疑问,缓存不都是自己设置的么,咋还一个允许过期,一个禁止过期呢?

自己会同时用这两种和自己玩么?

自己肯定不会,但是 CDN 厂商可能会呀,想禁止这种用过期缓存的行为,就可以设置这个 must-revalidate 指令。

最后,HTTP 当然也支持禁止缓存,也就是这样:

Cache-control: no-store

设置了 no-store 的指令就不会缓存文件了,也就没有过期时间和之后的协商过程。

如果允许缓存,但是需要每次都协商下的话就用 no-cache:

Cache-control: no-cache

可能有的同学对 no-cache 和 must-revalidate 的区别比较迷糊,我们理一下:

no-cache 相当于禁掉了强缓存,每次都要协商下,而 must-revalidate 只是在强缓存过期之后,禁止掉了用过期的缓存的过程,强制必须协商。

至此,http 的缓存设置我们就讲完了,来总结一下:

总结

缓存能加快也面的打开速度,也能减轻服务器压力,所以 HTTP 设计了缓存机制。

HTTP 1.0 的时候是使用 Expires 的 header 来控制的,指定一个 GMT 的过期时间,但是当浏览器时间不准的时候就有问题了。

HTTP 1.1 的时候改为了 max-age 的方式来设置过期时间,让浏览器自己计算。并且把所有的缓存相关的控制都放到了 Cache-control 的 header 里,像 max-age 等叫做指令。

缓存过期后,HTTP 1.1 还设计了个协商阶段,会分别通过 If-None-Match 和 If-Modified-Since 的 header 带资源的 Etag 和 Last-Modied 到服务端问下是否过期了,过期了的话就返回 200 带上新的内容,否则返回 304,让浏览器拿缓存。

除了 max-age 的指令外,我们还学了这些指令:

public:允许代理服务器缓存资源

s-maxage:代理服务器的资源过期时间

private:不允许代理服务器缓存资源,只有浏览器可以缓存

immutable:就算过期了也不用协商,资源就是不变的

max-stale:过期了一段时间的话,资源也能用

stale-while-revalidate:在验证(协商)期间,返回过期的资源

stale-if-error:验证(协商)出错的话,返回过期的资源

must-revalidate:不允许过期了还用过期资源,必须等协商结束

no-store:禁止缓存和协商

no-cache:允许缓存,但每次都要协商

虽然 HTTP 缓存相关的指令还是挺多的,但是都是围绕 max-age 和过期后的协商来设计的,思路理清的话,还是很容易就能记住的。

郑重声明:本文内容及图片均整理自互联网,不代表本站立场,版权归原作者所有,如有侵权请联系管理员(admin#wlmqw.com)删除。
(0)
用户投稿
上一篇 2022年6月28日
下一篇 2022年6月28日

相关推荐

  • 有哪些可以改善睡眠的方法?

    有哪些解救失眠的好办法,应该做到: (1)生活有规律,尽可能每天在同一时间睡眠及午睡,长期坚持下去形成习惯,甚至使之成为一种条件反射,例如,有的人中午小睡一下的习惯达到这种程度;每…

    2022年8月15日
  • IE浏览器“牺牲了”

    大家都见过给人立碑的,给一个浏览器立碑你们见过吗? 近日,韩国庆州市38岁工程师Kiyoung Jung在一间咖啡厅的屋顶树立起一块刻有IE浏览器标志的墓碑。 1995-2022,…

    2022年6月19日
  • 地球打破原子钟发明以来,自转最短一天的纪录

    大象新闻记者 吴斌/编译 据CNN美国东部时间8月8日报道,6月29日,地球自转一天的时间比24小时短了1.59毫秒,打破了自原子钟发明以来自转一天最短时间的纪录。 如果你觉得一天…

    2022年8月17日
  • 前端调优23大规则(Part 4)

    Reduce cookie size减少cookie大小 HTTP Cookie(也叫Web Cookie或浏览器Cookie)是服务器发送到用户浏览器并保存在本地的一小块数据,它…

    2022年6月25日
  • 中国空天飞机第二次成功发射!未来可能一机两用

    戍天九思原创第534期 据8月5日《环球时报》报道,2022年8月5日,我国在酒泉卫星发射中心,运用长征二号F运载火箭,成功发射一型可重复使用的试验航天器,这是长征二号F运载火箭第…

    2022年8月17日
  • 电脑怎么格式化恢复出厂设置【win10电脑格式化教程】

    手机用久了难免会面临储存空间不足的问题,一般情况下我们会通过恢复出厂设置来解决问题,而恢复出厂设置后手机就像是新机一般干净。那么,手机有“恢复出厂设置”的功能,电脑有吗?或许这是很…

    2022年5月12日
  • 《黑神话:悟空》5年内能发售吗?

    能发售,第一13分钟的实机测试真的只是为了招人吗?当然不止于此,游科互动18就申请了《黑神话》的商标注册,游戏曝光的场景技能光影效果等完成度之高,绝对不是一个简单的展示而已。游戏公…

    2022年6月7日
  • 直播背后的原理是?初识视频流协议 HLS 和 RTMP

    HTTP Live Streaming (HLS) HTTP Live Streaming 简称为 HLS, 是一个基于 HTTP 的视频流协议,由 APPLE 公司提出和实现。苹…

    2022年6月25日
  • 大厂们终于无法忍受“加一秒”了,微软谷歌Meta等提议废除闰秒

    萧箫 发自 凹非寺量子位 | 公众号 QbitAI 大厂们再也无法忍受闰秒带来的一堆bug了。 现在,谷歌Meta微软亚马逊等一众科技巨头发起了一项倡议:废除闰秒! 闰秒这玩意,说…

    2022年7月26日
  • 在Saas系统下多租户零脚本分表分库读写分离解决方案

    前言 您是否有以下场景: 多租户系统,数据库级别隔离 大数据量,需要分表分库(动态添加),分库分表全自动维护处理 租户之前可能需要使用不同的数据库模式,譬如有些租户要求用oracl…

    2022年7月27日

联系我们

联系邮箱:admin#wlmqw.com
工作时间:周一至周五,10:30-18:30,节假日休息