前端缓存
缓存概述
在计算机领域中,缓存是一项十分重要的技术。
在软件开发,亦或者是在硬件设计开发中,缓存对性能的影响是十分显著的。
学过Java,会知道在Integer的自动装箱中 [-128, 127]
这个范围中的转换会有些特殊的表现,稍加研究源码,会知道这是因为Integer中的缓存类有关(该缓存类会使用数组存储[-128, 127]范围内的常量)。当然,在实际开发中,可能存在Redis缓存,框架缓存等。
再有,cpu cache可能是最常听到的一种硬件缓存机制了。对于计算机专业的同学来说,cpu cache可能了解的更多些,大致如图,这里就不多表述了:
前端方向的缓存包括:浏览器缓存,HTTP缓存,DNS缓存,CDN缓存等。
有人会说,浏览器缓存和前端肯定是有关系的,但是HTTP不是个协议吗,这个怎么缓存?DNS我倒是知道,这玩意儿是请求解析URL对应的IP地址的,这个跟前端又有啥关系?而且这CDN……
前端缓存
提到前端缓存,很多人立刻能想到的是浏览器缓存,然后又把浏览器缓存划分两种:1. 强缓存,2. 协商缓存。如果再向下深入,就按下F12打开控制台切换到Network选项卡,然后指着那些请求的Header的每一个字段说:你看,这个Cache-Control和Expires是用来控制强缓存的,这个Last-Modified,ETag还有这些If-xxx-xxx都是用于协商缓存的。
再继续往下进行,就有人要说了:其实,这里还要划分缓存策略,浏览器按照发生的时间顺序划分策略,1. 存储策略,2. 过期策略,3. 协商策略,每个字段对应有不同的缓存策略,存在一个字段对应多个策略的情况。
到这里,就有人意识到了,这说了半天本质上都在讲根据HTTP协议制定的缓存机制,也就是常说的HTTP缓存,也是一种浏览器缓存。
那么,其他的呢?
客户端缓存
通常,客户端缓存指的是浏览器缓存,更具体一点,就是本节开头所提到的HTTP缓存。
DNS缓存
DNS是一个协议,用于域名URL到IP地址的映射,或者说是根据URL去查询IP地址。
在Windows下,我们可以在C:\window\system32\drivers\etc\目录下找到一个hosts文件,这个文件记录了URL到IP的转换,通常来说,我们不需要手动去修改这个文件,而是在网络协议中配置DNS服务器,通过DNS查询完成URL到IP的转换。
现在hosts还在继续使用,一般内网中会要求员工手动配置hosts文件,或者通过运行脚本进行hosts的写入,这样配置完成之后才可以访问某些内部网络。
简单来说,DNS协议的实现就是:客户端请求服务器得到结果,这个结果就是IP地址。
假设DNS解析请求发出到收到结果的时间是100ms,不加DNS缓存:
浏览器访问网站需要查询URL对应的IP地址,而网站的接口也需要进行查询IP,首先浏览器在DNS请求过程中等待,什么都不做,这时候浏览器只能保持白屏100ms,而在后续的接口请求中,我们还要进行DNS查询,这样的等待是没有意义的,而且这对DNS服务器带来的压力也不小……
但是在加了DNS缓存后,我们就可以直接在缓存中找到URL对应的IP,省去了等待时间,响应速度一下就上去了。
通常,DNS查询的时间在20ms左右。
现在的浏览器都实现了DNS缓存,不过采用的方式是不同的,至于具体采用了啥方式,这个我也不知道了,不过有一点,IE设置了30分钟的缓存时间,Chrome和FireFox则是设置了1分钟的DNS缓存有效期。但无论是30分钟还是1分钟,时间长短并不是区分其优劣性的因素。
时间设置的短:那么浏览器就对IP变化敏感,可以保证请求是正确的。
而时间设置的长了:那么就可以避免重复请求DNS服务器,节省时间。
NOTE:还有一个协议需要了解,就是ARP协议(地址解析协议),我们在得到了IP之后,需要进行TCP握手,这一步需要找到MAC地址,而ARP协议的作用就是IP到MAC的映射。
HTTP缓存、浏览器缓存
HTTP缓存就是老生常谈的东西了,HTTP本身只是一个协议,HTTP缓存则是浏览器实现的,在本文开篇就已经提到了,后文统称HTTP缓存。
浏览器通过设置或者读取HTTP头来实现对应的缓存机制:
- 强缓存
当请求命中强缓存时,浏览器不会将本次请求发往服务器,而是直接从缓存中读取内容,在Chrome中打开控制台,切换到Network选项卡,可以看到一个比较不一样的状态码信息 200 OK (from disk cache),如下图:
控制强缓存的字段主要是Cache-Control和Expires(HTTP 1.0标准)。
Cache-Control:常见的值有public,private,max-age,no-cache,no-store,must-revalidation;少见,但是用的也多的值有max-stale,min-fresh。
max-age,max-stale,min-fresh这三个值同时使用时,其设置独立生效,但是最保守的缓存策略总是有效(所谓最保守,你可以认为,缓存时间最短的总是有效)。
此外,no-cache字面意思是不缓存,但实际上,浏览器读到这个值之后,依旧会将资源缓存,在下次请求时检查资源是否有效,如果有效,那服务器就返回304状态码,浏览器读缓存;否则浏览器向服务器请求更新的资源。这与 Cache-Control: max-age, must-validation
效果相同。
以上各个字段的意义均可在MDN文档中查询到,这里不再赘述。
Expires:资源到期时间,这个时间是服务器的时间,所以这里就会出现一个问题,服务器时间和本地时间不一致。但问题不大,只是这样本地强缓存会失效而已……等等,本地时间和服务器时间不一致并不一定是本地时间超出了指定的到期时间,也有可能是本地时间被修改至到期时间之前,那么这不就使得本地缓存有效了吗?那不就可能存在本地缓存和服务器资源不一致的问题了?
NOTE:
Cache-Control:max-age
的优先级要比Expires高
如果某天看到HTTP请求头中不包含这两个字段同时也不存在其他缓存设置,是不是就用不到缓存了呢?当然不是,浏览器自身会实现一个启发式缓存算法,通常是取出响应头中Date和Last-Modified两个字段,计算其差值的10%作为缓存时间:(Date - Last-Modified) * 10%。
- 协商缓存
根据字面意思,就是前后端进行协商,校验缓存的有效性。
相对来说,这一策略就有很多字段能控制了,不过也很好记,基本都是 If-Xxx-Xxx
这种形式的,而If前缀则表示这个字段是用于判断(验证)的。
ETag:实体标签,服务器资源的唯一标识符,有点像哈希值。
Nginx官方采用的计算方式是“文件最后修改时间的16进制-文件长度的16进制”。
配合If-Match和If-None-Match使用,验证缓存的有效性。
通过If-Match配合Range,我们还可以实现文件的断点续传、文件分段下载、并行下载这些听上去挺高大上的功能,原理很简单,就是请求头通过 Range:bytes
请求资源的某一部分,而 If-Match:ETag
可以保证新新范围的请求和前一个请求来自相同的源(ETag不一致,那就说明资源不一致咯)。
Last-Modified:标记请求资源的最后一次修改时间,GMT时间(格林尼治标准时间),由此可知,该字段可以精确到秒级。此外,该字段记录资源最后的修改时间,但是并不会验证资源内容是否真的发生了变化(资源编译打包就会改变该字段的值)。配合If-Modified-Since和If-Unmodified-Since校验缓存的有效性。
这个Last-Modified字段除了验证本地缓存资源的有效性之外,倒还可以用于当前请求的服务器文档是否被修改,比如说石墨文档,腾讯文档等共享文档之类的,当然了,这些软件都有很多的其他机制保证编辑修改操作能够正确的进行下去。通过 If-Unmodified-Since:Last-Modified
、 If-Range:Etag | Date
再搭配上 Range:bytes
(没有Range字段,If-Range字段就会被忽略)可以保证只有服务器上的文档在没有修改的情况下执行更新,实现一个粗糙的文档协作。
NOTE:
Last-Modified
字段优先级比Etag
低。
到这里,我们可以知道缓存的优先级为:
强缓存>协商缓存,
Cache-Control>Expires>ETag>Last-Modified
多数人不对HTTP缓存和浏览器缓存区分的,或者说直接合在一起称为“浏览器HTTP缓存”,还有人直接称之为“前端缓存”,其实都在说同一个内容。
服务器缓存
提到服务器,一般来说都和后端是相关的,但是前端也必须要了解一些相关的知识,因为每次出现问题的时候,都是会在前端页面上显示出来,比如说接口500,这时候,测试就来到了我们身边,俯下身子在我们耳边吟唱死亡颂歌……
当然了,上面说的有些夸大,因为多数时候遇到5xx的问题,测试会直接找到后端排查,若是遇到参数传递的问题,后端才会报告给前端去解决。
CDN缓存
在 DNS缓存 中提到过为了得到IP地址,需要进行一次DNS查询。
在这里,DNS请求获取到的IP地址是服务器的IP地址,对于同一台服务器来说,接收处理的请求越来越多,那么服务器的负载也就越大,服务器对请求的响应可能就会超时。此外,不同地区访问网站的时延是不同的,若服务器在北京,用户在新疆或西藏地区,那么这个访问时延会非常大,用户等待的时间也就越长。
CDN全称是Content Distribute Network,即内容分发网络。在使用了CDN的情况下,上述的两种情况都能得到很好的解决。
CDN理论大致可表述如下:通过在 不同地区 建立 多个 节点服务器,使得用户的请求可以根据其所在地区、当前网络流量、服务器负载、网络连接等因素,被导向最佳的服务器节点。
其适用场景较为广泛,如站点加速,直播,点播等。
讲到这里,就应该对CDN有个模糊的概念:这以前没有CDN的时候,直接请求源站,现在有了CDN,那么请求肯定会被转发到其他服务器,而且这个服务器中的资源可能是一个源站资源的拷贝。咱们可以称这个服务器为 CDN节点 。
CDN缓存是指,存在一个缓存服务器,当浏览器向服务器请求资源时,并不是直接向源站服务器请求,而是被导向CDN边缘节点。在这个边缘节点中缓存了用户的数据以及源站服务器资源,他(边缘cache)负责直接响应最终用户的访问请求,将缓存在本地的内容迅速提供给用户。同时,既然缓存了源站服务器的资源,那么就会涉及到资源的一致性,即保证边缘节点与源站服务器内容同步。
说得简单点,CDN就是一个房产中介,他根据用户的诉求和他掌握的一些信息(如工作地点,交通情况,距离等)为用户提供一个合适的房子。
CDN系统 在功能上可划分为三大模块:分发服务,负载均衡,运营管理。本节所提到的CDN缓存其实只是从属于分发服务的一个小块。本节内容所说的内容只是一个小的范围-CDN分发服务系统中的边缘cache,并不是指代整个CDN系统。
总结
其实还有一种缓存没有提到,这种缓存是用来做离线页面的,而且是直接在HTML中主动使用的——Manifest,使用起来十分简单,只需要一行代码:
但是很多网站并没有使用这种技术,原因在于这个配置的文件上:
- 如果我们想要缓存页面的所有资源,只能手动将资源写入Manifest声明的配置文件中进行缓存,而不能使用通配符缓存,这太麻烦了。
- 好不容易把Manifest配置文件写好了,浏览器这边访问起来也很快,这时候,网站的资源更新了,这个缓存会失效吗?不会的,如果不清除缓存,即使连上网络,资源也不会自动更新,浏览器加载的页面还是旧的页面。我们需要改变配置文件的名字,然后,才会更新本地缓存,通常会增加版本号或者使用hash的命名方式。
- 解决了这些问题,你又会发现,Manifest在不同设备,不同浏览器上可能存在大小限制。
综上,Manifest的使用还是比较麻烦的,而且用处看上去不大,但是对于一些静态网页,Manifest就显得比较有用了。
此外,还有一些没有提到的缓存技术,如代理服务器缓存,反代理服务器缓存等。