简介对HTTP协议稍有了解的前端同学对Cache-Control并不陌生,在优化性能的时候经常会和它打交道。常用的值有private、public、no-store、no-cache、must-revalidate、max-age等,每个值代表的含义网上总结的蛮多的,就不介绍了这里一一列举,有兴趣的可以一起探讨交流。本文仅选取no-cache和must-revalidate进行探索比较。在项目实践中,这两个值使用的比较频繁,也比较容易混淆。Cache-Control:no-cacheCache-Control:max-age=60,must-revalidate传送门:RFC2616关于Cache-Controlheader的介绍。no-cache和must-revalidateno-cache简介:告诉浏览器和缓存服务器,无论本地副本是否过期,在使用资源副本之前,都必须去源服务器验证副本有效性。must-revalidate:在本地副本过期之前,告诉浏览器和缓存服务器本地副本可以使用;一旦本地副本过期,它必须去源服务器进行有效性验证。上面的介绍涉及三个主体:浏览器、缓存服务器、源服务器。以下各节将作简要介绍。浏览器、缓存服务器、源站浏览器:资源请求的直接发起者。源服务器:资源的实际提供者。缓存服务器:设置在浏览器和源服务器之间的中间服务器,代替浏览器向源服务器发起资源请求;缓存服务器的功能如下。不需要缓存服务器,浏览器可以直接与源服务器通信。加快资源访问速度,减轻源服务器的负载。缓存服务器从源服务器获取资源并返回给浏览器。另外,缓存服务器一般会在本地保存一份资源副本。当对同一资源的请求到达时,缓存服务器可以返回资源副本,以提高资源访问速度。对比测试场景和环境准备对比测试场景以下部分将通过以下两种场景的对比测试来探究no-cache和must-revalidate的区别。浏览器直接访问源站。浏览器通过缓存服务器间接访问源站。环境准备操作系统:OSX10.11.4浏览器:Chrome52.0.2743.116(64位),Firefox49.0.2缓存服务器:Squid3.6源服务器:Express4.14.01,下载实验代码:可以访问github主页获取it,或者可以通过gitclone下载到本地。gitclonehttps://github.com/chyingp/tech-experiment.gitcdtech-experiment/2016.10.25-cache-control/npminstall2。安装Squid,步骤省略,附上下载地址。3.可选:启动squid,将本地http代理设置为squid的ip和端口。备注:该步骤仅针对“通过缓存服务器间接访问源服务器资源”的测试场景需要执行。4、可选:设置本地代理为Charles的地址,然后设置Charles的代理地址为Squid的代理地址。(避免浏览器开发者工具修改请求头,干扰实验结果)场景一:浏览器->源服务器首先通过如下脚本启动本地服务器(源服务器)。cdconnect-directlynodeserver.jsCache-Control:no-cache用例一:第二次访问,源服务器上的资源没有变化访问地址为:http://127.0.0.1:3000/no-cache第一步:第一次访问,返回内容如下。如您所见,返回Cache-Control:no-cache。HTTP/1.1200OKX-Powered-By:ExpressCache-Control:no-cacheContent-Type:text/html;charset=utf-8Content-Length:11ETag:W/"b-s0vwqaICscfrawwztfPIiA"Date:Wed,26Oct201607:46:28GMTConnection:keep-alivestep2:第二次访问,返回内容如下。返回的状态码为304NotModified,表示经过验证,源服务器上的资源没有变化,浏览器可以使用本地副本。HTTP/1.1304NotModifiedX-Powered-By:ExpressCache-Control:no-cacheETag:W/"b-s0vwqaICscfrawwztfPIiA"Date:Wed,26Oct201607:47:31GMTConnection:keep-alive用例2:二次访问,源服务器上的资源更改第一步:访问地址为:http://127.0.0.1:3000/no-cach...备注:change=1告诉源服务器每次访问返回不同的内容第一步:第一次访问,内容如下,不再赘述。HTTP/1.1200OKX-Powered-By:ExpressCache-Control:no-cacheContent-Type:text/html;charset=utf-8Content-Length:11ETag:W/“b-8n8r0vUN+mIIQCegzmqpuQ”日期:周三,2016年10月26日:48:01GMTConnection:keep-aliveStep2:第二次访问,返回内容如下。注意Etag变了,说明源服务器的资源变了。所以状态码是200OK,源服务器向浏览器返回新版本的资源。HTTP/1.1200OKX-Powered-By:ExpressCache-Control:no-cacheContent-Type:text/html;charset=utf-8Content-Length:11ETag:W/"b-0DK7Mx61dfZc1vIPJDSNSQ"Date:Wed,26Oct201607:48:38GMTConnection:keep-aliveCache-Control:must-revalidate访问地址:http://127.0.0.1:3000/must-re...可选参数说明:max-age:源站返回的内容,什么是max-age(单位为秒)。change:源站返回的内容是否有变化,如果为1则有变化。用例1:第二次访问,浏览器缓存未过期访问地址:http://127.0.0.1:3000/must-re...备注:max-age=10表示资源预计缓存10s第一步:***访问,返回内容如下。HTTP/1.1200OKX-Powered-By:ExpressCache-Control:max-age=10,must-revalidateContent-Type:text/html;charset=utf-8Content-Length:16ETag:W/"10-dK948plT5cojN3y7Cy717w"日期:星期三,26Oct201608:06:16GMTConnection:keep-alive第二步:第二次访问(10s以内),如下截图所示,浏览器直接从本地缓存中读取资源副本,没有重新发起HTTP请求。用例2:第二次访问,浏览器缓存已过期,源服务器上的资源没有变化第一步:跳过第一次访问。如下截图所示(10s后),第二次访问返回304NotModified。HTTP/1.1304NotModifiedX-Powered-By:ExpressCache-Control:max-age=10,must-revalidateETag:W/"10-dK948plT5cojN3y7Cy717w"Date:Wed,26Oct201608:09:22GMTConnection:keep-alive已过期,源服务器资源已更改访问地址:http://127.0.0.1:3000/must-re...第一步:第一次访问,截图如下。第二步:第二次访问(10s后),返回截图如下,可以看到返回了200。场景二:Browser->CacheServer->OriginServer通过上面的对比实验,我们知道no-cache和must-revalidate在不经过缓存服务器进行缓存验证方面的区别。接下来我们看看引入缓存服务器后两者的区别。备注:下面我们将检查Squid访问日志来确认缓存服务器的行为。这里对日志中的几个关键字进行粗略的解释:TCP_MISS:No***cache。有可能是缓存服务器没有资源的副本,也有可能是资源副本已经过期。TCP_MEM_HIT:命中缓存。缓存服务器上存在该资源的副本,并且该副本尚未过期。再贴上一张图。Cache-Control:no-cache用例一:chrome第一次访问资源chrome访问截图如下:200oksquidlog:TCP_MISS,说明没有***本地资源的副本。1477501799.57317127.0.0.1TCP_MISS/200299GEThttp://127.0.0.1:3000/no-cache-HIER_DIRECT/127.0.0.1text/html用例2:Chrome再次访问资源。而在源服务器上,资源没有变化访问地址:http://127.0.0.1:3000/no-cache最后访问省略。第二次访问,chrome访问截图如下:squid访问日志如下:TCP_MISS/304。表示缓存服务器联系源服务器发现内容没有变化,所以返回304.1477501987.7851127.0.0.1TCP_MISS/304238GEThttp://127.0.0.1:3000/no-cache-HIER_DIRECT/127.0.0.1-案例3:Chrome访问资源。并且在源服务器上,资源发生了变化访问地址:http://127.0.0.1:3000/no-cach...备注:change=1表示每次访问源服务器,返回的资源都是新的。省略了第一次访问。第二次访问,chrome截图如下,状态码为200,从squid日志看,缓存服务器访问源站,返回200给浏览器。1477647837.2161127.0.0.1TCP_MISS/200299GEThttp://127.0.0.1:3000/no-cache?-HIER_DIRECT/127.0.0.1text/htmlCache-Control:must-revalidate情况一:缓存服务器已有资源副本,资源copy未过期访问地址:http://127.0.0.1:3000/must-re...备注:max-age=900表示资源有效期为900s第一步:Chrome首次访问资源,并且资源在缓存服务器副本上不存在,然后访问源服务器。最后缓存服务器返回200给浏览器。此时,缓存服务器squid上有一份资源副本。第二步:firefox第一次访问资源(900s以内)。缓存服务器上已存在该资源的副本,并且该副本未过期。因此,缓存服务器将资源副本返回给firefox,状态代码为200。(cache***)要验证在步骤2中缓存服务器返回了本地资源副本,请检查squid日志。其中,第二个是firefox的访问记录,TCP_MEM_HIT/200表示***本地缓存。1477648947.5945127.0.0.1TCP_MISS/200325GEThttp://127.0.0.1:3000/must-revalidate?-HIER_DIRECT/127.0.0.1text/html1477649012.6250127.0.0.1TCP_MEM_HIT/200333GEThttp://127.0.0.1:3000/must-revalidate?-HIER_NONE/-text/html用例2:缓存服务器上已经存在资源副本,资源副本已经过期,但是源服务器上的资源没有变化访问链接:http://127.0.0.1:3000/must-re...使用chrome依次访问资源,间隔10s以上。在第二次访问时,chrome收到以下响应。检查鱿鱼日志。可以看到状态为TCP_MISS/304,说明本地副本已经过期。与源服务器核对后,发现源服务器上的资源没有变化。因此,向浏览器返回304。1477649429.10511127.0.0.1TCP_MISS/304258GEThttp://127.0.0.1:3000/must-revalidate?-HIER_DIRECT/127.0.0.1-案例三:缓存服务器上已经存在资源副本,资源副本已过期,但资源源服务器上的已更改。访问地址:http://127.0.0.1:3000/must-re...使用chrome依次访问资源,间隔10s以上。第二次访问时,chrome收到如下响应,squid日志如下,状态为TCP_MISS/200,说明没有***缓存。1477650702.8078127.0.0.1TCP_MISS/200325GEThttp://127.0.0.1:3000/must-revalidate?-HIER_DIRECT/127.0.0.1text/html1477651020.5164127.0.0.1TCP_MISS/200325GET-revalid-revalidate/127.30mHIER_DIRECT/127.0.0.1text/html对比结论以下都是浏览器第n次访问资源的情况。(n>1)不管缓存服务器的header是否过期,本地缓存已经过期。源服务器资源是否发生变化。状态代码是否已重新生效。(来自浏览器缓存)must-revalidateis304must-revalidateisyesis200考虑缓存服务器header本地缓存是否过期副本是否过期源服务器资源是否发生变化是否重新验证状态码no-cache不确定否304no-cache不确定不确定是不是200must-revalidatenoyes/noyes/nono200(frombrowsercache)must-revalidateyes/noyes304(fromcacheserver)must-revalidateyesyes304must-revalidateyesyesyes是200.经过一轮对比测试,发现no-cache和must-revalidate这两个值还是挺有意思的。其实由于篇幅原因,还有一些内容还没有对比。例如:must-revalidate或no-cache与max-stale一起使用时的性能。no-cache和max-age=0的区别,mustvalidate。no-chche指定特定字段名和不指定特定字段名时缓存验证行为的区别。proxy-revalidate和must-revalidate之间的区别。缓存服务器本身的优化算法对实验结果的影响。比较实验过程比较枯燥和繁琐。如果有不严谨或者错误的地方还请指出:)这里提一个经常遇到的问题供读者讨论:no-cache和max-age=0的区别,mustvalidate。
