(图)学习web缓存相关的概念

发布时间:2020-11-02编辑:脚本学堂
说明:本帖主要针对各个环节的缓存代理,以及本地用户代理(浏览器)上的缓存策略。

说明本帖主要针对各个环节的缓存代理,以及本地用户代理(浏览器)上的缓存策略。
ps:应舍瓦大大要求,从我的evernote里翻找出这篇和cache相关的东西. 难免有错漏指出,欢迎指正。

代理服务器简分类:(并不太全,仅当科普, 了解代理在web中的重要作用是有必要的.这里仅仅是简单介绍下.)

缓存角度分类:
(1) 缓存代理 : 根据某种约定,缓存曾经请求过的数据
(2) 常规代理 : 只转发请求的那一种.并不缓存数据的代理

控制方分类
(1) 反向代理:(对于原始服务器来说,反向代理即是一个客户端)
  a) 服务提供者主观使用,并控制.
  b) 相对其他客户端来说,离原始服务器最近(主观上)
  c) 转发请求,缓存,用于负载均衡.
  d) 屏蔽其他客户端与原始服务器的直接联,避免原始服务器被直接攻击
  e) CDN的应用(严格来说,CDN已经并非纯粹意义上的反向代理了.)
(2) 非反向代理: (非服务提供者所控制)
  其他分类:
(1) 用户代理
  a) 浏览器
  b) 各类终端机
  c) 搜索引擎爬虫.
(2) 透明代理
(3) 拦截代理
(4) http代理
(5) socket代理(UDP,FTP…etc)

缓存相关的约定,HTTP协议、相关概念

HTTP1.1协议:
继承自http1.0,在其基础上扩展或修改而产生. 比如1.0中Date标头,允许使用的RFC1036日期格式,的千年虫问题的修正.悲剧的是,有些明显的错误,无法矫正,比如一个著名的拼写错误. Referer 标头的拼写错误.一直沿用至今.因其,已经被广泛应用.在实际使用上,又无伤大雅,所以也就被继续将错就错了.
实体: 用于表示一个,请求或响应的消息中的一个资源,如post提交的表单内容,或请求服务器上的abc.jpg时,abc.jpg就代表一个实体.对于一个实体响应来说,除去响应标头外所剩余的部分,即是实体主体部分,比如abc.jpg的真实2进制数据.

标头(头域):
(1) 请求标头 : 在一个请求中使用的相关标头,用于添加附加信息,或对被请求服务器增加限制.
(2) 响应标头 : 用于添加相关响应的附加信息.
(3) 实体标头 : 提供实体相关的附加信息,如果修改时间,实体的大小等等.
(4) 常规标头 : (通用标头)在请求和响应中都可以使用的一些常规标头,如HTTP1.0所定义的Date.和 Pragma(是的,这并不是一个实体标头,它仅仅是试图与客户端,包括代理,进行协商,不要使用缓存的副本.).

补充:HTTP1.1的常规标头:
Date,Pragma,Cache-Control,Connection,Transfer-Encoding,Upgrade,Trailer,Via,Warning

缓存相关标头:
. Expires (实体标头,HTTP 1.0+)
一个GMT时间,试图告知客户端,在此日期内,可以信任并使用对应缓存中的副本,缺点是,一但客户端日期不准确.则可能导致失效.

.Pragma : no-cache(常规标头,http1.0+)
对Pragma定义的唯一的伪指令,同http1.1的Cache-Control : no-cache

.Last-Modified(实体标头,HTTP1.0+)
一个GMT时间,告知,被请求实体的最后修改时间.用于客户端校验其缓存副本是否仍然可以信任.与其相关的两个条件请求标头:
(1) If-Modified-Since:(此标头,仅对Get方法有意义)
如果实体在指定时间后,没有修改则返回一个304,否则返回一个常规的Get请求的响应(比如200). 另外,如果该标头的值是一个非法的值,那么也同样返回一个常规的Get请求的响应.
PS:用户代理发起 If-Modified-Since尝试握手的条件,可能会有不同,比如IE系,如果该实体第一次响应头中包含Cache-Control:no-cache.则 IE不会使用If-Modified-Since请求资源.而其他浏览器则会. 但是如果使用Cache-Control:no-store.则所有用户代理的表现一致.都不使用If-Modified-Since(因为no-store的语义十分强烈.不允许任何缓存,这个在后续有专门介绍.)

(2) If-Unmodified-Since:
如果实体在指定时间后,没有任何修改,那么就可以直接执行该请求使用方法的对应行为. 而如果有修改,则返回一个412 Precondition Failed状态码,并且抛弃该方法对应的行为操作(GET方法除外).

. Cache-Control : (常规标头,HTTP1.1)

.public:(仅为响应标头)
响应:告知任何途径的缓存者,可以无条件的缓存该响应.

.private(仅为响应标头)
响应:告知缓存者(据我所知,是指用户代理,常见浏览器的本地缓存.用户也是指,系统用户.但也许,不应排除,某些网关,可以识别每个终端用户的情况),只针对单个用户缓存响应. 且可以具体指定某个字段.如private –“username”,则响应头中,名为username的标头内容,不会被共享缓存.

.no-cache:
请求: 告知缓存者,必须原原本本的转发原始请求,并告知任何缓存者,别直接拿你缓存的副本,糊弄人.你需要去转发我的请求,并验证你的缓存(如果有的话).对应名词:端对端重载.
响应: 允许缓存者缓存副本.那么其实际价值是,总是强制缓存者,校验缓存的新鲜度.一旦确认新鲜,则可以使用缓存副本作为响应. no-cache,还可以指定某个包含字段,比如一个典型应用,no-cache=Set-Cookie. 这样做的结果,就是告知缓存者,对于Set-Cookie字段,你不要使用缓存内容.而是使用新滴.其他内容则可以使用缓存.

.no-store:
请求:告知,请求和响应都禁止被缓存.(也许是出于隐私考虑)
响应:同上.

.max-age:
请求:强制响应缓存者,根据该值,校验新鲜性.即与自身的Age值,与请求时间做比较.如果超出max-age值,则强制去服务器端验证.以确保返回一个新鲜的响应.其功能本质上与传统的Expires类似,但区别在于Expires是根据某个特定日期值做比较.一但缓存者自身的时间不准确.则结果可能就是错误的.而max-age,显然无此问题. Max-age的优先级也是高于Expires的.
响应:同上类似,只不过发出方不一样.

.max-stale:
请求:意思是,我允许缓存者,发送一个,过期不超过指定秒数的,陈旧的缓存.
响应:同上.

.must-revalidate(仅为响应标头)
响应:意思是,如果缓存过了新鲜期,则必须重新验证.而不是试图返回一个不在新鲜期的缓存.与no-cache的区别在于,no-cache,完全无视新鲜期的概念.总是强制重新验证.理论上,must-revalidate更节省流量,但相比no-cache,可能并不总是那么精准.因为即使缓存者,认为是新鲜的,也不能保证服务器端没有做过更新.如果缓存者是一个缓存代理服务器,如果其试图重新验证时,无法连接上原始服务器,则也不允许返回一个不新鲜的,缓存中的副本.而是必须返回一个504 Gateway timeout.

.proxy-revalidate(仅为响应标头)
响应:限制上与must-revalidate类似.区别在于受体的范围.proxy-revalidate,是要排除掉用户代理的缓存的.即,其规则并不应用于用户代理的本地缓存上.

.min-fresh(仅为请求标头)
请求:告知缓存者,如果当前时间加上min-fresh的值,超了该缓存的过期时间.则要给我一个新的.其实个人觉得,其功能上有点和max-age类似.但是更大的是语义上的区别.

.only-if-cached:(仅为请求标头)
请求:告知缓存者,我希望内容来自缓存,我并不关心被缓存响应,是否是新鲜的.

.s-maxage(仅为响应标头)
响应:与max-age的唯一区别是,s-maxage仅仅应用于共享缓存.而不引用于用户代理的本地缓存,等针对单用户的缓存. 另外,s-maxage的优先级要高于max-age.

.cache-extension (cache-extension是一个泛化的代称.它指所有自定义,或者说扩展的,指令,客户端和服务器端都可以自定义扩展Cache-Control相关的指令.)那么,实际上我们可以这样 Cache-Control:max-age=300, custom-directive = xxx, public. 这样我们就定义了一个被统称为cache-extension的扩展指令.该指令如果对应的客户端或服务器端,不认识,就会忽略掉.

扩展指令 中一个常见的东西是 none-check post-check 和 pre-check. 这玩意是IE5被加入的. 所以如果响应头中有这几个扩展指令,那么IE就会认得他们, 我经常在一些 为了解决 no-cache + gzip 命中ie6 JSONP 请求,导致脚本不执行bug的方案中见到这几个扩展指令,其目的是为了让IE放弃使用本地缓存.  我倒是觉得,对IE6放弃使用gzip,是更合理的做法.  当然缺点也很明显, 如果是cdn部署静态资源.显然这样做会很困难.

bug描述:
IE6,某些情况下,开启gzip的资源,会不渲染或不执行(如果是.js的话.)
 
会引发此bug的条件:
1. 首先,必须由a页面脚本导致跳转到b页面 : 即 a页面有 location.href = b页面.(点链接,form post,replace, assign等方式都会导致问题,包括target=_blank弹窗的情况)
2. b页面自身,或其使用动态创建脚本(硬编码script src=xxx 也会有此问题) 的响应头中包含下面情况:
cache-control 包含下列伪指令:
(1) no-store
(2) no-cache + 其他与缓存新鲜度检验有关头共存时, 如 max-age=xxx (xxx无所谓.0 或3000都会触发,) 或 no-cache + must-revalidate甚至是,no-cache, pre-check=0等情况..
(3) no-cache独立存在时,体现为一种不稳定情况.即当访问页面被cache时,可能会触发.但也不是100%.仅仅是偶尔...
 
遇到以上情况,页面可能会不渲染,而脚本可能会不执行.
ps: 本bug ,与 http1.0 头域 : Pragma : no-cache ,无关.

解决办法:
1. 放弃压缩.
2. 放弃cache-control 中的 no-cache,no-store头域. 比如 单独使用max-age=0.并对不支持http1.0的老浏览器配合Expires = 一个过期时间.

关于这个bug的msdn的描述:
To work around this problem, you can do either of the following:
If you use a Cache-Control: no-cache HTTP header to prevent the files from caching, remove that header. In some situations, if you substitute an Expires HTTP header, you do not trigger the problem.
-or-
Do not enable HTTP compression for the script files.
显然,微软给了我们两条路,去掉no-cache头,或者使用Expires指定过期时间,强制使其过期代替no-cache, 或者别压缩. 而pre-check=0.显然可以代替Expires做到这件事.

关于这几个扩展指令的, 参考msdn 的描述:http://msdn.microsoft.com/en-us/library/ms533020%28VS.85%29.aspx#Use_Cache-Control_Extensions

post-check
Defines an interval in seconds after which an entity must be checked for freshness. The check may happen after the user is shown the resource but ensures that on the next roundtrip the cached copy will be up-to-date.
pre-check
Defines an interval in seconds after which an entity must be checked for freshness prior to showing the user the resource.

一张图:web_cache
简单来说, 就是控制IE,如何使用本地缓存 ,如果缓存时间,超过post-check的值,就要保证下一次请求该资源,去要验证过的新鲜的.而pre-check则是超过了,就马上给个新的. no-check就无需解释了..
.no-transform
请求:告知代理,不要更改媒体类型,比如jpg,被你改成png.
响应:同上.

.Etag :(实体标头,HTTP1.1)
    通过[Mog95],( http://www.research.digital.com/wrl/publications/abstracts/95.4.html 该地址目前可能无法访问了。)生成一段可代表实体版本的字串.默认就是一段hash + 时间戳的形式.其实我们是可以使用自己的算法来生成Etag值.比如md5。

    PS:apache的默认Etag包含Inode,Mtime,Size三部分.而且Etag有强弱之分.比如一般的弱Etag,是以W/开头的,如:W/”abcde12”,这部分不是我们关注的焦点.因为弱Etag和强Etag的区别只在于算法.比如某种弱Etag关注的时间精度,为秒.而我们在项目中,最常见的做法是使用MD5.是一种忽略时间维度的,强Etag.为的是保证精确度.以及负载均衡设备的同步.除非我们的项目有特殊需求.但是往往我们可以根据需求,来调整算法.而不是沿用一些传统的弱Etag算法。