关于Web浏览器缓存机制的初步分析及消除缓存的影响
在了解浏览器缓存机制之前,我们首先要明白其缓存机制是由client(客户端)以及serve(服务器端)共同决定的,通过两者对http(HyperText Transfer Protocol【超文本传输协议】)协议的报文首部的设置,可以决定缓存的性质(包括寿命、是否有缓存以及缓存的方式等等),因此我们事先要简单了解下跟缓存有关的http报文首部设置,具体来说可分为以下几点:
1.Cache-Control
也是跟缓存最相关的一个头部特性,其值大体可分为no-Cache以及max-age等,显然从字面意义来讲no-Cache代表这不进行缓存(即每次均向服务端请求最新的资源),但这一选项其实并不是那么绝对(后面我们再来分析)可靠的,*这个很容易让人产生误解,使人误以为是响应不被缓存。实际上Cache-Control: no-cache是会被缓存的,只不过每次在向客户端(浏览器)提供响应数据时,缓存都要向服务器评估缓存响应的有效性。*max-age指明以秒为单位的缓存时间,Cache-Control: no-store:这个才是响应不被缓存的意思。private(默认): 只能在浏览器中缓存, 只有在第一次请求的时候才访问服务器, 若有max-age, 则缓存期间不访问服务器。public: 可以被任何缓存区缓存, 如: 浏览器、服务器、代理服务器等。
2.Pragma
Pragma:no-cache跟Cache-Control: no-cache相同,Pragma: no-cache兼容http 1.0 ,Cache-Control: no-cache是http 1.1提供的。因此,Pragma: no-cache可以应用到http 1.0 和http 1.1,而Cache-Control: no-cache只能应用于http 1.1,两者间没有必要的差别。
3.Expires
是指设置以分钟为单位的缓存绝对过期时间,从字面意义来看与Cache-control的max-age属性类似。
4.If-Modified-Since
是指浏览器客户端最新应用缓存的时间,服务端接收到请求头若有此属性,则会判断被请求的资源是否发生过变动(根据客户端提供的最新缓存资源时间),若有则返回最新资源,若没有则返回状态码304及其解释码(此时不将请求的资源返回客户端,因此大大减少了服务器的负载压力)。
优先级比较:
其中这三个选项均可以在客户端(页面的meta或通过js脚本动态改变请求头)及服务端进行设置(若不设置,则会根据web服务器的默认设置的缓存机制);上面列举了几种改变缓存方式的策略,但显然服务器同时接到多种报文请求头属性时必然存在着缓存处理的优先级,其优先级是:no-store>max-age>no-cache>expires。前面说到Cache-Control中的no-Cache不是那么绝对的且其不代表响应不被缓存,要根据有效性进行分析,其中这里的有效性就是指max-age,若max-age设置其大于0,则在缓存过期时间未来临前,即使设置了no-Cache,也会获取本地缓存资源,而expires作用与max-age相近,但其优先级较低,不常用。
优缺点分析
If-Modified-Since,它可以保证资源在未更新之前一直获取本地资源。一般与Cache-Control联合使用,这时,尽管设置了缓存,客户端也会每次都访问服务器,但在请求资源未更新前,服务端只返回相关信息而不直接返回请求资源,这样做可以减少冗余量,其优点显而易见,但其缺点是每次刷新页面客户端都会向服务端发送新的http报文(尽管此时本地缓存的资源就是客户端想要的东西)。因此针对较大的资源类型(比如html页面)我们可以采用这种方式来减少信息冗余,而像图片、js、css这类相对较小的资源采用直接读取本地缓存的方式(不请求服务端)比较可靠。
出现的问题及解决方法
以学校的项目为例(系统环境为Win10专业版、服务器为Tomcat7.0),缓存配置为服务器的默认配置(你会发现服务器已为你配置好了相对最科学的缓存机制0.0)。我们通过观察每次的http请求头和响应头来分析其机制:
第一次请求页面资源返回结果如***:这里以chrome开发者调试工具为例)
接下来的请求页面资源返回结果如下
我们发现在以后的每次刷新页面后,客户端会默认在页面请求头中加上If-Modified-Since: Mon, 16 Oct 2017 04:59:56 GMT的属性及最新缓存日期(html文件是缓存在本地磁盘中,后面日期为GMT(世界时)的格式),而之后的相应头的状态行均为HTTP/1.1 304 Not Modified,意为服务器判断请求的资源没有更新,用304 Not Modified的标志来替代请求资源来响应客户端。这样的好处就是根本不需要我们来管理缓存,开发人员在对html页面进行修改后,可在最新的一次请求中响应给客户端并更新缓存,从而能保证客户端始终能获得最新更改后的html内容。
再来观察js、css、image等资源的变动,第一次请求上述资源返回结果如下:
第一次默认请求报文中的关键字为no-Cache,且没有max-age的限制条件,因此请求的是最新的资源
接下来的请求资源返回结果如下:
我们发现请求和响应报文跟之前乍一看一摸一样,但其实多了一个变动,是最上方General中Status Code返回200后注明了from memory cache,代表资源是从本地内存缓存中获取的,其实此时客户端根本没有去向服务器发送请求,直接在内存获取的资源,当关闭页面重新加载后如下结果:
区别是只有在200 ok后多了(from disk cache),指明资源是从本地磁盘中获取的,这时我们恍然大悟,原来在第一次请求这些资源的时候(本地无任何缓存),浏览器在获取资源的同时,将在磁盘中创建本地文件放入资源,同时将这一类资源放入到了内存中(这里大概是因为浏览器会根据请求资源的大小来决定是否将其同时放入内存缓存中,因为当加载svg/xml等较大类型的资源时浏览器仅将其存入了磁盘缓存中),这样在接下来的刷新中,内存中的资源获取的速度可想而知,但一旦离开此页面(kill此进程),则内存资源流失,将重新从磁盘中获取资源(前提是本地缓存未过期)。
问题分析
每次无需重新向服务端发送请求而直接向本地磁盘缓存及内存缓存获取资源带来速度上的优势是显而易见的,但随之而来的问题是:开发人员在修改这一类资源后,用户若不手动清除缓存的话,那将无法获取最新的请求资源,显然要求每次用户都用ctrl+F5来获取最新资源的方式是不合理的,因此当本地缓存资源发生更新后,我们应保证此时客户端重新向服务端请求最新的资源。
解决方法
首先我们应当清楚存在本地缓存资源的请求顺序:内存缓存—磁盘缓存—重新请求服务器。因此我们只要通过某种方法来让客户端分析出本地资源已发生了更新,就可以按照请求的顺序链最终进行重新请求。客户端是根据文件名(文件id)来获取资源的,因此只要我们每次修改资源后改变其文件名就使问题得到了解决,但当有大量文件时,我们不可能一一修改其文件名,这时我们可以采用添加版本信息的方式来解决问题:如index.js?version=1.0,其中?及后面的参数在请求时不会被服务器解析(原理和利用get/post方法进行url传参一样),因此可以保证响应信息的准确性。