HTTP缓存及其使用

(之前觉得HTTP缓存是个简单的事,项目中遇到后才发觉关于缓存实践有挺深的学问)html

0. What  

这里的缓存指的是http标准中定义的缓存技术(如Cache-Control),主要由服务端设置和处理(固然还要由客户端旳浏览器配合)而不须要前端开发者参与。当作了恰当的参数设置后客户端发起请求过程当中缓存会由浏览器和服务端间自动进行处理完成,而不用用户参与。前端

当前HTML5 API中有LocalStorage、SessionStorage技术也被用于“缓存”,然而这类本质上是种相似于DB的本地存储,由开发者进行控制。这种不是咱们这里所要讨论的缓存。web

 

整个 Web 系统架构在 HTTP 协议 之上, 利用 HTTP 的缓存机制不只能够极大地减小服务器负载, 更重要的是加速页面的载入以及减小用户的流量消耗。 HTTP缓存机制也早已普遍地被服务器厂商(如 Tomcat、Apache、Virgo)和浏览器厂商(如Tomcat、Apache、Virgo)实现。此外,一些服务器端框架(如 Django、Express.js)也实现了 HTTP 缓存机制。chrome

1. 两类缓存

浏览器和服务器之间使用的缓存策略能够分为强缓存、协商缓存两种,一般一块儿搭配使用。浏览器

强缓存:用户发送的请求,直接从用户客户端缓存读取,不发送到服务端,无与服务端交互缓存

协商缓存:用户发送的请求,发送到服务端,由服务端根据参数判断是否让客户端从客户端缓存读取。协商缓存没法减小请求开销,但可减小返回的正文大小服务器

相同点:最终都是从客户端缓存读取而不用服务端发送请求的数据回来;架构

不一样点:客户端的请求是否发送到服务端,便是否与服务端交互负载均衡

适用性:强缓存适用于不多变化的资源,能够把过时时间设长;协商缓存适用于常常变化的资源。框架

 

2. 相关参数

与强缓存和协商缓存相关的HTTP Header参数以下:(注:HTTP标准中Header名首字母大写)

Date:用于记录这次缓存时服务器的时间

Cache-Control、Expires、Pragma:用于强缓存的判断

Last-Modified、 If-Modified-Since、Etag、 If-None-Match:用于协商缓存判断,以实现有条件的HTTP请求

后面用到时详细介绍。

 

3. 总体过程

总体过程以下,若(b)是N则是使用强缓存、若(c)为Y则是使用协商缓存。

总体过程可理解为:

  1. 发送请求前浏览器先检查本地是否有缓存,没有则直接向服务器请求资源;
  2. 若本地有缓存且还没有过时(根据请求头的Expires和Cache-Control)则直接取缓存,若过时则向服务端发送请求;
  3. 服务端收到请求后判断是否仍让客户端使用缓存(根据请求头的Last-Modified和Etag,例如文件没变化,服务端可以让浏览器直接用缓存而不用从新发文件给用户)、如果则返回304告诉客户端直接取缓存
  4. 若以上两个缓存都没命中,则浏览器再请求服务器获取最新资源,服务器返回资源的同时设置一些上述缓存参数

(a)浏览器判断是否有缓存

浏览器会在系统某个位置专门存放缓存信息。经过检查该位置是否有对应请求的缓存信息来判断是否有缓存(对于Chrome 66以前的版本也可在chrome://cache 查看请求及对应的缓存信息)。

缓存信息包括请求的响应头及对应的缓存内容。一个缓存示例以下:

 

(b)判断缓存是否过时

客户端检查到本地有缓存的话会判断缓存是否过时。

缓存信息中包含被缓存的请求的响应头。里面包含Date、Cache-Control、Expires、Pragma字段(可能不是每一个都有)用于判断缓存是否过时。下面介绍各字段的做用再说明判断方法。

各字段旳做用:

Date:指明这次缓存的时间(服务器时间)

Expires:指明缓存过时的绝对时间(服务器时间),如 Thu, 28 Sep 2017 06:38:37GMT 。http 1.0的标准存在的问题:客户端服务端时间不一致可能致使缓存效果不符合指望。

Cache-Control:指明缓存过时策略,可当作是Expires的补充,使用相对时间。http 1.1的标准。属性设置:

  • max-age: 设置普通缓存的最大有效时间(单位为s)。max-age会覆盖掉Expires
  • s-maxage: 只用于共享缓存如CDN缓存(单位为s)。与max-age 的区别:max-age用于普通缓存而s-maxage用于代理缓存。s-maxage会覆盖max-age 和 Expires设置
  • public:响应会被缓存,且在多用户(如多个浏览器)间共享。未指定public或private则默认是public
  • private: 响应只做为私有缓存,不能在用户间共享。若是要求HTTP认证,响应会自动设置为private
  • no-cache: 指定不缓存响应,代表资源不进行缓存。设置了no-cache以后并不表明浏览器不缓存,而是在缓存前要向服务器确认资源是否被更改。故有时只设置no-cache防止缓存不够保险,还可加上private指令,将过时时间设为过去的时间
  • no-store: 绝对禁止缓存,每次请求资源都会从服务端从新获取
  • must-revalidate: 若是页面过时,则去服务器进行获取。不经常使用

Cache-Control的设置规则:

Pragma:只有 Pragma: no-cach 一种用法,与Cache-Control:no-cache的做用同样。出现缘由:http 1.0没有实现no cache的功能,所以用Pagama使no cache功能应用到1.0。

 

 判断缓存是否过时的规则:

若Cache-Control中有max-age或s-maxage则用它们加上date做为过时绝对时间,不然直接用expires指定的时间做为过时绝对时间。将绝对时间与当前时间比较是否过时。若未过时则直接使用缓存(此时就是前面说的强缓存)。

 

(c)向服务器询问是否使用缓存

若客户端判断缓存已过时,则向服务端发送请求。服务端根据Last-Modified/If-Modified-Since、Etag/If-None-Match字段(也可能不是每一个都有)判断是否让客户端用缓存。

下面一样先介绍各字段做用再说明判断方法。

各字段做用:(这几个字段一般用于有条件的HTTP请求

Last-Modified:代表请求的服务端资源上次的修改时间(服务器时间)
If-Modified-Since:客户端保留的资源上次的修改时间
Etag:服务端根据资源内容生成的一段标识(不惟一,一般为文件的md5或者hash值或版本号等,只要保证写入和验证时的方法一致便可)
If-None-Match:客户端保留的上次获取到的的Etag

 

判断规则(这部分一般服务器实现或服务端代码本身实现):

浏览器向服务端发送请求时,若上一次的缓存中有Last-Modified或Etag字段则在request header中加入If-Modified-Since(对应Last-Modified)或If-None-Match字段(对应If-None-Match),以询问服务端资源是否被修改过。服务端判断资源是否修改(对于If-Modified-Since服务端看自该时间后资源是否修改、对于If-None-Match服务端比较资源目前的Etag是否与所收到的同样):若未修改则服务端返回304,浏览器使用缓存;不然浏览器再次请求资源,状态码为200、资源为服务器最新资源。Etag处理流程示意图:

一般状况下,若同时发送If-None-Match、If-Modified-Since字段,服务器只要比较Etag的内容便可(即Etag的优先级高于另者),固然具体处理方式,看服务器的约定规则。

 

注:

使用ETag能够解决Last-modified存在的一些问题:

  • 某些服务器不能精确获得资源的最后修改时间,这样就没法经过最后修改时间判断资源是否更新
  • 若是资源修改很是频繁,在秒如下的时间内进行修改,而Last-modified只能精确到秒
  • 一些资源的最后修改时间改变了,可是内容没改变,使用ETag就认为资源仍是没有修改的

分布式系统中尽可能不用Etag,由于每台机器生成的Etag都同样。(啥意思???)

分布式系统里多台机器间资源的Last-Modified必须一致,以避免负载均衡不一样致使对比失败

 

至此,结合上述参数的整个请求处理过程以下:

 Chrome浏览器从本地缓存中取资源时,发起的请求中会有"from memory cache" 或 "from disk cache" 标识(最先只有from cache,从某个版本起改成此二者),示例:

Chrome from memory cache与from disk cache的区别:

Chrome employs two caches — an on-disk cache and a very fast in-memory cache. The lifetime of an in-memory cache is attached to the lifetime of a render process, which roughly corresponds to a tab. Requests that are answered from the in-memory cache are invisible to the web request API. If a request handler changes its behavior (for example, the behavior according to which requests are blocked), a simple page refresh might not respect this changed behavior. To make sure the behavior change goes through, call handlerBehaviorChanged() to flush the in-memory cache. But don't do it often; flushing the cache is a very expensive operation. You don't need to call handlerBehaviorChanged() after registering or unregistering an event listener.

 

4. 缓存参数设置总结

  • 谨慎地使用过时时间,最好配合 MD5 一块儿使用。参数设置过长会致使客户端看到的一直是强缓存的旧数据,得不到及时更新。CDN资源、图片等常过时时间一般较长。
  • 老是启用条件请求,好比 Etag 或 Last-Modified。
  • 文件服务采用 Last-Modified,动态内容采用 Etag。
  • 分离常常变化的部分,也会提升缓存的命中率。

 

5. 用户行为对浏览器缓存的影响

用户操做 Cache-Control/Expires Last-Modified/Etag
地址栏回车 有效 有效
页面连接跳转 有效 有效
新开窗口 有效 有效
前进、后退 有效 有效
F5刷新 无效 有效
Ctrl + F5刷新 无效 无效

 

6. 实践

Java Web中设置缓存

// 设置缓存。强缓存适合不频繁变动的文件,协商缓存用于变化频繁的文件。咱们的业务场景中资源不多变化,故以强缓存为主、协商缓存几乎触发不到。
        {
            // 如下设置强缓存
            int cacheTimeSecond = 5 * 3600;// 强缓存时间
            Date curDate = new Date();
            Date newLastModifyDate = PersistentStorageUtilFactory.getPersistStorageUtil()
                    .getObjectMetaData(bucketName, objKey).getLastModified();

            response.setHeader("Date", curDate.toGMTString());
            response.setHeader("Cache-Control", "private, max-age=" + cacheTimeSecond);// second。时间太长的话可能致使前端缓存的资源与服务端资源不一致。max-age会覆盖expires
            response.setDateHeader("expries", curDate.getTime() + cacheTimeSecond * 1000);

            // 如下设置协商缓存
            String newEtagStr = String.valueOf(newLastModifyDate.hashCode());

            SimpleDateFormat sdf = new SimpleDateFormat();
            String oldModifiedTimeStr = request.getHeader("If-Modified-Since");
            String oldEtagStr = request.getHeader("If-None-Match");

            boolean isModifiedTimeNotChanged = null != oldModifiedTimeStr
                    && sdf.parse(oldModifiedTimeStr).equals(newLastModifyDate);
            boolean isContentNotChanged = null != oldEtagStr && oldEtagStr.equals(newEtagStr);
            if (isModifiedTimeNotChanged || isContentNotChanged) {// 服务器资源没有更新。返回304
                response.setStatus(HttpStatus.SC_NOT_MODIFIED);
                return;
            } else {
                response.setHeader("Last-Modified", newLastModifyDate.toGMTString());
                response.setHeader("Etag", newEtagStr);
            }
        }
View Code

 

 

 

 

7. 参考资料

https://blog.csdn.net/u014590757/article/details/80140654

http://www.alloyteam.com/2016/03/discussion-on-web-caching/

https://excaliburhan.com/post/things-you-should-know-about-browser-cache.html

相关文章
相关标签/搜索