[入门篇] HTTP 缓存 概念
Posted 该清缓存了
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[入门篇] HTTP 缓存 概念相关的知识,希望对你有一定的参考价值。
缓存定义
根据REST约束:“每个响应都应该定义它自己是否可以被缓存”
HTTP缓存是HTTP标准的一部分(RFC2616, RFC 7234)
"除非性能可以得到很大的提升,否则用缓存是没啥用的。HTTP/1.1中缓存的目标就是在很多场景中可以避免发送请求,在其他情况下避免返回完整的响应"
针对避免/减少发送请求的数量这一点,缓存使用了过期机制
针对避免返回完整响应这点,缓存采用了验证机制
缓存是什么?
缓存是一个独立的组件,存在于API和API消费者之间
缓存接收API消费者的请求,并把请求发送给API
缓存还从API接收响应并且如果响应是可缓存的就会把响应保存起来,并把响应返回给API的消费者。如果同一个请求再次发送,那么缓存就可能会把保存的响应返回给API消费者
缓存可以看作是请求--响应通讯机制的中间人
缓存种类
客户端缓存/浏览器缓存,它存在于客户端,并且是私有的(因为它不会与其它客户端共享)
网关缓存,它是共享的缓存,位于服务器端,所有的API消费者客户端都会共享这个缓存。它的别名还有反向代理服务器缓存,HTTP加速器等
代理缓存,它位于网络上,共享的,它既不位于API消费者客户端,也不在API服务器上,它在网络的其它地方。这种缓存经常被大型企业或ISP使用,用来服务大规模的用户
缓存支持
表明每个资源是否可以被缓存
Cache-Control:max-age=120,(ResponseHeader)
ResponseCache(Attribute),它只是设置了ResponseHeader,并没有缓存任何东西
缓存存储
API端的缓存,可以使用Responsecache中间件
Expires Header
包含一个HTTP日期,该日期表述了响应会在什么时间过期,例如:Expires:Mon, 11 Jun 2018 13:55:41 GMT。但是它可能会存在一些同步问题,所以要求缓存和服务器的时间是保持一致的。它对响应的类型、时间、地点的控制很有限,因为这些东西都是由cache-control这个Header来控制和限制的
Cache-Control Header
例如Cache-Control:public, max-age=60,这个Header里包含两个指令public和max-age。max-age表明了响应可以被缓存60秒,所以时钟同步就不是问题了;而public则表示它可以被共享和私有的缓存所缓存。所以说服务器可以决定响应是否允许被网关缓存或代理缓存所缓存。对于过期模型,优先考虑使用Cache-Control这个Header
Cache-Control还有很多其它的指令,
常见的几个可以官网上看
https://docs.microsoft.com/en-us/aspnet/core/performance/caching/response?view=aspnetcore-3.1#http-based-response-caching
过期模型
过期模型让服务器可以声明请求的资源也就是响应信息能保持多长时间是“新鲜”的状态。缓存可以存储这个响应,所以后续的请求可以由缓存来响应,只要缓存是“新鲜”的
总的来说私有缓存会减少网络带宽的需求,同时会减少从缓存到API的请求
而共享缓存并不会节省缓存到API的网络带宽,但是它会大幅减少到API的请求。例如同时10000个客户端发出了同样请求到API,第一个到达的请求会来到API程序这里,而其它的同样请求只会来到缓存,这也意味着代码的执行量会大大减少,访问数据库的次数也会大大减少
所以组合使用私有缓存和共享缓存(客户端缓存和公共/网关缓存)还是不错的。但是这种缓存还是更适用于比较静态的资源,例如图片、内容网页;而对于数据经常变化的API并不太合适。如果API添加了一条数据,那么针对这10000个客户端,所缓存的数据就不对了,针对这个例子有可能半个小时都会返回不正确的数据,这时就需要用到验证模型了
过期模型工作原理
这里的Cache缓存可以是私有的也可以是共享的
客户端程序发送请求GETcountries,这时还没有缓存版本的响应,所以缓存会继续把请求发送到API服务器;然后API返回
响应给缓存,响应里面包含了Cache-Control这个Header,Cache-Control声明了响应会保持“新鲜”(或者叫有效)半个小时,最后缓存把响应返回给客户端,但同时缓存复制了一份响应保存了起来
然后比如10分钟之后,客户端又发送了一样的请求
这时,缓存里的响应还在有效期内,缓存会直接返回这个响应,响应里包含一个age Header,针对这个例子(10分钟),age的值就是600(秒)
这种情况下,对API服务器的请求就被避免了,只有在缓存过期(或者叫不新鲜Stale)的情况下,缓存才会访问后端的API服务器
如果缓存是私有的,例如在web应用的localstorage里面,或者手机设备上,请求到此就停止了
如果缓存是共享的,例如缓存在服务器上,情况就不一样了
比如说10分钟之后另一个客户端发送了同样的请求,这个请求肯定首先来到缓存这里,如果缓存还没有过期,那么缓存会直接把响应返回给客户端,这次ageHeader的值就是1200(秒),20分钟了
验证模型
验证模型用于验证缓存的响应数据是否是保持最新的
这种情况下,当被缓存的数据将要成为客户端请求的响应的时候,它首先会检查一下源服务器或者拥有最新数据的中间缓存,看看它所缓存的数据是否仍然最新。这里就要用到验证器
验证器
验证器分为两种:强验证器,弱验证器
强验证器:如果响应的body或者header发生了变化,强验证器就会变化。典型的例子就是ETag(EntityTag)响应header,例如:ETag: "12345678",ETag是由Web服务器或者API发配的不透明标识,它代表着某个资源的特定版本。强验证器可以在任意带有缓存的上下文中使用,在更新资源的时候强验证器可以用来做并发检查
弱验证器:当响应变化的时候,弱验证器通常不一定会变化,由服务器来决定什么时候变化,通常的做法有“只有在重要变化发生的时候才变化”。一个典型的例子就是Last-Modified(最后修改时间)这个Header,例如:Mon,11 Jun 2018 13:55:41 GMT,它里面包含着资源最后修改的时间,这个就有点弱,因为它精确到秒,因为有可能一秒内对资源进行两次以上的更新。但即使针对弱验证器,时钟也必须同步,所以它和expiresheader有同样的问题,所以ETag是更好的选择
还有一种弱ETag,它以w/开头,例如ETag:"w/123456789",它被当作弱验证器来对待,但是还是由服务器来决定其程度。当ETag是这种格式的时候,如果响应有变化,它不一定就变化
弱验证器只有在允许等价(大致相等)的情况下可以使用,而在要求完全相等的需求下是不可以使用的
HTTP标准建议如果可能的话最好还是同时发送ETag和Last-Modified这两个Header
验证器工作原理
客户端第一次请求的时候,请求到达缓存后发现缓存里没有,然后缓存把请求发送到API;API返回响应,这个响应包含ETag和Last-Modified这两个Header,响应被发送到缓存,然后缓存再把它发送给客户端,与此同时缓存保存了这个响应的一个副本
10分钟后,客户端再次发送了同样的请求,请求来到缓存,但是无法保证缓存的响应是“新鲜”的,这个例子里并没有使用Cache-ControlHeader,所以缓存就必须到服务器的API去做检查。这时它会添加两个Headers:If-None-Match,它被设为已缓存响应数据的ETag的值;If-Modified-Since,它被设为已缓存响应数据的Last-Modified的值。现在这个请求就是根据情况而定的了,服务器接收到这个请求并会根据验证器来比较这些header或者生成响应
如果检查合格,服务器就不需要生成响应了,它会返回304Not Modified,然后缓存会返回缓存的响应,这个响应还包含了一个最新的Last-ModifiedHeader(如果支持Last-Modifed的话)
而如果响应的资源发生变化了,API就会生成新的响应
如果是私有缓存,那就请求就会停在这
但如果是共享缓存的话,假如10分钟之后另一个客户端发送了请求,这个请求也会到达缓存,然后跟上面一样的流程
总的来说就是,同样的响应只会被生成一次
对比
私有缓存:后续的请求会节省网络带宽,我们需要与API进行通信,但是API不需要把完整的响应返回来,如果资源没有变化的话只需要返回304即可
共享缓存:会节省缓存和API之间的带宽,如果验证通过的话,API不需要重新生成响应然后重新发送回来
组合使用
过期模型和验证模型还是经常被组合使用的
如果使用私有缓存,这时只要响应没有过期,那么响应直接会从私有缓存返回。这样做的好处就是减少了与API之间的通信,也减少了API生成响应的工作,减轻了带宽需求。而如果私有缓存过期了,那还是会访问到API的。如果只有过期(模型)检查的话,这就意味着如果过期了API就得重新生成响应。但是如果使用验证(模型)检查的话,我们可能就会避免这种情况。因为缓存的响应过期了并不代表缓存的响应就不是有效的了,API会检查验证器,如果响应依然有效,就会返回304。这样网络带宽和响应的生成动作都有可能被大幅度减少了
如果是共享缓存,缓存的响应只要没过期就会一直被返回,这样虽然不会节省客户端和缓存之间的网络带宽,但是会节省缓存和API之间的网络带宽,同时也大幅度减少了到API的请求次数,这个要比私有缓存幅度大,因为共享缓存是共享与可能是所有的客户端的。如果缓存的响应过期了,缓存就必须与API通信,但这也不一定就意味着响应必须被重新生成。如果验证成功,就会返回304,没有响应body,这就有可能减少了缓存和API之间的网络带宽需求,响应还是从缓存返回到客户端的
所以综上,客户端配备私有缓存,服务器级别配备共享缓存就应该是最佳的实践
响应的Cache-Control常用指令
新鲜度:
max-age定义了响应的生命期, 超过了这个值, 缓存的响应就过期了, 它的单位是秒.
s-maxage对于共享缓存来说它会覆盖max-age的值. 所以在私有缓存和共享缓存里响应的过期时间可能会不同
存储地点:
public, 它表示响应可以被任何一个缓存器所缓存, 私有或者共享的都可以
private, 它表示整个或部分响应的信息是为某一个用户所准备的, 并且不可以被共享的缓存器所缓存
验证:
no-cache, 它表示在没有和源服务器重新验证之前, 响应不可以被后续的请求所使用
must-revalidate, 使用它服务器可以声明响应是否已经不新鲜了(过期了), 那么就需要进行重新验证. 这就允许服务器强制让缓存进行重新验证, 即使客户端认为过期的响应也是可以的
proxy-revalidate, 他和must-revalidate差不多, 但不适用于私有缓存
其它:
no-store, 它表示缓存不允许存储消息的任何部分
no-transform, 它表示缓存不可以对响应body的媒体类型进行转换
请求的Cache-Control常用指令
新鲜度:
max-age, 它表示客户端不想要接收已经超过这个值的有效期的响应
min-fresh, 它表示客户端可以接受到这样的响应, 它的有效期不小于它当前的年龄加上这个设定的值(秒), 也就是说客户端想要响应还可以在指定的时间内保持新鲜
max-stale, 它表示客户端可以接收过期的响应
验证:
no-cache, 它表示缓存不可以用存储的响应来满足请求. 源服务器需要重新验证成功并生成响应
其他:
no-store, 和响应的一样
no-transform, 和响应的一样
only-if-cached, 它表示客户端只想要缓存的响应, 并且不要和源服务器进行重新验证和生成. 这个比较适用于网络状态非常差的状态
实践
其实大多数情况下使用max-age和public,private即可...
更多指令请查看: https://tools.ietf.org/html/rfc7234#section-5.2
感谢您的关注,您分享和点赞,是我分享路上最大的动力
以上是关于[入门篇] HTTP 缓存 概念的主要内容,如果未能解决你的问题,请参考以下文章