一、前言
缓存~缓存~缓存~ 它作为性能优化的一种方式,那它存在于哪里?它是怎么进行优化的?目前工作中用在了哪里?
针对上面的问题,大家可能对缓存的定义以及作用有所了解,但是工作中又是如何和产生交集的呢?
实际场景:
支付列表中基本都会存在“去支付”按钮,理所应当的,我们点击“去支付”的时候会跳转到支付页面,当然此处也有可能在当前页面直接调用支付功能,但是我们假装不存在,此时如果我们不选择支付,或者已经支付完毕,这时候就会引导用户回跳到支付列表页面,那么现在的支付列表中的对应数据应该会流转状态至“待付款”或“已支付”。
上面的一切都感觉理所应当,因为我觉得回转到上一个页面了,页面刷新,接口重新调用,没有理由状态不会改变,除非接口出现了问题,流转状态失败,但是问题来了,接口数据已经变化,偏偏我的页面数据状态没有任何改变,重新刷新也是无效的,为什么?
原因:
接口数据已经重新返回且能确定是最新的数据,那么这时候就要考虑页面渲染问题,按照现在的框架,无论是Vue
还是React
,其都是响应式视图,即数据发生变化,视图就会更新。那么视图没有及时更新的原因也就是因为数据没有变化了,经过一番研究,应该是浏览器缓存在作祟。
当我跳转到支付页面,不论我是否支付成功,当我返回到上一个页面的时候,路由参数没有任何变化,这时候浏览器就会自动认为我需要的数据是不会有任何变化的,没有必要重新获取接口数据在进行渲染,所以直接获取到缓存中的数据返回到页面上,浏览器的这种行为也是为了提高性能,毕竟从缓存中获取数据是比从接口中获取数据要快的,但是就是因为这种缓存机制,导致我们无法更新页面。
解决方法:
既然浏览器是根据URL参数来判断我所需要的数据是否有改变,那我保证URL的参数每次都进行改变,那么浏览器就不会默认采用缓存中的数据,那我们可以在URL
上拼接一个_v=Math.random()
或者_v=new Date()
,只要保证参数有变化就好了,这时候发现即使这样操作,浏览器还是默认使用缓存中的数据,究其原因,其实是因为我们把页面缓存和HTTP
缓存搞乱了,接口数据使用缓存,那我们应该是给接口地址拼接一个随机参数才可以解决这个问题,所以在请求接口的时候拼接_v=Math.random()
或者_v=new Date()
,以上使用缓存数据的问题就解决啦!
杂七杂八:
浏览器缓存在前后端没有分离时期是非常有效的一种性能优化方式,由于在前后端没有分离的时候,前端的很多数据都是依赖于服务的,所以此时对没有变化的页面进行缓存是一种非常节省性能的方式。
但是针对目前前后端分离的项目,像目前Vue
和React
框架,其打包完成之后体积是较小的,而且我们访问的入口就是index.html
,所以这种情况下,浏览器缓存好像在开发过程中“消失”了一般。因为要想实现浏览器缓存,是需要前后端就行配合的,然后我们在工作中应该几乎不需要这种配合了。
上面讲Vue
和React
框架是通过index.html
来引用js
、css
、图片等静态资源的,由于浏览器默认就会使用缓存策略,每次访问index.html
的时候参数也不会改变,那岂不是会出现即使静态资源改变了,还是会使用浏览器的缓存,这时候就非常简单粗暴了,在ngnix
代理过程中直接关闭浏览器缓存就可以了即使用no-cache no-store
。
疑惑:
浏览器缓存是划分为强制缓存和协商缓存,但是针对上面的场景,好像并不完全属于这两种的其一,所以此处也欢迎各位帮忙解答。🙏🙏🙏
噔~噔~噔~说明完上面的问题,接下来还是说说浏览器缓存这种老生常谈的事情吧~
二、浏览器缓存
1、HTTP
缓存
HTTP
缓存应该算是前端开发中最常按触的缓存机制之一,它又可细分为强制缓存与协商缓存,二者最大的区别在于判断缓存命中时,浏览器是否需要向服务器端进行询问以协商缓存的相关信息,进而判断是否需要就响应内容进行重新请求。
1.1、强制缓存
对于强制缓存而言,如果浏览器判断所请求的目标资源有效命中,则可直接从强缓存中返回请求响应,无须与服务器进行任何通信。 在介绍强制缓存命中判断之前,我们首先来看一段响应头的部分信息:
access-control-allow-origin: *
age: 734978
cache-control:max-age=31536000
content-length:40830
content-type: image/jpeg
date:Web, 14 Feb 2020 12:23:42 GMT
expires:Web, 14 Feb 2021 12:23:42 GMT
其中与强制缓存相关的两个字段是expires
和cache-control
,expires
是在HTTP 1.0
协议中声明的用来控制缓存失效日期时间戳的字段,它由服务器端指定后通过响应头告知浏览器,浏览器在接收到带有该字段的响应体后进行缓存。
若之后浏览器再次发起相同的资源请求,便会对比expires
与本地当前的时间戳,如果当前请求的本地时间戳小于expires
的值,则说明浏览器缓存的响应还末过期,可以直接使用而无须向服务器端再次发起请求。只有当本地时间戳大于expires
值发生缓存过期时,才允许重新向服务器发起请求。
从上述强制缓存是否过期的判断机制中不难看出,这个方式存在一个很大的漏洞,即对本地时间戳过分依赖,如果客户端本地的时间与服务器端的时间不同步,或者对客户端时间进行主动修改,那么对于缓存过期的判断可能就无法和预期相符。
为了解决expires
判断的局限性,从HITTP 1.1
协议开始新增了cache-control
字段来对expires
的功能进行扩展和完善。从上述代码中可见cache-control
设置了max-age=31536000
的属性值来控制响应资源的效期,它是一个以秒为单位的时间长度,表示该资源在被请求到后的31536000
秒内有效,如此便可避免服务器端和客户端时间戳不同步而造成的问题。除此之外,cache-control
还可配置一些其他属性值来更准确地控制缓存。
具体的属性值如下:
no-cache
和no-store
:
设置no-cache
并非像字面上的意思不使用缓存,其表示为强制进行协商绥存,即对于每次发起的请求都不会再去判断强制缓存是否过期,而是直接与服务器协商验证缓存的有效性,若缓存未过期,则会使用本地缓存。设置no-store
则表示禁止使用任何缓存策略,容户端的每次请求都需要服务器端给予全新的响应。no-cache
和no-store
是两个互斥的属性值,不能同时设置。
private
和public
private
和public
也是cache-control
的一组互斥属性值,它们用以明确响应资源是否可被代理服务器进行缓存,若资源响应头中的cache-control
字段设置了public
属性值,则表示响应资源既可以被浏览器缓存,又可以被代理服务器缓存,若未明确指定则默认值为private
。
max-age
和s-maxage
max-age
属性值会比s-maxage
更有用,它表示服务器端告知客户端浏览器响应资源的过期时长,在一般项目的使用场最中基本够用,对于大型架构的项目通常会涉及使用各种代理服务器的情况,这就需要考虑存在代理服务器上的有效性问题,这便是s-maxage
存在的意义,它表示缓存在代理服务器中的过期时长,且仅当设置了public
属性值时才有效。
由此可见cache-control
能作为expires
的完全替代方案,并且拥有其所不具备的一些缓存控制特性,在项目实践中使用它就足够了,目前expires
还存在的唯一理由是考虑可用性方面的向下兼容。
1.2、协商缓存
顾名思义,协商缓存就是在使用本地缓在之前,需要向服务器端发起一次请求,与之协商当前浏览器保存的本地缓存是否己经过期。
通常是采用所请求资源最近一次的修改时间戳来判断的,为了便于理解,下面来看一个例子:假设客户端浏览器需要向服务器请求一个manifest.js
的JavaScript
文件资源,为了让该资源被再次请求时能通过协商缓存的机制使用本地缓存,那么首次返回该图片资源的响应头中应包含一个名为last modified
的字段,该字段的属性值为该Javascript
文件最近一次修改的时间戳,简略截取请求头与响应头的关键信息如下:
//资源的首次请求头
:authority: ssl.example.com
:method: GET
:path: /dist/manifest.is
: scheme: https
//资源的首次响应头
last-modified: Fri, 22 Sep 2017 05:58:50 GMT
status: 200
content-type: application/javascript
当我们键刷新网页时,由于该JavaScript
文件使用的是协商缓存,客户端浏览器无法确定本地缓存是否过期,所以需要向服务器发送一次请求,进行缓存有效性的协商,此次请求的请求头中需要包含一个if-modified-since
字段,其值正是上次响应头中last-modified
的字段值。
当服务器收到该请求后便会对比请求资源当前的修改时间戳与if-modified-since
字段的值,如果二者相同则说明缓存未过期,可继续使用本地缓存,否则服务器重新返回全新的文件资源,简略截取请求头与响应头的关键信息如下:
// 再次请求的请求头
:authority: ssl.example.com
:method: GET
:path: /dist/manifest.js
:scheme: https
if-modified-since:Fri, 22 Sep 2017 05:58:50 GMT
// 协商缓存有效的响应头
statuc code: 304 not modified
这里需要注意的是,协商缓存判断缓存有效的响应状态码是304
,即缓存有效响应重定向到本地缓存上。这和强制缓存有所不同,强制缓存若有效,则再次请求的响应状态码是200
。
last-modified
的不足
通过last-modified
所实现的协商缓存能够满足大部分的使用场景,但也存在两个比较明显的缺陷:首先它只是根据资源最后的修改时间戳进行判断的,最然请求的文件资源进行了编辑,但内容并没有发生任何变化,时间戳也是会更新 从而导致协商缓存时关于有效性的判断验证为失效,需要重新进行完整的资源请求。
这无疑会造成网络带宽资源的浪费,以及延长用户获取到目标资源的时间。其次标识文件资源修改的时间戳单位是秒,如果文件修改的速度非常快,假设在几百毫秒内完成,那么上述通过时间戳的方式来验证缓在的有效性,是无法识别出该次文件资源的更新的。
其实造成上述两种缺陷的原因相同,就是服务器无法仅依据资源修改的时间戳来识别出真正的更新,进而导致重新发起了请求,该重新请求却使用了缓存的Bug
场景。
基于ETag
的协商缓存
为了弥补通过时间戳判断的不足,从HTTP 1.1
规范开始新增了一个ETag
的头信息,即实体标签(Entity Tag)
。其内容主要是服务器为不同资源进行哈希运算所生成的一个字往串,该字符串类似于文件指纹,只要文件内容编码存在差异,对应的ETag
标签值就会不同,因此可以使用ETag
对文件资源进行更精准的变化感知。下面我们看一个使用ETag
进行协商缓存的图片资源实例:
// 响应头
Content-Type: image/jpeg
ETag: "c39046a19cd8354384c2de0c32ce7ca3"
Last-Modified: Fri, 12 Jul 2019 06:45:17 GMT
Content-Length: 9887
上达响应头中同时包含了last-modified
文件修改时间戳和ETag
实体标签两种协商缓存的有效性校验字段,因为ETag
比last-modified
具有更准确的文件资源变化感知,所以它的优先级也会更高,二者同时存在时以ETag
为准。再次对该图片资源发起请求时,会将之前响应头中ETag
的字段值作为此次请求头中If-None-Match
字段,提供给服务器进行缓存有效性验证。请求头与响应头的关键字段信息如下:
// 再次请求头
If-Modified-Since: Fri, 12 Ju1 2019 06:45:17 GMT
If-None-Match:"c39046a19cd8354384c2de0c32ce7ca3"
// 再次响应头
Content-Type: image/jpeg
Etag: "c39046a1 9cd8354384c2de0c32ce7ca3"
Last-Modified: Fri, 12 Jul 2019 06:45:17 GMT
Content-Length:0
验证缓存有效,则返回304
状态码响应重定向到本地缓存,所以上面响应头中的内容长度Content-Length
字段值也就为0
了。
ETag
的不足
不像强制缓存中cache-control
可以完全替代expires
的功能,在协商缓存中,ETag
并非last-modified
的替代方案而是一种补充方案,因为它依旧存在一些弊端,一方面服务器对于生成文件资源的ETag
需要付出额外的计算开销,如果资源的尺寸较大,数量较多且修改比较频繁,那么生成ETag
的过程就会影响服务器的性能。
另一方面ETag
字段值的生成分为强验证和弱验证,强验证根据资源内容进行生成,能够保证每个字节都相同;弱验证则根据资源的部分属性值来生成,生成速度快但无法确保每个字节都相同,并且在服务器集群场景下,也会因为不够准确而降低协商缓存有效性验证的成功率,所以恰当的方式是根据具体的资源使用场景选择恰当的缓存校验方式。
1.3、缓存选择
2、CDN
缓存
2.1、CDN
概述
CDN
全称Content Delivery Network
,即内容分发网络,它是构建在现有网络基础上的虛拟智能网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、调度及内容分发等功能模块,使用户在请求所需访问的内容时能够就近获取,以此来降低网络拥塞,提高资源对用户的响应速度。
CDN
可以提升首次请求资源的响应速度。
2.2、CDN
工作原理
回想在初学计算机网络的时候,常见的B/S
模型都是浏览器直接向服务器请求所需的资源,但实际组网情况并非如此简单。因为通常对热门站点来说,同时发起资源请求的用户规模量往往非常巨大,而如果这些请求都发往同一服务器则极有可能造成访问拥塞。所以更合理的做法是将部分数据缓存在距离用户较近的边缘服务器上,这样不但可以提升对资源的请求获取速度,而且也能有效减少网站根节点的出口带宽压力,这便是CDN
技术的基本思路。
如果末使用CDN
网络进行缓存加速,那么通过浏览器访问网站获取资源的大致如下:
- 当用户在浏览器中输入所要访问的域名时,若本机无法完成域名解析工作,则会转向
DNS
服务器请求对该域名的解析。 DNS
服务器解析完成返回给浏览器该域名所对应的IP
地址。- 浏览器向该
IP
地址指向的服务器发起资源请求。 - 最后服务器响应用户请求将资源返回给浏览器。
如果使用了CDN
网络,则资源获取的大致过程是这样的:
- 由于
DNS
服务器将对CDN
的域名解析权交给了CNAME
指向的专用DNS
服务器,所以对用户输入域名的解析最终是在CDN
专用的DNS
服务器上完成的。 - 解析出的结果
IP
地址并非确定的CDN
缓存服务器地址,而是CDN
的负載均衡器的地址。 - 浏览器会重新向该负载均衡器发起请求,经过对用户
IP
地址的距离、所请求资源内容的位置及各个服务器复杂状况的综合计算,返回给用户确定的缓存服务器地址。 - 对目标缓存服务器请求所需资源的过程。 当然这个过程也可能会发生所需资源未找到的情况,那么此时便会依次向其上一级缓存服务器继续请求查询,直至追溯到网站的根服务器并将资源拉取到本地。
针对静态资源
CDN
网络能够缓存网站资源来提升首次请求的响应速度,但并非能适用于网站所有资源类型,它往往仅被用来存放网站的静态资源文件。所谓静态资源,就是指不需要网站业务服务器参与计算即可得到的资源,包括第三方库的JavaScript
脚本文件、样式表文件及图片等,这些文件的特点是访问频率高、承载量大,但更新修改频次低。且不与业务有太多耦合。
如果是动态资源文件,比如依赖服务 器端渲染得到的HTM
页面,它需要借助服务器端的数据进行计算才能得到,所以它就不适合放在CDN
缓存服务器上。
核心功能
CDN
网络的核心功能包括两点:缓存与回溯,缓存指的是将所需要的静态资源文件复制一份到CDN
缓存服务器上;回溯指的是如果未在CDN
缓存服务器上查找到目标资源,或CDN
缓存服务器上的缓存资源已经过期,则重新追溯到网站根服务器获取相关资源的过程。
以上是针对工作中所遇问题做的总结与进一步学习,希望对浏览者也能起到作用。