存储在硬盘上的缓存会严格根据 HTTP 头信息中的各类字段来判定哪些资源可以缓存,哪些资源不可以缓存;哪些资源是仍然可用的,哪些资源是过时需要重新请求的。当命中缓存之后,浏览器会从硬盘中读取资源,虽然比起从内存中读取慢了一些,但比起网络请求还是快了不少的。绝大部分的缓存都来自 disk cache。 凡是持久性存储都会面临容量增长的问题,disk cache 也不例外。在浏览器自动清理时,每个浏览器都会识别“最老的”和“最可能过时的”资源。
3、Service Worker
Service Worker 是 Chrome 团队提出和力推的一个 WEB API,用于给 web 应用提供高级的可持续的后台处理能力。 service worker 能够操作的缓存是有别于浏览器内部的 memory cache 或者 disk cache。它是独立于当前页面的一段运行在浏览器后台进程里的脚本。大家可以把 Service Worker 理解为一个介于客户端和服务器之间的一个代理服务器。在 Service Worker 中我们可以做很多事情,比如拦截客户端的请求、向客户端发送消息、向服务器发起请求等等,离线资源缓存只是它的作用之一。 这个缓存是永久性的,即关闭 TAB 或者浏览器,下次打开依然还在(而 memory cache 不是)。有两种情况会导致这个缓存中的资源被清除:手动调用 API cache.delete(resource) 或者容量超过限制,被浏览器全部清空。 Service Worker 特点 网站必须使用 HTTPS。除了使用本地开发环境调试时(如域名使用 localhost) 运行于浏览器后台,可以控制打开的作用域范围下所有的页面请求 单独的作用域范围,单独的运行环境和执行线程不能操作页面 DOM,但可以通过事件机制来处理。
浏览器缓存知识
web缓存
缓存是指存储指定资源的一份拷贝,并在下次请求该资源时提供该拷贝的技术。当 web 缓存发现请求的资源已经被存储,它会拦截请求,返回该资源的拷贝,而不会去源服务器重新下载。
Web缓存类型
在Web应用领域,Web缓存大致可以分为以下几种类型:
数据库缓存
Web应用,特别是SNS类型的应用关系比较复杂、数据库表繁多,需频繁进行数据库查询,很容易导致数据库不堪重荷。为了提高查询的性能,将查询后的数据放到内存中进行缓存,方便下次查询直接从内存缓存返回,快速响应。比如常用的缓存方案 memcached,redis。
服务器端缓存
代理服务器是浏览器和源服务器之间的中间服务器,浏览器先向这个中间服务器发起Web请求,经过处理后(比如权限验证,缓存匹配等),再将请求转发到源服务器。代理服务器缓存的运作原理跟浏览器的运作原理差不多,只是规模更大。可以把它理解为一个共享缓存,不只为一个用户服务,一般为大量用户提供服务,因此在减少相应时间和带宽使用方面很有效,同一个副本会被重用多次。常见代理服务器缓存解决方案有 Squid 等。
CDN(Content delivery networks)缓存,也叫网关缓存、反向代理缓存。CDN缓存一般是由网站管理员自己部署,为了让他们的网站更容易扩展并获得更好的性能。浏览器先向CDN网关发起Web请求,网关服务器后面对应着一台或多台负载均衡源服务器,会根据它们的负载请求,动态将请求转发到合适的源服务器上。虽然这种架构负载均衡源服务器之间的缓存没法共享,但却拥有更好的处扩展性。从浏览器角度来看,整个CDN就是一个源服务器。
浏览器缓存
浏览器缓存根据一套与服务器约定的规则进行工作,在同一个会话过程中会检查一次并确定缓存的副本足够新。如果你浏览过程中,比如前进或后退,访问到同一个图片,这些图片可以从浏览器缓存中调出而即时显现。
浏览器缓存有那些
1、HTTP 缓存是基于 HTTP 协议的浏览器文件级缓存机制
2、HTML5 Web SQL 这种方式部分主流浏览器支持,从2010年11月18日W3C宣布舍弃 Web SQL database 草案开始
3、HTML5 indexedDB 是一个为了能够在客户端存储可观数量的结构化数据,并且在这些数据上使用索引进行高性能检索的 API
4、Cookie 一般网站为了辨别用户身份、进行 session 跟踪而储存在用户本地终端上的数据(通常经过加密)
5、localStorage 是 HTML5 的一种新的本地缓存方案,目前用的比较多,一般用来存储ajax返回的数据,加快下次页面打开时的渲染速度
6、sessionStorage 和 localStorage 类似,但是浏览器关闭则会全部删除,api和localstorage相同,实际项目中使用较少。
7、Application Cache (AppCache) 接口设定浏览器应该缓存的资源放在 manifest 配置文件中并使得离线用户可用,不过目前已从 Web 标准中删除
8、cacheStorage是在 ServiceWorker 的规范中定义的,可以保存每个serverWorker申明的cache对象
浏览器缓存存放位置
1、memory cache
按照操作系统的常理:先读内存,再读硬盘。几乎所有的网络请求资源都会被浏览器自动加入到 memory cache 中。但是也正因为数量很大但是浏览器占用的内存不能无限扩大这样两个因素,memory cache 注定只能是个“短期存储”。常规情况下,浏览器的 TAB 关闭后该次浏览的 memory cache 便告失效 (为了给其他 TAB 腾出位置)。而如果极端情况下 (例如一个页面的缓存就占用了超级多的内存),那可能在 TAB 没关闭之前,排在前面的缓存就已经失效了。 memory cache 机制保证了一个页面中如果有两个相同的请求 (例如两个 src 相同的 ,两个 href 相同的 )都实际只会被请求最多一次,避免浪费。 在从 memory cache 获取缓存内容时,浏览器会忽视例如 max-age=0, no-cache 等头部配置。 如果站长是真心不想让一个资源进入缓存,就连短期也不行,那就需要使用 no-store。 思考:刚才提到 几乎所有的网络请求资源都会被浏览器自动加入到 memory cache 中,为什么? preloader,在2007年之前,许多情况下,浏览器某些元素的解析和执行可能会影响紧随其后的资源,浏览器停止并等待资源完成下载,然后再获取下一个资源。这意味着如果一个页面包含多个JavaScript资源或外部CSS资源,在它们之后的元素,解析器将暂停并等待每个外部资源完成下载,然后再获取下一行内容。随着页面越来越繁琐的JavaScript,对性能的影响是巨大的,并且是灾难性的。所以在资源解析执行时候,网络状态是空闲的,能不能边解边请求资源呢,在2008年,ie/谷歌等浏览器便有了相关的机制(预加载器机制),预加载的资源都是放在memory cache中的。
2、disk cache
存储在硬盘上的缓存会严格根据 HTTP 头信息中的各类字段来判定哪些资源可以缓存,哪些资源不可以缓存;哪些资源是仍然可用的,哪些资源是过时需要重新请求的。当命中缓存之后,浏览器会从硬盘中读取资源,虽然比起从内存中读取慢了一些,但比起网络请求还是快了不少的。绝大部分的缓存都来自 disk cache。 凡是持久性存储都会面临容量增长的问题,disk cache 也不例外。在浏览器自动清理时,每个浏览器都会识别“最老的”和“最可能过时的”资源。
3、Service Worker
Service Worker 是 Chrome 团队提出和力推的一个 WEB API,用于给 web 应用提供高级的可持续的后台处理能力。 service worker 能够操作的缓存是有别于浏览器内部的 memory cache 或者 disk cache。它是独立于当前页面的一段运行在浏览器后台进程里的脚本。大家可以把 Service Worker 理解为一个介于客户端和服务器之间的一个代理服务器。在 Service Worker 中我们可以做很多事情,比如拦截客户端的请求、向客户端发送消息、向服务器发起请求等等,离线资源缓存只是它的作用之一。 这个缓存是永久性的,即关闭 TAB 或者浏览器,下次打开依然还在(而 memory cache 不是)。有两种情况会导致这个缓存中的资源被清除:手动调用 API cache.delete(resource) 或者容量超过限制,被浏览器全部清空。 Service Worker 特点 网站必须使用 HTTPS。除了使用本地开发环境调试时(如域名使用 localhost) 运行于浏览器后台,可以控制打开的作用域范围下所有的页面请求 单独的作用域范围,单独的运行环境和执行线程不能操作页面 DOM,但可以通过事件机制来处理。
HTTP 缓存分类
1.非验证性缓存,或者称为强缓存,用 Cache-Control 、 Expires 、 Pragma(比较老的版本兼容写法) 来控制,其特点是一旦有效就在有效期内不会发任何请求到服务器
2.验证性缓存,也叫协商缓存,用 ETag 、 Last-Modified 、 If-None-Match 、 If-Modified-Since 来控制,其特点是会发一个请求给服务器来确认缓存是否有效,如果有效就返回 304 ,省去传输内容的时间。
从描述上很容易看出来,非验证性缓存的优先级是高于验证性缓存的,因为有验证性缓存存在就根本不会发请求,自然也没有什么 If-None-Match 之类的东西出现的机会了。
非验证性缓存
HTTP 1.0: 基于Pragma和Expires的缓存实现
在 http1.0 时代,给客户端设定缓存方式可通过两个字段——“Pragma”和“Expires”来规范。目前这两个字段早可抛弃,但为了做http协议的向下兼容,我们还是可以看到很多网站依旧会带上这两个字段。
Pragma
当该字段值为“no-cache”的时候(事实上现在RFC中也仅标明该可选值),会知会客户端不要对该资源读缓存,即每次都得向服务器发一次请求才行。
Expires
Expires
是RFC 2616(HTTP/1.0)协议中和网页缓存相关字段,用来控制缓存的失效日期。 Expires 头指定了一个日期/时间, 在这个日期/时间之后,HTTP响应被认为是过时的; 无效的日期,比如 0, 代表着一个过去的事件,即该资源已经过期了。Cache-Control
HTTP1.1新增了
Cache-Control
来定义缓存过期时间。Cache-Control
在 HTTP 响应头中(通用首部字段),用于指示代理和 UA 使用何种缓存策略。 比如no-cache
为不可缓存、private
为仅 UA 可缓存,public
为大家都可以缓存。报文中同时出现了Expires 和 Cache-Control,会以 Cache-Control 为准。在RFC中规范了 Cache-Control 的格式为:常用 cache-directive 值
常见 Response 内的 Cache-Control
验证性缓存
协商缓存会根据[last-modified/if-modified-since]或者[etag/if-none-match]来进行判断缓存是否过期。
Last-Modified
服务器将资源传递给客户端时,会将资源最后更改的时间以
“Last-Modified: GMT”
的形式加在实体首部上一起返回给客户端。客户端会为资源标记上该信息,下次再次请求时,会把该信息附带在请求报文中一并带给服务器去做检查,若传递的时间值与服务器上该资源最终修改时间是一致的,则说明该资源没有被修改过,直接返回304状态码即可。
传递标记的最终修改时间的请求报文首部字段一共有两个:
该请求首部告诉服务器如果客户端传来的最后修改时间与服务器上的一致,则直接返回304 和响应报头即可。
当前各浏览器均是使用的该请求首部来向服务器传递保存的 Last-Modified 值。
告诉服务器,若Last-Modified没有匹配上(资源在服务端的最后更新时间改变了),则应当返回412(Precondition Failed) 状态码给客户端。
ETag
ETag HTTP响应头是资源的特定版本的标识符。ETag是http协议提供的若干机制中的一种Web缓存验证机制,并且允许客户端进行缓存协商。生成ETag常用的方法包括对资源内容使用抗碰撞散列函数,使用最近修改的时间戳的哈希值,甚至只是一个版本号。
ETag能够解决last-modified的一些缺点,但是etag每次服务端生成都需要进行读写操作,而last-modified只需要读取操作,从这方面来看,etag的消耗是更大的。
If-None-Match: ETag-value
告诉服务端如果 ETag 没匹配上需要重发资源数据,否则直接回送304 和响应报头即可。当前各浏览器均是使用的该请求首部来向服务器传递保存的 ETag 值。
If-Match: ETag-value
告诉服务器如果没有匹配到ETag,或者收到了“*”值而当前并没有该资源实体,则应当返回412(Precondition Failed) 状态码给客户端。否则服务器直接忽略该字段。
缓存头部对比
2、以时刻标识失效时间。
2、存在版本问题,到期之前的修改客户端是不可知的。
2、比Expires多了很多选项设置。
2、存在版本问题,到期之前的修改客户端是不可知的。
2、以时刻作为标识,无法识别一秒内进行多次修改的情况。
3、某些服务器不能精确的得到文件的最后修改时间。
2、不存在版本问题,每次请求都会去服务器进行校验。
2、分布式服务器存储的情况下,计算ETag的算法如果不一样,会导致浏览器从一台服务器上获得页面内容后到另外一台服务器上进行验证时发现ETag不匹配的情况。
缓存头部对比
从浏览器请求到展示资源的过程:
用户行为对浏览器缓存的控制
地址栏访问重载
地址栏重新输入当前页面地址并按下回车也会当做刷新处理, 这意味着只有从新标签页或超链接打开时,才能观察到直接使用硬盘缓存的情况。
正常重新加载
按下刷新按钮或快捷键(在 MacOS 中是 Cmd+R)会触发浏览器的“正常重新加载”(normal reload), 此时浏览器会执行一次 Conditional GET。
Cache-Control
等缓存头字段会被忽略,并且带If-None-Match
,If-Modified-Since
等头字段。 此时服务器总会收到一次 HTTP GET 请求。 在 Chrome 中按下刷新,浏览器还会带如下请求头:强制重新加载
在 Chrome 中按下 Cmd+Shift+R (MacOS)可以触发强制重新加载(Hard Reload), 此时包括页面本身在内的所有资源都不会使用缓存。 浏览器直接发送 HTTP 请求且不带任何条件请求字段。
缓存运用情况
目前,各大公司对静态资源基本都采用 CDN 强缓存处理,通过加大 Cache-Control 的 max-age 有效值(例如京东设置的max-age为315360000一年),那输入URL按回车,我们会始终看到200 OK (form cache)缓存信息,知乎解释。
但同时也会对一些不经常变更的静态资源做 ETag 协商缓存处理,以达到优化作用。
缓存配置实践
Nginx 缓存配置
nginx proxy_cache
NodeJS 缓存配置
QA:遇到的缓存问题
1.from disk cache 和 from memory cache 区别
Chrome在高版本中缓存策略之后, 当看到的 200 from memory cache 就是验证性缓存 ,而 200 from disk cache 就是非验证性缓存,可通过两个浏览器访问对比得出
2.只设置Etag,那么为什么在 Chrome 下会有非验证性缓存呢?
没有设置 Cache-Control 这个头,其默认值是 Private ,在标准中明确说了:
如果没有 Cache-Control 进行限制,缓存系统可以对一个成功的响应进行存储
很显然, Chrome 是遵守标准的,它在没有检查到 Cache-Control 的时候对响应做了非验证性缓存,所以你看到了 200 from memory cache 同时 Safari 也是遵守标准的,因为标准只说了可以进行存储,而非应当或者必须,所以 Safari 不进行缓存也是合理的
我们可以理解为,没有 Cache-Control 的情况下,缓存不缓存就看浏览器高兴,你也没什么好说的。那么你如今的需求是“明确不要非验证性缓存”,则从标准的角度来说,你必须指定相应的 Cache-Control 头
查看浏览器缓存文件: chrome://view-http-cache/
3.常见Cache-Control 的 max-age 有效值设置
365天
30天
4.Response Header 中 Age 与 Date
Age表示命中代理服务器的缓存. 它指的是代理服务器对于请求资源的已缓存时间, 单位为秒.
Date指的是响应生成的时间,请求经过代理服务器时, 返回的Date未必是最新的, 通常这个时候, 代理服务器将增加一个Age字段告知该资源已缓存了多久。
5.Cache-Control设置强缓存计算过期时间
过期日期 = Date + max-age ,所得出的日期
6.CAUTION: Provisional headers are shown
出现这个警告,是因为去获取该资源的请求其实并(还)没有真的发生,所以 Header 里显示的是伪信息,直到服务器真的有响应返回,这里的 Header 信息才会被更新为真实的。
参考资料:
Hypertext Transfer Protocol rfc2616 -- HTTP/1.1
IndexedDB 浏览器存储限制和清理标准
Caching Tutorial 缓存指南
Cache-control
Web缓存机制系列
浅谈 web 缓存
浅谈浏览器http的缓存机制
HTTP 缓存
使用 HTTP 缓存:Etag, Last-Modified 与 Cache-Control
合理使用 HTTP 缓存
NGINX缓存使用官方指南
ASP.NET Core 缓存技术 及 Nginx 缓存配置
网页性能优化:设置永久缓存
HTTP缓存控制小结
你应该知道的浏览器缓存知识