首頁 >常見問題 >如何設計一個完美的http快取策略

如何設計一個完美的http快取策略

angryTom
angryTom轉載
2019-08-20 17:35:543508瀏覽

如何設計一個完美的http快取策略

1、前言

  作為一個前端,了解http快取是非常必要,它不僅是面試的必要環節,也更是實戰開發中必不可少需要了解的知識點,本文作者將從緩存的概念講到如何在業務中設計一個合理的緩存架構,帶你一步一步解開http緩存的神秘面紗。

2、http快取定義

  當客戶端向伺服器請求資源時,會先抵達瀏覽器緩存,如果瀏覽器有「要請求資源」的副本,就可以直接從瀏覽器快取中提取而不是從原始伺服器中提取這個資源。 http快取一般針對GET請求,POST請求一般不會緩存,因為POST請求執行的任務都是對伺服器產生副作用或者非冪等性的任務,既然要改變伺服器資源,自然請求是要進入伺服器進行處理的。快取帶來的好處是巨大的,減少了http請求,自然也就減少的服務端壓力,並且增加了資源的存取速度,但是胡亂使用緩存,將會帶來資源的不及時更新,甚至資源更新錯位,災難也是巨大的。
http快取分為強緩存和協商緩存,以下帶大家一一了解兩種快取機制。

3、強緩存

3.1、強快取定義

  如果命中緩存,直接從快取拿數據,請求不會經過伺服器,傳回的http狀態碼為200(from disk cache)

如何設計一個完美的http快取策略

  下面給一張流程圖來說明強緩存的請求過程,為了方便假設瀏覽器存在一個快取資料庫(其實就是disk磁碟,快取資料存放的地方)。

如何設計一個完美的http快取策略

如何設計一個完美的http快取策略

  仔細看上面的流程圖,強緩存的最大特點就是在命中快取的情況下不會經過伺服器,而是直接返回。

3.2、Http頭Expires/Cache-Control設定強緩存

  Cache-Control裡面存在多個屬性來控制緩存,設定強緩存即設定資源的有效期,屬性為max-age .
下面使用Express給大家示範

app.get('/script1.js', function (req, res, next) {  
  // res.header('Cache-Control', 'must-revalidate, max-age=600')    
  // res.header('Content-Type', 'text/html')
    res.header('Cache-Control', 'max-age=20')
    res.sendFile(__dirname + '/script.js')
})

如何設計一個完美的http快取策略

#  Expires和max-age都是用來控制快取的生命週期。不同的是Expires指定的是過期的具體時間,例如Sun, 21 Mar 2027 08:52:14 GMT,而max-age指定的是生命時長秒數315360000。

  差異在於Expires是 HTTP/1.0 的中的標準,而max-age是屬於Cache-Control的內容,是 HTTP/1.1 中的定義的。但為了想向前相容,這兩個屬性仍然要同時存在。 max-age是要優先於Expires的。

4、協商/比較快取

4.1、定義

  協商快取與強制快取的不同之處在於,協商快取每次讀取資料時都需要跟隨伺服器通信,並且會增加快取標識。在第一次要求伺服器時,伺服器會傳回資源,並且傳回一個資源的快取標識,一起儲存到瀏覽器的快取資料庫。當第二次請求資源時,瀏覽器會先將快取標識傳送給伺服器,伺服器拿到標識後判斷標識是否匹配,如果不匹配,表示資源有更新,伺服器會將新資料和新的快取標識一起傳回到瀏覽器;如果快取標識匹配,表示資源沒有更新,並且返回304 狀態碼,瀏覽器就會讀取本機快取伺服器中的資料。

  協商快取的最大特點是要經過伺服器驗證的,下面我們來講解協商快取的驗證流程。

第一次造訪:

如何設計一個完美的http快取策略

再次造訪:

如何設計一個完美的http快取策略

還是給一張流程圖來說明。 (圖是盜的,協商快取也可以成為比較緩存,圖中的比較緩存就是協商快取)
如何設計一個完美的http快取策略

#

4.2、协商缓存如何验证

  第一次请求将response header的Last-Modified和Etag存起来,在第二次请求通过request header的If-Modified-Since和If-None-Match传到服务端进行验证,如果命中缓存,返回304,不带返回的数据,浏览器自动从缓存中获取数据资源,若未命中缓存返回200,带上数据资源。

** Last-Modified:**

服务器在响应请求时,告诉浏览器资源的最后修改时间。

如何設計一個完美的http快取策略

** If-Modified-Since:**

再次请求服务器时,通过此字段通知服务器上次请求时,服务器返回的资源最后修改时间。

服务器收到请求后发现有头If-Modified-Since 则与被请求资源的最后修改时间进行比对。

若资源的最后修改时间大于If-Modified-Since,说明资源又被改动过,则响应整片资源内容,返回状态码200;

若资源的最后修改时间小于或等于If-Modified-Since,说明资源无新修改,则响应HTTP 304,告知浏览器继续使用所保存的cache。

如何設計一個完美的http快取策略

** Etag / If-None-Match(优先级高于Last-Modified / If-Modified-Since) **

** Etag:**

服务器响应请求时,告诉浏览器当前资源在服务器的唯一标识(生成规则由服务器决定)。

如何設計一個完美的http快取策略

** If-None-Match:**

再次请求服务器时,通过此字段通知服务器客户段缓存数据的唯一标识。

服务器收到请求后发现有头If-None-Match 则与被请求资源的唯一标识进行比对,

不同,说明资源又被改动过,则响应整片资源内容,返回状态码200;

相同,说明资源无新修改,则响应HTTP 304,告知浏览器继续使用所保存的cache。

如何設計一個完美的http快取策略

4.3、Http头如何设置协商缓存

  在强缓存那一节说到使用Cache-Control的max-age来设置资源过期时间,那么当max-age=0的时候呢,自然浏览器第一时间发现资源过期,request header就会带着If-Modified-Since和If-None-Match去服务端验证。
所以设置response header为:

Cache-Control: max-age=0

  就可以触发协商缓存了,其实Cache-Control中还有两个属性都可以设置协商缓存 must-revalidate和no-cache
must-revalidate的意义为必须进行验证,但是它一般是和max-age一起使用的,不会单独使用,

Cache-Control: must-revalidate, max-age=600

  该头信息意义就是在资源有效期过后必须进行验证, 与只设置max-age=600的区别是,前面一个是MUST,而后面一个是SHOULD,理论上来说它们的效果是一致的。

  no-cache的意义千万不能理解为不缓存,下面两段代码的意义是一样的,即请求必须进行验证,才可以使用缓存资源,注意是MUST

Cache-Control: no-cacheCache-Control: must-revalidate, max-age=0

  如果要不缓存,每次都请求新的资源应该使用

Cache-Control: no-store

5、关于缓存的Http头总结

5.1、"no-cache", "no-store", "must-revalidate"

  Cache-Control字段可以设置的不仅仅是max-age存储时间,还有其他额外的值可以填写,甚至可以组合。主要使用的值有如下:

  no-cache: 虽然字面意义是“不要缓存”。但它实际上的机制是,仍然对资源使用缓存,但每一次在使用缓存之前必须(MUST)向服务器对缓存资源进行验证。

  no-store: 不使用任何缓存

  must-revalidate: 如果你配置了max-age信息,当缓存资源仍然新鲜(小于max-age)时使用缓存,否则需要对资源进行验证。所以must-revalidate可以和max-age组合使用Cache-Control: must-revalidate, max-age=60

  有趣的事情是,虽然no-cache意为对缓存进行验证,但是因为大家广泛的错误的把它当作no-store来使用,所以有的浏览器也就附和了这种设计。这是一个典型的劣币驱逐良币。

5.2、Expires VS. max-age

  Expires和max-age都是用于控制缓存的生命周期。不同的是Expires指定的是过期的具体时间,例如Sun, 21 Mar 2027 08:52:14 GMT,而max-age指定的是生命时长秒数315360000。

  区别在于Expires是 HTTP/1.0 的中的标准,而max-age是属于Cache-Control的内容,是 HTTP/1.1 中的定义的。但为了想向前兼容,这两个属性仍然要同时存在。

  但有一种更倾向于使用max-age的观点认为Expires过于复杂了。例如上面的例子Sun, 21 Mar 2027 08:52:14 GMT,如果你在表示小时的数字缺少了一个0,则很有可能出现出错;如果日期没有转换到用户的正确时区,则有可能出错。这里出错的意思可能包括但不限于缓存失效、缓存生命周期出错等。

5.3、Etag VS. Last-Modified

  Etag和Last-Modified都可以用于对资源进行验证,而Last-Modified顾名思义,表示资源最后的更新时间。

  我们把这两者都成为验证器(Validators),不同的是,Etag属于强验证(Strong Validation),因为它期望的是资源字节级别的一致;而Last-Modified属于弱验证(Weak Validation),只要资源的主要内容一致即可,允许例如页底的广告,页脚不同。

  根据RFC 2616标准中的13.3.4小节,一个使用HTTP 1.1标准的服务端应该(SHOULD)同时发送Etag和Last-Modified字段。同时一个支持HTTP 1.1的客户端,比如浏览器,如果服务端有提供Etag的话,必须(MUST)首先对Etag进行Conditional Request(If-None-Match头信息);如果两者都有提供,那么应该(SHOULD)同时对两者进行Conditional Request(If-Modified-Since头信息)。如果服务端对两者的验证结果不一致,例如通过一个条件判断资源发生了更改,而另一个判定资源没有发生更改,则不允许返回304状态。但话说回来,是否返回还是通过服务端编写的实际代码决定的。所以仍然有操纵的空间。

5.4、max-age=0 VS. no-cache

  max-age=0是在告诉浏览器,资源已经过期了,你应该(SHOULD)对资源进行重新验证了;而no-cache则是告诉浏览器在每一次使用缓存之前,你必须(MUST)对资源进行重新验证。

  区别在于,SHOULD是非强制性的,而MUST是强制性的。在no-cache的情况下,浏览器在向服务器验证成功之前绝不会使用过期的缓存资源,而max-age=0则不一定了。虽然理论上来说它们的效果应该是一致的。

5.5、public VS. private

  要知道从服务器到浏览器之间并非只有浏览器能够对资源进行缓存,服务器的返回可能会经过一些中间(intermediate)服务器甚至甚至专业的中间缓存服务器,还有CDN。而有些请求返回是用户级别、是私人的,所以你可能不希望这些中间服务器缓存返回。此时你需要将Cache-Control设置为private以避免暴露。

6、缓存实战

  前面写了很多缓存的基础知识,那么如何设计一个可靠的缓存规则,这个其实得根据你的实际需求而定。

  比如某个资源永远不会改变,比如某些第三方库(一般都放CDN做优化了),或者某些图片,比如百度的图片,就让它们永久缓存着吧,设置一个最大的max-age

Cache-Control: max-age=31536000

其他的资源可根据下面这张决策树来进行设置

如何設計一個完美的http快取策略

7、memory cache

如何設計一個完美的http快取策略

  “内存缓存”中主要包含的是当前文档中页面中已经抓取到的资源。例如页面上已经下载的样式、脚本、图片等。我们不排除页面可能会对这些资源再次发出请求,所以这些资源都暂存在内存中,当用户结束浏览网页并且关闭网页时,内存缓存的资源会被释放掉。

  这其中最重要的缓存资源其实是preloader相关指令(例如)下载的资源。总所周知preloader的相关指令已经是页面优化的常见手段之一,而通过这些指令下载的资源也都会暂存到内存中。根据一些材料,如果资源已经存在于缓存中,则可能不会再进行preload。

  需要注意的事情是,内存缓存在缓存资源时并不关心返回资源的HTTP缓存头Cache-Control是什么值,同时资源的匹配也并非仅仅是对URL做匹配,还可能会对Content-Type,CORS等其他特征做校验
这个应该是浏览器做的一种优化,缓存也只是暂时的。

8、瀏覽器可能會限制Cache-Control頭無效

如何設計一個完美的http快取策略

推薦教學:HTTP中文開發手冊

以上是如何設計一個完美的http快取策略的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:cnblogs.com。如有侵權,請聯絡admin@php.cn刪除