腾讯DeepOcean原创文章:dopro.io/http-cache-…
首先,让我们来看这样一个场景配置http缓存与cdn缓存一直以来都是web性能优化中重要而常见的手段。合理的http缓存与cdn缓存配置可以起到减轻服务器压力,缓解网络瓶颈,提升用户体验等作用,不当的缓存配置却会导致资源无法及时更新,用户体验差异,甚至流程出错等问题。本文主要讲解http缓存与cdn缓存的原理和配置规则,希望通过本文的讲解能够让大家清楚什么是合理的缓存配置,如何为自己的项目定制化缓存方案,以及如果碰到缓存问题,应该如何分析解决。
项目A上线了一个新特性,包含着逻辑的改动和页面UI的更新,小明作为项目开发将代码提交后进行了预发布。产品经理小红开始体验新特性,奇怪的是,小红进入项目后却并没有看到最新的特性,这时小明思考了一会说,小红你点击刷新再试试,果然,刷新后项目有了变化,新特性出来了,但是这时又有了新的问题,项目里的图片似乎还是旧图,小明又思考了一会,随后在电脑前捣鼓一番,让小红再次体验,终于,这个时候特性完整验收通过。上述的案例中,其实就包含着http缓存和cdn缓存的应用,当然,这是一个反面教材,实际上线过程中,我们不可能让每一个用户点击刷新来体验我们的新特性,那应该如何解决上述问题呢,接下来上干货。
http缓存是客户端缓存,浏览器作为客户端接受到服务端响应后,对于响应首部字段进行解析,分析出相应的缓存规则,将资源按规则进行缓存,再次请求时如果命中缓存则直接读取本地缓存不再发出请求。
Expires,Cache-Control ,Last-Modified ,Etag 四个字段,Expires和Cache-Control用来确定确定缓存的存储时间,Last-Modified 和Etag则用来确定缓存是否要被更新,我们简单来看一下区别。
- expires: HTTP1.0中用来控制缓存时间的参数,响应头包含日期/时间, 即在此时间之后,响应过期。
- cache-control: HTTP1.1中用来控制缓存时间的参数
- public: 表明响应可以被任何对象(包括:发送请求的客户端,代理服务器,等等)缓存。
- private: 表明响应只能被单个用户缓存,不能作为共享缓存(即代理服务器不能缓存它)。
- max-age=<seconds>: 设置缓存存储的最大周期,相对于请求的时间缓存seconds秒,在此时间内,访问资源直接读取本地缓存,不向服务器发出请求。(与expires同时出现时,max-age优先级更高)
- s-maxage=<seconds>: 规则等同max-age,覆盖max-age 或者 Expires 头,但是仅适用于共享缓存(比如各个代理),并且私有缓存中它被忽略。(与expires或max-age同时出现时,s-maxage优先级更高)
- no-store: 不缓存服务器响应的任何内容,每次访问资源都需要服务器完整响应
- no-cache: 缓存资源,但立即过期,每次请求都需要跟服务器对比验证资源是否被修改。(等同于max-age=0)
- Last-modified: 源头服务器认定的资源做出修改的日期及时间。精确度比Etag低。包含有If-Modified-Since或 If-Unmodified-Since首部的条件请求会使用这个字段。
- Etag: HTTP响应头是资源的特定版本的标识符。
图中配置
- cache-control: max-age=31535000 代表相对于请求时间,缓存31536000秒,即365天,在此时间内,再次访问资源直接读取本地缓存,不向服务器发送请求.
- last-modified: Mon...上次修改时间,如果缓存时间过期,该字段将用于与请求中的If-Modified-Since字段进行对比,一致则继续使用之前缓存,不一致则认定缓存失效
- expires: 在http1.0版本下被cache-control覆盖,此处意为缓存至Sat, 11 Aug ...
缓存规则在其中是如何起作用的呢,我们来看几个重点关注部分
重点关注1: 缓存是否过期
基于该资源上次响应缓存规则同时满足下列条件则视为缓存未过期。需要注意的是,判断缓存是否过期只跟客户端有关系,与服务端无关。1&2&3同时满足即认为缓存未过期,相反则是已过期
- cache-control值为max-age
- max-age > 0
- 当前 date < 上次请求时的date + max-age
重点关注2: 询问服务器资源是否修改
判断资源是否修改,需要客户端与服务器共同协作,客户端在首次拿到资源缓存后会存储Etag(若有)和Last-Modified(若有),在下次缓存过期时会将Etag写在请求头部中的If-None-Match中,将Last-Modified值写在请求头部中的If-Modified-Since中,服务端优先对Etag进行对比,然后再对比Last-Modified,完全通过后即视为缓存没有修改,有一项不通过则认为资源已被修改,缓存失效
重点关注3: 缓存规则
缓存规则主要由cache-control字段和expires字段体现,同时出现则以cache-control为准具体情况如下:
- cache-control=no-store 不缓存任何资源
- cache-control=no-cache 缓存但立即过期
- cache-control=max-age(s-maxage) = 0 缓存但立即过期(等同于no-cache)
- cache-control=max-age(s-maxage)= seconds (seconds > 0)—— 基于请求时间缓存seconds秒
- cache-control=其他 根据http标准,如果不携带任何关于缓存时常的标记,则缓存时间等于当前时间和 Last-Modified时间的差值的10%,等同于cache-control=max-age=(date - Last-Modified)/ 10,通过fiddler抓包可看到英文原文:No explicit HTTP Cache Lifetime information was provided.Heuristic expiration policies suggest defaulting to: 10% of the delta between Last-Modified and Date.
- expires = 过去的时间或无效时间,缓存但立即过期,等同于cache-control=no-cache
- expires = 未来的时间,缓存到对应时间
缓存配置
从上述规则和与流程图中我们可以看到,缓存规则的配置其实并不复杂,除开Etag和Last-Modified用于缓存对比(实际使用中只需要开启该功能即可),我们需要关注的其实只是cache-control(expires可转化为max-age形式,不再赘述),方案如下:- cache-control: no-store:不缓存,每次访问都从服务下载所有资源。
- cache-control: no-cache或cache-control: max-age=0:对比缓存,缓存当前资源,但每次访问都需要跟服务器对比,检查资源是否被修改。
- cache-control: max-age=seconds //seconds > 0:强缓存,缓存当前资源,在一定时期内,再次请求资源直接读取本地缓存。
实际项目中,方案1的应用基本上看不到,对比方案2和方案3,方案1没有任何优势。在方案2和方案3的选择中,我们会对资源作区分。
- 对于img,css,js,fonts等非html资源,我们可以直接考虑方案3,并且max-age配置的时间可以尽可能久,类似于缓存规则案例中,cache-control: max-age=31535000配置365天的缓存,需要注意的是,这样配置并不代表这些资源就一定一年不变,其根本原因在于目前前端构建工具在静态资源中都会加入戳的概念(例如,webpack中的[hash],gulp中的gulp-rev),每次修改均会改变文件名或增加query参数,本质上改变了请求的地址,也就不存在缓存更新的问题。
- 对于html资源,我们建议根据项目的更新频度来确定采用哪套方案。html作为前端资源的入口文件,一旦被强缓存,那么相关的js,css,img等均无法更新。对于高频维护的业务类项目,建议采用方案2,或是方案3但max-age设置一个较小值,例如3600,一小时过期。对于一些活动项目,上线后不会进行较大改动,建议采用方案3,不过max-age也不要设置过大,否则一旦出现bug或是未知问题,用户无法及时更新。
小结
对于http缓存的配置,我们始终要做到两点,一是清楚明白http缓存的原理与规则,二是明确缓存的配置不是一次性的,根据不同的情况配置不同的规则,才能够更好的发挥http缓存的价值。cdn缓存
cdn缓存是一种服务端缓存,CDN服务商将源站的资源缓存到遍布全国的高性能加速节点上,当用户访问相应的业务资源时,用户会被调度至最接近的节点最近的节点ip返回给用户,在web性能优化中,它主要起到了,缓解源站压力,优化不同用户的访问速度与体验的作用。缓存规则
与http缓存规则不同的是,这个规则并不是规范性的,而是由cdn服务商来制定,我们以腾讯云举例,打开cdn加速服务配置,面板如下。可以看到,提供给我们的配置项只有文件类型(或文件目录)和刷新时间,意义也很简单,针对不同文件类型,在cdn节点上缓存对应的时间。
cdn运作流程
由图我们可以看出,cdn缓存的配置主要作用在缓存处理阶段,虽然配置项只有文件类型和缓存时间,但流程却并不简单,我们先来明确一个概念——回源,回源的意思就是返回源站,何为源站,就是我们自己的服务器,很多人误解接入cdn就是把资源放在了cdn上,其实不然,如图中所示,接入cdn后,我们的服务器就是源站,源站一般情况下只会在cdn节点没有资源或cdn资源失效时接收到cdn节点的请求,其他时间,源站并不会接收请求(当然,如果我们知道源站的地址,我们可以直接访问源站)。明确了回源的概念后,cdn的流程就显得不那么复杂了,简单的理解就是,没有资源就去源站读取,有资源就直接发送给用户。与http缓存不同的是,cdn中没有no-cache(max-age=0)的情况,当我们设置缓存时间为0的时候,该类型文件就被认定为不缓存文件,就是所有请求直接转发源站,只有当缓存时间大于0且缓存过期的时候,才会与源站对比缓存是否被修改。
缓存配置
cdn缓存配置并不麻烦,整体来说,建议和http缓存配置保持统一。需要特别注意的是,cdn的缓存配置会受到http缓存配置的影响,而且各个cdn服务商并不完全一致,以腾讯云为例,在缓存配置的文档中特别有以下说明。这会对我们有什么影响呢?
- 如果我们http缓存设置cache-control: max-age=600,即缓存10分钟,但cdn缓存配置中设置文件缓存时间为1小时,那么就会出现如下情况,文件被访问后第12分钟修改并上传到服务器,用户重新访问资源,响应码会是304,对比缓存未修改,资源依然是旧的,一个小时后再次访问才能更新为最新资源
- 如果不设置cache-control呢,在http缓存中我们说过,如果不设置cache-control,那么会有默认的缓存时间,但在这里,cdn服务商明确会在没有cache-control字段时主动帮我们添加cache-control: max-age=600。
小结
cdn缓存的配置并不复杂, 复杂的情况在于cdn缓存配置会受到http缓存配置的影响,并且不同的cdn运营商对于这种影响的处理也都不一致,实际使用时,建议去对应的cdn服务商文档中找到对应的注意事项。http缓存与cdn缓存的结合
当我们分别理解了http缓存配置和cdn缓存配置后,我们还有一件事情,就是理解二者结合时,请求的流向问题当用户访问我们的业务服务器时,首先进行的就是http缓存处理,如果http缓存通过校验,则直接响应给用户,如果未通过校验,则继续进行cdn缓存的处理,cdn缓存处理完成后返回给客户端,由客户端进行http缓存规则存储并响应给用户。当我们分析缓存问题时,一定要将两个流程独立开来分析,现在来看开篇时的错误案例,很明显,第一个问题时由于http缓存配置不合理,导致用户必须进行强制刷新才能更新资源,第二个问题则是cdn缓存未及时更新造成的。
总结
http缓存和cdn缓存分别作为客户端缓存和服务端缓存共同影响着我们的web请求流向,要想做好缓存配置,首先是清楚缓存的原理和配置规则,其次则是结合项目分析缓存级别,具体情况具体处理。