9

前几天看到一篇关于缓存的文章彻底弄懂 Http 缓存机制 - 基于缓存策略三要素分解法,觉得很有意思,所以打算系统学习下Http缓存相关的知识。

我把缓存分为缓存存储缓存对比两部分。

  1. 基本概念

  2. 命中缓存速度对比

  3. 200 from cache  vs  304 Not Modified 

  4. 思考:localStorage存储

(一)基本概念

(1)缓存存储

  • Pragma : no-cache  http1.0时期的属性  为了兼容会使用

  • Expires:0  http1.0时期的属性 

  • Cache-Control  http1.1 中加入的新属性,它有以下常用参数:

    • Public/Private 私有缓存/共有缓存

    • no-cache:不建议使用本地缓存,但仍然会缓存到本地

    • no-store:不会在客户端缓存任何数据

    • max-age:比较特殊,是一个混合属性,替代了Expires的过期时间

举个栗子:如果要设置客户端不缓存,并兼容http1.0的方式可以这样写:

Pragma : no-cache 
Expires:0
Cache-Control:no-store

等价于

Pragma : no-cache  // Pragma为了兼容http1.0
Cache-Control:max-age=0  // 去掉了Expires属性(下面名词解释会说到为什么被去掉),合并到max-age中,

名词解释:
私有缓存:《HTTP权威指南》里面讲到了私有缓存的一种就是在浏览器里面输入  about:cache  可以查看自己浏览器缓存的内容,会给出一个显示了缓存内容“磁盘缓存统计”页面,这个可以看看还挺有意思,能展示不少信息
Expires:过时期限值,GMT格式,是Web服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。不过Expires 是HTTP 1.0的东西,现在默认浏览器均默认使用HTTP 1.1,所以它的作用基本忽略。Expires 的一个缺点就是,返回的到期时间是服务器端的时间,这样存在一个问题,如果客户端的时间与服务器的时间相差很大(比如时钟不同步,或者跨时区),那么误差就很大,所以在HTTP 1.1版开始,使用Cache-Control: max-age=秒替代。

(2)缓存对比

  • Last-Modified  http1.0时期属性  现在仍在使用

  • ETag(Entity Tag)  http1.1时期新加属性 ,使用inode+mtime(以下有解释)来计算。根据实体内容生成的一段hash字符串(类似于MD5或者SHA1之后的结果),可以标识资源的状态。 当资源发送改变时,ETag也随之发生变化。

名词解释:
inode :包含文件的元信息,包括以下内容

  • 文件的字节数、文件拥有者的User ID、文件的Group ID

  • 文件的读、写、执行权限

  • 文件的时间戳,共有三个:ctime指inode上一次变动的时间,mtime指文>件内容上一次变动的时间,atime指文件上一次打开的时间。

  • 链接数,即有多少文件名指向这个inode、 文件数据block的位置
    mtime:指文件内容上一次变动的时间

2.1为什么用http1.1新推出了ETag

  1. 某些服务器不能精确得到文件的最后修改时间, 这样就无法通过最后修改时间来判断文件是否更新了。

  2. 某些文件的修改非常频繁,在秒以下的时间内进行修改. Last-Modified只能精确到秒。

  3. 一些文件的最后修改时间改变了,但是内容并未改变。 我们不希望客户端认为这个文件修改了。

2.2ETag有哪些问题

ETag也有他自己的问题,所以经常在使用中会关闭ETag。举例来说,同一个文件在不同物理机上的inode是不同的,这就导致了在分布式的Web系统中,当访问落在不同的物理机上时会返回不同的ETag,进而导致304失效,降级为200请求。解决办法也有从ETag算法中剥离inode,只是使用mtime,但是这样实际上和Last-Modified就一样了。当然你也可以额外的做一些改进,使ETag对静态资源的算法也是通过hash计算的。不过由于一般我们会使用CDN技术,因此集群部署中的ETag问题并不会造成太大的影响,所以折腾的人也不太是很多。  参考:这篇文章hefangshi同学的回答

(二)命中缓存速度对比

图片描述
引一张《HTTP权威指南》中的一张图,可以看出命中缓存过程:

(1)缓存命中速度

缓存命中 > 缓存再验证成功 > 缓存未命中 = 缓存再验证失败;

1.1缓存命中优先级

Cache-Control http1.1 > Expires > Pragma http1.0来决定是否 (200 from cache)

1.2缓存再验证成功

根据Last-Modified http1.0 和 ETaghttp1.1 来验证是否返回 (304 Not Modified) 两者都有,就必须同时验证,并且两者都满足才会返回304;

图片描述

  • 服务端响应头 Last-Modified 与 客户端请求头 If-Modified-Since 对应

  • 服务端响应头 ETag 与 客户端请求头 If-None-Match

(三) 200 from cache vs 304 Not Modified

为什么有时候明明命中了缓存,控制台中Status显示的不是 200 from cache ?原来是浏览器的原因:

  • 触发 200 from cache:

    • 直接点击链接访问

    • 输入网址按回车访问

    • 二维码扫描

  • 触发 304 Not Modified:

    • 刷新页面时触发

    • 设置了长缓存、但 Entity Tags 没有移除时触发

二者怎么选择

毫无疑问选择可以尽量多的命中缓存,然后靠更新静态文件的版本号来使缓存失效。关于版本号建议使用 file.xxx.js 的形式而不是 file.js?v=xxx。

可以看这两篇文章有讲述原因:

  1. Best Practices for Speeding Up Your Web Site

  2. 大公司里怎样开发和部署前端代码

(四)思考

在研究缓存问题的时候,知乎上看到这个问题:静态资源(JS/CSS)存储在localStorage有什么缺点?为什么没有被广泛应用? ,看了大神们的答案主要是维护成本实在过高,如果真的速度超快,这点可以忽略,值得花时间研究,但是如果读取再执行的速度可能会比浏览器直接304性能要低,就完全没有必要使用这种方式了。

(五 )参考文章:

  1. 配置错误产生的差距:200 OK (FROM CACHE) 与 304 NOT MODIFIED 

  2. http://www.benhallbenhall.com/2012/03/http-codes-200-from-cache-304/


阿驴
328 声望19 粉丝