浏览器端的缓存规则
对于浏览器端的缓存来讲,这些规则是HTTP协议头
和HTML页面的Meta标签
中定义的。他们分别从新鲜度和校验值两个维度来规定浏览器是否可以直接使用缓存中的副本,还是需要去原服务器获取更新的版本。
新鲜度(过期机制):也就是缓存副本有效期。一个缓存副本必须满足一下几个条件,浏览器会认为它是有效的,足够新的:
- 含有完整的过期时间控制头信息(HTTP协议报头),并且仍在有效期内;
- 浏览器已经使用过这个缓存副本,并且在一个会话中已经检查过新鲜度。
满足以上两个情况的一种,浏览器会直接从缓存中获取副本并渲染。
校验值(验证机制):服务器返回资源的时候有时在控制头信息带上这个资源的实体标签Etag(Entity Tag)
,它可以用来作为浏览器再次请求过程的校验标识。如果发现校验标识不匹配,说明资源已经被修改或过期,浏览器需要重新获取资源内容。
缓存的作用
- 减少网络带宽消耗
- 降低服务器压力
- 减少网络延迟,加快页面打开速度
HTTP缓存机制
缓存行为主要由缓存策略决定,而缓存策略由内容拥有者设置。这些策略主要通过特定的HTTP头部来清晰的表达。
当一个用户发起一个静态资源请求的时候,浏览器会通过以下几个步骤来获取资源:
- 本地缓存阶段:现在本地查找该资源,如果有发现该资源,而且该资源还没有过期,就使用这一个资源,完全不会发送http请求到服务器;
- 协商缓存阶段:如果在本地缓存找到对应的资源,但是不知道该资源是否过期或者已经过期,则发一个http请求到服务器,然后服务器判断这个请求,如果请求的资源在服务器上没有改动过,则返回304,让浏览器使用本地找到的那个资源。
- 缓存失败阶段:当服务器发现请求的资源已经修改过,或者这是一个新的请求(在本来没有找到资源),服务器则返回该资源的数据,并且返回200,当然这个是值找到资源的情况下,如果服务器上没有这个资源,则返回404。
强制缓存
强制缓存就是想浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程,强制缓存的情况主要有三种,如下:
- 不存在该缓存结果和缓存标识,强制缓存失效,则直接向服务器发起请求(跟第一次发起请求一致)。
- 存在该缓存结果和缓存标识,但该结果已失效,强制缓存失败,则使用协商缓存。
- 存在该缓存结果和缓存标识,且该结果尚未失效,强制缓存生效,直接返回该结果。
当浏览器想服务器发起请求时,服务器会将缓存规则放入HTTP响应报文的HTTP头中和请求结果一起返回给浏览器,控制强制缓存的字段分别是Expires和Cache-Control,其中Cache-Control优先级比Expires高。
Expires
Expires是HTTP/1.0控制网页缓存的字段,其值为服务器返回该请求结果缓存的到期时间,即在此发起该请求时,如果客户端的时间小于Expires的值时,直接使用缓存结果。
到了HTTP/1.1,Expire已经被Cache-Control替代,原因在于Expires控制缓存的原理是使用客户端的时间与服务端返回的时间做对比,那么如果客户端与服务端的时间因为某些原因(例如时区不同,客户端和服务端有一方的时间不准确)发生误差,那么强制缓存会直接失效,这样的话强制缓存的存在则毫无意义,那么Cache-Control又是如何控制的呢?
Cache-Control
在HTTP/1.1中,Cache-Control是最重要的规则,主要用于控制网页缓存,主要取值为:
- public:所有内容都将被缓存(客户端和代理服务器都可缓存)
- private:所有内容只有客户端可以缓存,Cache-Control的默认取值。
- no-cache:客户端缓存内容,但是是否使用缓存则需要经过协商缓存来验证决定
- no-store:所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存。
- max-age=xxx (xxx is number):缓存内容将在xxx秒后失效。
cache-control: max-age=600
expires: Mon, 16 Apr 2018 01:41:50 GMT
由这个例子可以知道:
- HTTP响应报文中expires的时间值,是一个绝对值。
- HTTP响应报文中Cache-Control为max-age=600,是相对值。
由于Cache-Control的优先级比expires高,那么直接根据Cache-Control的值进行缓存,意思就是说在600秒内再次发起该请求,则会直接使用缓存结果,强制缓存生效。
注:在无法确定客户端的时间是否与服务端的时间同步的情况下,Cache-Control相比于expires是更好的选择,所以同时存在时,只有Cache-Control生效。
缓存保存的位置
缓存有内存缓存和硬盘缓存两种
- 内存缓存(from memory cache):内存缓存具有两个特点,分别是快速读取和时效性;
- 快速读取:内存缓存会将编译解析后的文件,直接存入该进程的内存中,占据该进程一定的内存资源,以方便下次运行使用时的快速读取。
- 时效性:一旦该进程关闭,则该进程的内存则会清空。
- 硬盘缓存(from disk cache):硬盘缓存则是直接将缓存写入硬盘文件中,读取缓存需要对该缓存存放的硬盘文件进行I/O操作,然后重新解析该缓存内容,读取复杂,速度比内存缓存慢。
在浏览器中,浏览器会在js和图片等文件解析执行后直接存入内存缓存中,那么当刷新页面时只需直接从内存缓存中读取(from memory cache);而css文件则会存入硬盘文件中,所以每次渲染页面都需要从硬盘读取缓存(from disk cache)。
协商缓存
协商缓存就是强制缓存失效后,浏览器携带缓存标识想服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况:
-
协商缓存生效,返回304,即数据未更改可以继续使用。
-
协商缓存失效,返回200,数据已发生更改,并返回客户端新的数据。
同样,协商缓存的标识也是在响应报文的HTTP头中和请求结果一起返回给浏览器的,控制协商缓存的字段有:Last-Modified/ If-Modified-Since和Etag / If-None-Match,其中Etag / If-None-Match的优先级比Last-Modified / If-Modified-Since高。
Last-Modified / If-Modified-Since
Last-Modified是服务器相应请求时,返回该资源文件在服务器最后被修改的时间,。
If-Modified-Since则是客户端再次发起该请求时,携带上次请求返回的Last-Modified值,通过此字段值告诉服务器该资源上次请求返回的最后被修改时间。服务器收到请求,发现请求头含有If-Modified-Since字段,则会根据If-Modified-Since的字段值与该资源在服务器的最后修改时间做对比,若服务器的资源最后被修改时间大于If-Modified-Since的字段值,则重新返回资源,状态码为200;否则则返回304,代表资源无更新,可继续使用文件。
Etag / If-None-Match
Etag是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),如下。
If-None-Match是客户端再次发起该请求时,携带上次请求返回的唯一标识Etag值,通过此字段值告诉服务器该资源上次请求返回的唯一标识值。服务器收到该请求后,发现该请求头中含有If-None-Match,则会根据If-None_match的字段值与该资源在服务器的Etag值做对比,一致则返回304,代表资源无更新,继续使用缓存文件;不一致则重新返回资源文件,状态码为200。
tips:Etag / If-None-Match优先级高于Last-Modified / If-Modified-Since,同时存在则只有Etag / If-None-Match生效。
Etag/LastModified过程如下
- 客户端请求一个页面(A)。
- 服务器返回页面A,并在给A加上一个Last-Modified/Etag。
- 客户端展现该页面,并将页面连同Last-Modified/Etag一起缓存。
- 客户端再次请求页面A,并将上次请求时服务器返回的Last-Modified/Etag一起传递给服务器。
- 服务器检查该Last-Modified或Etag,并判断出该页面自上次客户端请求之后还未被修改,直接返回响应304和一个空的响应体。
Etag主要为了解决Last-Modified无法解决的一些问题:
- 一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新GET;
- 某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说1s内修改了N次),If-Modified-Since能检查到的力度是s级的,这种修改无法判断(或者说UNIX记录MTIME只能精确到秒);
- 某些服务器不能精确的得到文件的最后修改时间。
其他标签
Content-Length:尽管并没有在缓存中明确设计,Content-Length头部在设置缓存策略时很重要。某些软件如果不提前获知内容的大小以留出足够空间,则会拒绝缓存该内容。
Vary:缓存系统同行使用请求的主机和路径作为存储该资源的键。当判断一个请求是否是请求同样内容时,Vary头部被用来提醒缓存系统需要注意另一个附加头部。它通常被用来告诉缓存系统同样注意Accept-Encoding头部,以便缓存系统能够区分压缩和未压缩的内容。
服务器端的缓存
CDN缓存
CDN缓存,也叫网关缓存、反向代理缓存。浏览器先向CDN网关发起web请求,网关服务器后面对应着一台或多态负载均衡源服务器,会根据它们的负载请求,动态地请求转发到合适的源服务器上。
CND缓存策略
CDN边缘节点缓存策略因服务商不同而不同,但一般都会遵循http标准协议,通过http响应头中的Cache-control:max-age
的字段来设置CDN边缘节点数据缓存时间。
当客户端向CDN节点请求数据时,CDN节点会判断缓存数据是否过期,若缓存数据并没有过期,则直接将缓存数据返回给客户端;否则,CDN节点就会向源站发出回源请求(back to the source request)
,从源站拉去最新数据,更新本地缓存,并将最新数据返回给客户端。
CDN服务商一般会提供基于文件后缀、目录多个维度来指定CDN缓存时间,为用户提供更精细化的缓存管理。
CDN缓存时间会对回源率
产生直接的影响。若CDN缓存时间较多,CDN边缘节点上的数据会经常失效,导致频繁回源,增加了源站的负载,同时也增打了访问延时;若CDN缓存时间太长,会带来数据更新时间慢的问题。开发者需要增对特定的业务,来做特定的数据缓存时间管理。
CDN缓存刷新CDN边缘节点对开发者是透明的,相比于浏览器Ctrl+F5的强制刷新来使浏览器本地缓存失效,开发者可以通过CDN服务商提供的“刷新缓存”接口来达到清理CDN边缘节点缓存的目的。这样开发者在更新数据后,可以使用“刷新缓存”功能来强制CDN节点上的数据缓存过期,保证客户端在访问时,拉取到最新的数据。
CDN的优势
- CDN节点解决了跨运营商和跨地域访问的问题,访问延时大大降低;
- 大部分请求在CDN边缘节点完成,CDN祈祷了分流作用,减轻了源站的负载。
CDN缓存的缺点
当网站更新时,如果CDN节点上数据没有及时更新,即便用户在浏览器使用Ctrl+F5的方式使浏览器端的缓存失效,也会因为CDN边缘节点没有同步最新数据而导致用户访问异常。
Combo服务
Combo服务,也就是我们在最终拼接生成页面资源引用的时候,并不是生成多个独立的link标签,而是将资源地址拼接成一个url路径,请求一种线上的动态资源合并服务,从而实现减少HTTP请求的需求。
/??file1,file2,file3,...
的url请求响应就是动态combo服务提供的,它的原理很简单,就是根据url找到对应的多个文件,合并成一个文件来响应请求,并将其缓存,以加快访问速度。
但它也存在一些缺陷:
- 浏览器有url长度限制,因此不能无限制的合并资源。
- 如果用户在网站内有公共资源的两个页面间跳转访问,由于两个页面的combo的url不一样导致用户不能利用浏览器缓存来加快对公共资源的访问速度。如果combo的url中任何一个文件发生改变,都会导致整个url缓存失效,从而导致浏览器缓存利用率降低。
HTML5缓存思路
html5离线应用缓存manifest
- 用户可离线访问你的应用,这对于无法随时保持联网状态的移动终端用户来说尤其重要
- 用户访问本地的缓存文件,通常意味着更快的访问速度。
- 仅仅加载被修改过的资源,避免同一资源对服务器多次的请求,大大降低了对服务器的访问压力。
manifest文件罗列了需要被缓存的文件清单。
CACHE MANIFEST
# wanz app v1
# 指明缓存入口
CACHE:
index.html
images/logo.png
scripts/main.js
# 以下资源必须在线访问
NETWORK:
login.php
# 如果index.php无法访问则用404.html代替
FALLBACK:
/index.php /404.html
这个过程中有几个问题需要注意:
- 如果服务器对离线的资源进行了更新,那么必须更新manifest文件之后这些资源才能被浏览器重新下载,如果只是更新了资源而没有更新manifest文件的话,浏览器并不会重新下载资源,也就是说还是使用原来离线存储的资源。
- 对于manifest文件进行缓存的时候需要十分小心,因为可能出现一种情况就是你对manifest文件进行了更新,但是http的缓存规则告诉浏览器本地缓存的manifest文件还没过期,这个情况下浏览器还是使用原来的manifest文件,所以对于manifest文件最好不要设置缓存。
- 浏览器在下载manifest文件中的资源的时候,它会一次性下载所有资源,如果某个资源由于某种原因下载失败,那么这次的所有更新就算是失败的,浏览器还是会使用原来的资源。
- 在更新了资源之后,新的资源需要到下次再打开app才会生效,如果需要资源马上就能生效,那么可以使用
window.applicationCache.swapCache()
方法来使之生效,出现这种现象的原因是浏览器会先使用离线资源加载页面,然后再去检查manifest是否有更新,所以需要到下次打开页面才能生效。
LocalStorage
localStorage.fresh = "vfresh.org"; // 设置一个键值
var a = localStorage.fresh; // 获取键值
// API
// 清空storage
localStorage.clear();
//设置一个键值
localStorage.setItem("fresh", "vfresh.org");
// 获取一个键值
localStorage.getItem("fresh");
// return "vfresh.org" // 获取指定下表的键的名称(如同Array)
localStorage.key(0);
// return "fresh" // 删除一个键值
localStorage.removeItem("fresh");
与sessionStorage主要的区别是存储时间和作用域。
另外,严格来说localStorage更像cookie一类的本地数据存储。但是在标准缓存之外,开发人员可以用浏览器的一些功能来实现自定义的客户端“缓存”。
构建可缓存站点的建议
- 同一个资源保证URL的稳定性
- 给CSS、JS、图片等资源增加HTTP缓存头,并强制入口HTML不背缓存。
- 减少对Cookie的依赖
- 减少对HTTPS加密协议的使用
- 多用GET方式请求动态Cgi。
- 动态Cgi也是可以被缓存
引用
网友评论