HTTP 缓存简单了解。文章整理了相关资料,记录了部分实践。方便大家轻松了解缓存。能回答上三个问题,HTTP缓存就算理解呢。能否缓存?缓存是否过期?协商缓存?

概要:

  • web缓存
  • 缓存的处理
  • 前端解决方案
  • 总结

1.web缓存

Web缓存是可以自动保存常见文档副本的 HTTP 设备。当 Web请求抵达缓存时, 如果本地有“已缓存的”副本,就可以从本地存储设备而不是原始服务器中提取这个文档。《HTTP权威指南》

缓存是一种存储给定资源副本并在请求时将其提供回来的技术。

当Web缓存在其存储中具有请求的资源且能用时,它将拦截该请求并返回其副本,而不是从原始服务器重新下载。

关键字:缓存,原始服务器(产生原始文档)

1.1缓存类型

缓存的种类:浏览器缓存(本文讨论点),代理缓存,网关缓存。

以上种类  缓存工作的原理是一致的,只是缓存所在的位置不同,涉及面更宽广。

这几种缓存,可以分为两大类:

  • 专用缓存(私有缓存):私有缓存专用于单个用户
  • 共享缓存:多用户共享。

图片源:HTTP caching

上图展示了:

上图展示了:

没有缓存:没有缓存直接向服务器请求资源。

共享缓存:当用户Browser1请求资源,经过缓存服务器,缓存服务器也没有资源,向原始服务器请求资源。得到资源后,缓存服务器缓存资源并返还数据给Browser1。当用户Browser2请求相同资源时,缓存服务器有资源,且能用,就直接返还数据给Browser2,不再向原始服务器发起请求。

私有缓存:用户Browser1请求资源,向服务器请求资源。得到资源后,缓存在本地,供下一次请求同样资源时判定使用。用户Browser2需要同样的资源,只能向服务器请求资源,并缓存供下一次请求同样资源时判定使用。

1.2缓存目的

缓存减少了冗余的数据传输,节省了你的网络费用。
缓存缓解了网络瓶颈的问题。不需要更多的带宽就能够更快地加载页面。
缓存降低了对原始服务器的要求。服务器可以更快地响应,避免过载的出现。
缓存降低了距离时延,因为从较远的地方加载页面会更慢一些。

2.缓存的处理

对于HTTP 缓存流程中涉及到的简单问题及相关首部字段。

  • Http 响应的内容是否可缓存到客户端(能否缓存)。
  • 客户端是否可直接从本地缓存中加载并展示,或者发送请求到服务端再验证(缓存是否过期)。
  • 客户端将缓存标识发往服务端,服务端通过标识来判断客户端的缓存是否仍有效,或发送新的数据给客户端(协商缓存能否再用)。
 

2.1相关的首部字段

2.1.1数据能否缓存,相关字段

⑴默认存储

默认情况下,如果请求方法,请求标头字段和响应状态的要求表明响应是可缓存的,则该响应是可缓存的。

常见的HTTP缓存通常仅限于缓存对GET的响应,并且可能会拒绝其他方法。 主缓存键由请求方法和目标URI组成(通常仅使用URI,因为只有GET请求才是缓存目标)

除非特别受cache-control指令约束,否则缓存系统可以始终将成功的响应存储为缓存条目,如果新鲜则可以不经验证就将其返回。如果新鲜也可以在成功验证后返回。

状态码为200、203、206、300、301或410的响应也可以由缓存存储,并用于回复后续请求。

具体参考响应可缓存性

⑵Cache-Control

Cache-Control头里的no-store、no-cache、Public、Private、max-age 用来指明响应内容是否可以被客户端存储,

no-store :禁止进行缓存 缓存不应存储有关客户端请求或服务器响应的任何内容。每次由客户端发起的请求都会下载完整的响应内容。
no-cache:缓存但重新验证 缓存将在使用缓存副本之前,将此请求(带有与本地缓存相关的验证字段)到原始服务器进行验证。
public: 公共缓存 表示该响应可以被任何缓存器(比如中间代理、CDN等)缓存
一些通常不被中间缓存器缓存的页面(比如 带有HTTP验证信息(帐号密码)的页面 或 某些特定状态码的页面),将会被其缓存。
s-maxage=<seconds>: 缓存有效时间 同max-age作用一样表示缓存有效时间,但 s-maxage指令只适用于供多位用户使用的公共缓存服务器(比如CDN缓存)。使用 s-maxage 指令后,直接忽略对 Expires 首部字段及max-age 指令的处理。
private: 私有缓存 表示该响应是专用于某单个用户的,该响应只能应用于浏览器私有缓存中。
max-age=<seconds>: 缓存有效时间 表示资源能够被缓存(保持新鲜)的最大时间。相对Expires而言,max-age是距离请求发起的时间的秒数。

⑶Expires:

指明可以被客户端存储,还告诉了时间。(Expires首部和Cache-control:max-age 首部做的事情基本一致)
Expires:value为缓存过期时间,用来指定资源到期的时间,是服务器端具体的时间点,在过期时间前浏览器可以直接使用缓存数据。如果响应中存在带有max-age或s-maxage指令的Cache-Control标头,则Expires标头将被忽略。

2.1.2缓存是否过期

⑴不能直接使用

Cache-Control:no-cache 

缓存将在使用缓存副本之前,将此请求(带有与本地缓存相关的验证字段)到原始服务器进行验证。

⑵计算是否过期

Date:创建报文的日期时间。

Expires:指明可以被客户端存储,还告诉了时间。(Expires首部和Cache-control:max-age 首部做的事情基本一致)。

Cache-Control:max-age=<seconds> 缓存有效时间。

计算新鲜度公式如下:

max-age指令优先于Expires,因此,如果响应中存在max-age,则计算很简单:
// 新鲜度 = max_age_value
fresh_lifetime = max_age_value
否则,如果响应中存在Expires,则计算为:
// 新鲜度 = expires_value - date_value(Date创建报文的日期时间(启发式缓存阶段会用到这个字段))
fresh_lifetime = expires_value - date_value

Age:告诉接收端响应已产生多长时间(Age值有具体算法感兴趣可以查看Age Calculations)(HTTP/1.1缓存必须在发送每条响应中都包含一个Age头部)

缓存计算是否过期:

// 响应是否新鲜    current_age: 是浏览器计算出的age 值
response_is_fresh = (freshness_lifetime > current_age)

当响应中没有Cache-Contral:max-age 首部,也没有Expires首部,缓存可以计算出一个试探性最大使用期,即启发式缓存。

⑶启发式缓存:

如果响应中未显示Expires,Cache-Control:max-age或Cache-Control:s-maxage,并且响应中不包含其他有关缓存的限制,缓存可以使用启发式方法计算新鲜度寿命。

通常会根据响应头中的2个时间字段 Date 减去 Last-Modified 值的 10% 作为缓存时间。

// Date 减去 Last-Modified 值的 10% 作为缓存时间。
// Date:创建报文的日期时间, Last-Modified 服务器声明文档最后被修改时间
response_is_fresh = max(0,(Date - Last-Modified)) % 10

通常会设置计算出来的值会设置上线。但服务器端最好还是显示提供到期时间比较好

HTTP / 1.1规范没有提供特定的算法,但是对结果施加了最坏情况的约束。 由于启发式到期时间可能会损害语义透明性,因此应谨慎使用,并且我们鼓励原始服务器尽可能提供显式的到期时间。

2.1.3协商缓存能否再用相关字段

当客户端第一次请求的时候没有带条件首部,服务端响应带有条件首部,如Last-Modified ,ETag等,当下次缓存过期客户端将缓存的数据标识发往服务端进行验证

HTPP定义的条件首部最有用的两个 If-Modified-Since 和If-None-Match

⑴ Last-Modified 和 If-Modified-Since
Last-Modified(服务器响应首部): 服务器记录的资源的更新时间。
If-Modified-Since(请求首部字段):已缓存副本的最后修改日期。

当缓存过期,再验证。客户端将缓存的数据标识If-Modified-Since发往服务端,服务端将用Last-Modified 与 If-Modified-Since做对比。If-Modified-Since 字段值早于资源的Last-Modified更新时间,则希望返回新资源。而在指定 If-Modified-Since 字段值的日期时间之后,如果请求的资源都没有过更新,则返回状态码 304 Not Modified 的响应。

缺点:last-Modified 只能精确到秒,文件的修改非常频繁,在秒以下的时间内进行修改,Last-Modified不能精确。

一个文件位于多个CDN服务器上内容虽然一样,当修改时间不一样。(比对后会返回信息更新)

所以在 HTTP / 1.1 出现了 ETag 。

⑵ ETag 和 If-None-Match

ETag(服务器响应首部): 实体标识。它是一种可将资源以字符串形式做唯一性标识的方式。服务器会为每份资源分配对应的 ETag值。

If-None-Match(请求首部字段):缓存的实体标签。用于指定 If-None-Match 字段值的实体标记(ETag)值与请求资源的 ETag 不一致时,它就告知服务器处理该请求返回新资源,相反则返回状态码 304 Not Modified 的响应。

当缓存过期,再验证。客户端将缓存的数据实体标签If-None-Match发往服务端,服务端将用If-None-Match 与 ETag做对比。

⑶ 其它if 条件首部参考文档

2.1.4 vary 可以简单了解

vary可以简单了解,后端用于配置。

vary定义如下:

Vary 是一个HTTP响应头部信息,它决定了对于未来的一个请求头,应该用一个缓存的回复(response)还是向源服务器请求一个新的回复。它被服务器用来表明在 content negotiation algorithm(内容协商算法)中选择一个资源代表的时候应该使用哪些头部信息(headers).

举个例子:图片来源《图解http》

一个客户端向服务器请求/sample.html资源,Accept-Language: en-us,代理服务器没有此资源,向服务器请求

服务器返回了资源,HTTP响应头部信息Vary指定了 Accept-Language,代理服务器返回资源给客户端,并缓存了数据。

第二个客户端向服务器请求/sample.html资源,Accept-Language: zh-cn,代理服务器没有此资源,向服务器请求

服务器返回了资源,HTTP响应头部信息Vary指定了 Accept-Language,代理服务器返回资源给客户端,并缓存了数据。缓存如下

第三个客户端向服务器请求/sample.html资源,Accept-Language: zh-cn或者 en-us, 代理服务器都能返回缓存数据(缓存没过期)。

所以:

服务器使用Vary字段来通知缓存哪些请求头字段用于区分相同的URL请求,服务端存在不同内容的响应。

缓存也会根据Vary指定了 的字段X,根据X字段的值,决定使用缓存,还是发起请求获取数据

上述例子是简单描述存在代理服务器的请款,浏览器通常不实现针对每个URL存储多个变体的功能。

感兴趣可以查看:Understanding The Vary HeaderCaching Negotiated Responses

2.2缓存流程

假设只有浏览器缓存和服务器的场景。参考以上字段画图如下

2.2.1 流程分析

⑴当对资源发起请求的时候,缓存对url 报文进行解析,提取首部判断客户端是否有缓存。

没有缓存,求直接向服务器端请求数据,当得到数据后按缓存控制存储。

有缓存的情况:

  • 缓存需要验证才可使用,向服务器发送本地缓存的相关的验证字段(If-None-Match 、If-Modified-Since)到原始服务器进行验证。
  • 缓存能用,是否过期
    • 缓存能用,没有过期,构造响应报文,展示缓存内容
    • 缓存过期,发送本地缓存的相关的验证字段(If-None-Match 、If-Modified-Since)到原始服务器进行验证。

⑵向服务器发送本地缓存的相关的验证字段(If-None-Match 、If-Modified-Since)到原始服务器进行验证

条件方法再验证(协商缓存):

  • 条件验证成功:原始服务器向客户端发送一个小的 HTTP 304 Not Modified 响应,不包括内容。客户端缓存会更新缓存文档的新鲜度,构造响展示缓存内容。
  • 验证失败:原始服务器向客户端返回新的内容,客户端缓存,展示新的内容。
  • 内容被删除:原始服务器向客户端发送一个 404 Not Found 响应,客户缓存也会删除缓存。

2.2.2 memory cache和 disk cache

当缓存足够新鲜,直接返回缓存数据或者重新加载数据,数据 from memory cache 还是 from disk cache 不要太过纠结。这个跟HTTP 缓存机制没关系。

Chrome employs two caches — an on-disk cache and a very fast in-memory cache. The lifetime of an in-memory cache is attached to the lifetime of a render process, which roughly corresponds to a tab. Requests that are answered from the in-memory cache are invisible to the web request API. If a request handler changes its behavior (for example, the behavior according to which requests are blocked), a simple page refresh might not respect this changed behavior. To make sure the behavior change goes through, call handlerBehaviorChanged() to flush the in-memory cache. But don't do it often; flushing the cache is a very expensive operation. You don't need to call handlerBehaviorChanged() after registering or unregistering an event listener.

大致意思就是:memory cache 的生存期与渲染过程的生存期相关,渲染过程的生存期大致与选项卡相对应。

Chrome优化可以询问正在运行的进程,然后再在磁盘上查找它们是否仍在内存中加载了它们的副本。当页面刷新或者加载,所有的内容文档都会读取到内存中展示,如果此时文档在内存中已经存在,那么缓存 from memory cache,如果是从磁盘中读取的就 from disk cache 。

我自己的简单理解如上图

如果感兴趣资料:Disk Cache 3.0

3.前端解决方案

HTML:设置Cache-control:no-cache(服务器端配置)浏览器再每次请求时都始终重新验证文档,并在内容变化时获取最新版本。
在HTML内挂载的 js css png 都带上 文件唯一标识字符串。任意文件变化,url 就会变化,从而引起HTML 文件变化。下次请求资源就会更新。

4.总结

文章整理了相关资料,记录了部分实践和自己的理解,理解不准确之处,还请教正。欢迎一起讨论学习。

参考资料:

《图解HTTP》

《HTTP权威指南》

Understanding The Vary Header

rfc2616

Disk Cache 3.0

http-caching

HTTP caching(MDN)

Caching Tutorial

What does Blink in-memory cache store?

HTTP 缓存简单了解的更多相关文章

  1. mysql查询缓存简单使用

    MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,属于 Oracle 旗下产品.MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBM ...

  2. php静态缓存简单制作

    制作缓存的目的是为了让我们的页面运行更加快速,减少读取数据库内容的次数,给用户更好的体验,为此我们可以使自己的程序做一下缓存,并且设置一个缓存过期的时间,来保证与数据库的一致,当然并不是所有的程序都适 ...

  3. C# Memcache分布式缓存简单入门

    什么是Memcache?能做什么? 以下是百度的观点: memcache是一套分布式的高速缓存系统,由LiveJournal的Brad Fitzpatrick开发,但目前被许多网站使用以提升网站的访问 ...

  4. 基于Annotation与SpringAOP的缓存简单解决方案

    前言: 由于项目的原因,需要对项目中大量访问多修改少的数据进行缓存并管理,为达到开发过程中通过Annotation简单的配置既可以完成对缓存的设置与更新的需求,故而设计的该简易的解决方案. 涉及技术: ...

  5. [项目回顾]基于Annotation与SpringAOP的缓存简单解决方案

    前言: 由于项目的原因,需要对项目中大量访问多修改少的数据进行缓存并管理,为达到开发过程中通过Annotation简单的配置既可以完成对缓存的设置与更新的需求,故而设计的该简易的解决方案. 涉及技术: ...

  6. Hibernatne 缓存中二级缓存简单介绍

    hibernate的session提供了一级缓存,每个session,对同一个id进行两次load,不会发送两条sql给数据库,但是session关闭的时候,一级缓存就失效了. 二级缓存是Sessio ...

  7. LRU cache缓存简单实现

    LRU cache LRU(最近最少使用)是一种常用的缓存淘汰机制.当缓存大小容量到达最大分配容量的时候,就会将缓存中最近访问最少的对象删除掉,以腾出空间给新来的数据. 实现 (1)单线程简单版本 ( ...

  8. java hashMap缓存简单实现

    直接上代码,干货: import java.util.HashMap; import java.util.Map; /** * map缓存 * @author ming * * @param < ...

  9. LRU缓存简单实现

    缓存接口定义 /** * 缓存接口 * * @author zhi * */ public interface ICache<K, V> { /** * 添加缓存数据 * * @param ...

随机推荐

  1. Java Optional orElse() 和 orElseGet() Optional.flatMap()和Optional.map()区别

    Java Optional 的 orElse() 和 orElseGet() 的区别 1. 接收的参数不同 orElse()方法以一个自定义类型的数据作为参数 public T orElse(T t) ...

  2. C++入门经典-例5.12-动态内存的销毁

    1:当申请一块堆内存后,系统不会再程序执行时一句情况自动销毁它.若想释放该内存,则需要使用delete关键字.下面的代码中,可以看出堆和栈的不同.代码如下: // 5.12.cpp : 定义控制台应用 ...

  3. js中filter过滤用法总结

    定义和用法 filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素. 注意: filter() 不会对空数组进行检测. 注意: filter() 不会改变原始数组 ...

  4. Windows UI Library - Roadmap Win UI3.0

    https://github.com/microsoft/microsoft-ui-xaml/blob/master/docs/roadmap.md 微软更新太快了.是不是要把开发人员折磨死.... ...

  5. golang gRPC初探

    gRPC使用protocol buffers作为Interface Definition Language (IDL). gRPC的底层信息交互格式也使用的是protocol buffers. 默认情 ...

  6. CountDownLatch用法实践

    项目中写多线程时,需要判断所有线程是否执行完毕,所以想到了添加累加器来判断.这个累加器使用什么变量,找到了以下2种方式. 1. 类似AtomicInteger这种提供原子操作的类型AtomicInte ...

  7. Http常见的响应头

    Location: http://www.it315.org/index.jsp   -表示重定向的地址,该头和302的状态码一起使用. Server:apache tomcat            ...

  8. android:layout_gravity 和 android:gravity 的区别?

    第一个是让该布局在其父控件中的布局方式,第二个是该布局布置其字对象的布局方式

  9. 集成ShareSdk一键分享和第三方登录

    在Mob官网http://mob.com/注册,创建应用,下载SDK,申请APP_key 根据官网开发指南导入SDK到你的项目中: 在assets/ShareSDk.xml中修改你的APP_key p ...

  10. pandas中根据列的值选取多行数据

    # 选取等于某些值的行记录 用 == df.loc[df['column_name'] == some_value] # 选取某列是否是某一类型的数值 用 isin df.loc[df['column ...