<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          前端資源請(qǐng)求速度優(yōu)化

          共 11906字,需瀏覽 24分鐘

           ·

          2021-07-27 20:13

          來(lái)源 | http://www.zhiqianduan.com


          DNS解析

          當(dāng)瀏覽器從第三方服務(wù)器請(qǐng)求資源時(shí),必須先將該跨域域名解析為IP地址,然后瀏覽器才能發(fā)出請(qǐng)求,此過(guò)程稱為DNS解析。
          DNS作為互聯(lián)網(wǎng)的基礎(chǔ)協(xié)議,其解析的速度似乎很容易被網(wǎng)站優(yōu)化人員忽略,現(xiàn)在大多數(shù)新流量乃全已經(jīng)針對(duì)DNS解析進(jìn)行了優(yōu)化,比如DNS緩存。
          典型的一次DNS解析需要耗費(fèi)20-120毫秒,所花費(fèi)的時(shí)間幾乎可以忽略不計(jì),但是當(dāng)網(wǎng)站中使用的資源依賴多個(gè)不同域的時(shí)候,時(shí)間就會(huì)成倍增加,從而增加了網(wǎng)站的加載時(shí)間。
          比如某些圖片較多的頁(yè)面中,在發(fā)起圖片加載請(qǐng)求之前預(yù)先把域名解析好將會(huì)有至少5%的圖片加載速度提成。
          一般來(lái)說(shuō)前端與華中與DNS有關(guān)的有兩點(diǎn),一是減少DNS的請(qǐng)求次數(shù)(緩存DNS地址),二是進(jìn)行DNS的預(yù)獲取,DNS Prefetch。
          dns緩存可以在服務(wù)器設(shè)置DNS緩存的時(shí)間,不經(jīng)常變更的ip建議設(shè)置的時(shí)間長(zhǎng)一些。盡可能使用A或者AAAA代替CNAME,使用CND加速域名。還可以自己搭建DNS服務(wù)。
          DNS與解析可以在頁(yè)面中通過(guò)link標(biāo)簽來(lái)實(shí)現(xiàn)。
          <link rel="dns-prefetch" href="https://fonts.googleapis.com" />

          DNS與解析只能解析不同域,同域是不能解析的,因?yàn)橐呀?jīng)解析完了。dns-prefetch要慎用,不要每個(gè)頁(yè)面都添加,會(huì)造成資源浪費(fèi)。

          默認(rèn)情況下瀏覽器會(huì)對(duì)當(dāng)前頁(yè)面中所有出現(xiàn)的域名進(jìn)行預(yù)解析,及時(shí)沒有寫link標(biāo)簽,這是隱式解析。

          HTTP1.1長(zhǎng)鏈接

          經(jīng)過(guò)DNS解析獲取到IP之后就要進(jìn)行TCP的鏈接進(jìn)行數(shù)據(jù)傳輸。

          HTTP協(xié)議的初始版本中,每進(jìn)行一次HTTP通信就要斷開一次TCP鏈接,也就是短連接。

          以早期的通信情況來(lái)說(shuō),因?yàn)槎际切┤萘亢苄〉奈谋緜鬏?,所以即使這樣也沒有多大問(wèn)題,但是隨著HTTP的大量普及,文旦中包含大量富文本的情況多了起來(lái)。每次的請(qǐng)求都會(huì)造成無(wú)謂的TCP鏈接建立和斷開,增加通信錄的開銷。

          為了解決這個(gè)問(wèn)題,有些瀏覽器在請(qǐng)求時(shí),用了一個(gè)非標(biāo)準(zhǔn)的Connection字段。這個(gè)字段要求服務(wù)器不要關(guān)閉TCP鏈接,以便其他請(qǐng)求復(fù)用,服務(wù)器同樣回應(yīng)這個(gè)字段。

          Connection: keep-alive

          一個(gè)可以復(fù)用的TCP鏈接就建立了,直到客戶端或服務(wù)器主動(dòng)關(guān)閉鏈接,但是這并非標(biāo)準(zhǔn)字段,不同實(shí)現(xiàn)的行為可能不一致,還可能造成混亂。

          長(zhǎng)鏈接

          HTTP1.1版本在1997年1月發(fā)布,最大的變化就是引入了持久鏈接,即TCP鏈接默認(rèn)不關(guān)閉,可以被多個(gè)請(qǐng)求復(fù)用,不需要再聲明Connection: keep-alive。

          持久連接減少了TCP鏈接的重復(fù)建立和斷開所造成的的額外開銷,減輕了服務(wù)器端的負(fù)載。減少開銷的時(shí)間讓HTTP請(qǐng)求和響應(yīng)能夠更早的結(jié)束,這樣Web頁(yè)面的速度也就響應(yīng)變快了。

          客戶端和服務(wù)器發(fā)現(xiàn)對(duì)方一段時(shí)間沒有活動(dòng),就可以主動(dòng)關(guān)閉鏈接,不過(guò)規(guī)范的做法是客戶端在最后一個(gè)請(qǐng)求時(shí)發(fā)送Connection: close,明確要求服務(wù)器關(guān)閉鏈接。目前對(duì)于同一個(gè)域名,大多數(shù)瀏覽器允許同時(shí)建立6個(gè)持久鏈接。

          管道機(jī)制

          同一個(gè)TCP鏈接里面客戶端可以同時(shí)發(fā)送多個(gè)請(qǐng)求,這樣就進(jìn)一步改變了HTTP協(xié)議的效率。

          從前發(fā)送請(qǐng)求后需等待及接收響應(yīng),才能發(fā)送下一個(gè)請(qǐng)求,管道化技術(shù)出現(xiàn)后不用等待響應(yīng)即可直接發(fā)送下一個(gè)請(qǐng)求,這樣就能夠做到同時(shí)并行發(fā)送多個(gè)請(qǐng)求,而不需要一個(gè)接一個(gè)的等待響應(yīng)了。

          管道化技術(shù)比持久化鏈接還要快,請(qǐng)求數(shù)越多時(shí)間差越明顯。

          一個(gè)TCP鏈接可以傳送多個(gè)回應(yīng),勢(shì)必就要有一種機(jī)制,區(qū)分數(shù)據(jù)包是屬于哪一個(gè)回應(yīng)的,這就是Content-length字段的作用,聲明本次回應(yīng)的數(shù)據(jù)長(zhǎng)度。

          Content-Length: 3000

          上面代碼告訴瀏覽器,本次回應(yīng)的長(zhǎng)度是3000個(gè)字節(jié),后面的字節(jié)就屬于下一個(gè)回應(yīng)了。

          在1.0版本中,Content-Length字段不是必須的,因?yàn)闉g覽器發(fā)現(xiàn)服務(wù)器關(guān)閉了TCP鏈接,就表明收到的數(shù)據(jù)包已經(jīng)完成了。

          分塊傳輸

          使用Content-Length字段的前提條件是,服務(wù)器發(fā)送回應(yīng)之前,必須知道回應(yīng)的數(shù)據(jù)長(zhǎng)度。

          對(duì)于一些耗時(shí)的動(dòng)態(tài)操作來(lái)說(shuō),意味著,服務(wù)器要等到所有操作完成,才能發(fā)送數(shù)據(jù),顯然這樣的效率不高,更好的方法是產(chǎn)生一塊數(shù)據(jù)就發(fā)送一塊,采用流模式取代緩存模式。

          因此1,1規(guī)定可以不使用content-length字段,而是用分塊傳輸編碼,只要請(qǐng)求或響應(yīng)頭信息有Transfer-Encoding字段,就表明響應(yīng)將又?jǐn)?shù)量未定的數(shù)據(jù)塊組成。

          Transfer-Encoding: chunked

          每個(gè)非空數(shù)據(jù)塊之前會(huì)有一個(gè)16進(jìn)制的數(shù)值,表示這個(gè)塊的的長(zhǎng)度,最后是一個(gè)大小為0的塊,表示本次回應(yīng)的數(shù)據(jù)發(fā)送完了。

          HTTP/1.1 200 OK...25This is the data in the first chunk...2...4...0...

          雖然HTTP1.1允許復(fù)用TCP鏈接,但是同一個(gè)TCP鏈接里面,所有的數(shù)據(jù)通信是按次序進(jìn)行的,服務(wù)器只有處理完一個(gè)回應(yīng)才會(huì)進(jìn)行下一個(gè)回應(yīng)。

          如果前面的請(qǐng)求慢,后面就會(huì)有需要請(qǐng)求排隊(duì),稱為對(duì)頭阻塞。

          為了避免這種問(wèn)題,可以減少請(qǐng)求數(shù)或者同事多開持續(xù)請(qǐng)求。

          這就出現(xiàn)了很多的優(yōu)化技巧,比如說(shuō)。合并腳本和樣式表,將圖片嵌入css代碼,域名分片等等。

          其實(shí)如果HTTP協(xié)議設(shè)計(jì)的更好一些,這些額外的工作都是可以避免的。

          HTTP2協(xié)議

          為了解決響應(yīng)阻塞問(wèn)題2015年推出了HTTP2。

          HTTP2主要用于解決HTTP1.1效率不高的問(wèn)題,他不叫HTTP2.0是因?yàn)椴淮蛩惆l(fā)布子版本了,下一個(gè)版本直接就叫HTTP3。

          二進(jìn)制協(xié)議

          HTTP1.1頭信息肯定是文本,數(shù)據(jù)體可以是文本也可以是二進(jìn)制,HTTP2則是一個(gè)徹底的二進(jìn)制協(xié)議,頭信息和數(shù)據(jù)體都是二進(jìn)制,并且統(tǒng)稱為幀,頭信息幀和數(shù)據(jù)幀。

          二進(jìn)制協(xié)議的一個(gè)好處是可以定義額外的幀,HTTP2定了一近十種幀,為將來(lái)的高級(jí)應(yīng)用打好基礎(chǔ),如果使用文本實(shí)現(xiàn)這種功能,解析數(shù)據(jù)將會(huì)變得非常麻煩,二進(jìn)制解析則方便很多。

          多工

          HTTP2復(fù)用TCP鏈接,在一個(gè)鏈接里,客戶端和瀏覽器都可以同時(shí)發(fā)送多個(gè)請(qǐng)求或回應(yīng),而且不用按照順序一一對(duì)應(yīng),這樣就避免了堵塞。

          在一個(gè)TCP鏈接里面,服務(wù)器同時(shí)收到了A請(qǐng)求和B請(qǐng)求,先回應(yīng)了A請(qǐng)求結(jié)果發(fā)現(xiàn)處理過(guò)程非常耗時(shí),先發(fā)送A請(qǐng)求已經(jīng)處理好的部分,再回應(yīng)B請(qǐng)求,完成后再發(fā)送A請(qǐng)求剩余的部分。

          這種雙向的,實(shí)時(shí)通信就叫做多工。

          效果地址: https:http2.akamai.com/demo

          數(shù)據(jù)流

          因?yàn)镠TTP2的數(shù)據(jù)包是不按順序發(fā)送的,同一個(gè)鏈接里面連續(xù)的數(shù)據(jù)包,可能屬于不同的回應(yīng),因此必須要對(duì)數(shù)據(jù)包做標(biāo)記,指出他屬于哪個(gè)回應(yīng)。

          HTTP2將每個(gè)請(qǐng)求或回應(yīng)的所有數(shù)據(jù)包,稱為一個(gè)數(shù)據(jù)流,每個(gè)數(shù)據(jù)流都有一個(gè)獨(dú)一無(wú)二的編號(hào),數(shù)據(jù)包發(fā)送的時(shí)候,都必須標(biāo)記數(shù)據(jù)流ID,用來(lái)區(qū)分它屬于哪個(gè)數(shù)據(jù)流,另外還規(guī)定,客戶端發(fā)出的數(shù)據(jù)流,ID一律為奇數(shù),服務(wù)器發(fā)布的,ID為偶數(shù)。

          數(shù)據(jù)流發(fā)送到一半的時(shí)候,客戶端和服務(wù)器都可以發(fā)送信號(hào)取消這個(gè)數(shù)據(jù)流。

          1.1版本取消數(shù)據(jù)的唯一方法就是關(guān)閉TCP鏈接,HTTP2可以取消某一次請(qǐng)求,同時(shí)保證TCP鏈接還開著,可以被其他請(qǐng)求使用。

          客戶端還可以指定數(shù)據(jù)流的優(yōu)先級(jí),優(yōu)先級(jí)越高,服務(wù)器就會(huì)越早回應(yīng)。

          壓縮頭信息

          HTTP協(xié)議不帶有狀態(tài),每次請(qǐng)求都必須附上所有信息,所以請(qǐng)求的很多字段都是重復(fù)的,比如Cookie和User Agent,一模一樣的內(nèi)容每次請(qǐng)求都必須附帶,這會(huì)浪費(fèi)很多帶寬也影響速度。

          HTTP2對(duì)這一點(diǎn)做了優(yōu)化,引入了頭信息壓縮機(jī)制,一方面頭信息使用gzip或compress壓縮后再發(fā)送。

          另一方面,客戶端和服務(wù)器同時(shí)維護(hù)一張頭信息表,所有字段都會(huì)存入這個(gè)表,生成一個(gè)索引號(hào),以后就不發(fā)送這個(gè)字段只發(fā)送索引號(hào)這樣就提高速度了。

          服務(wù)器推送

          HTTP2允許服務(wù)器未經(jīng)過(guò)請(qǐng)求主動(dòng)向客戶端發(fā)送資源,這就叫服務(wù)器推送。

          常見場(chǎng)景是客戶端請(qǐng)求一個(gè)網(wǎng)頁(yè),這個(gè)網(wǎng)頁(yè)包含很多靜態(tài)資源,正常情況下,客戶端必須收到網(wǎng)頁(yè)后解析html編碼,發(fā)現(xiàn)有靜態(tài)資源再發(fā)出靜態(tài)資源請(qǐng)求,其實(shí)服務(wù)器可以預(yù)期到客戶端請(qǐng)求網(wǎng)頁(yè)后很可能會(huì)再請(qǐng)求靜態(tài)資源,所有就主動(dòng)把這些靜態(tài)資源隨著網(wǎng)頁(yè)一起發(fā)給客戶端了。

          這個(gè)功能還是建議考慮自身的需要,會(huì)增加一部分成本開銷。

          壓縮傳輸數(shù)據(jù)資源

          通過(guò)壓縮傳輸數(shù)據(jù)資源提升性能體驗(yàn)。默認(rèn)HTTP進(jìn)行數(shù)據(jù)傳輸數(shù)據(jù)是沒有進(jìn)行壓縮的,原始數(shù)據(jù)多大傳輸?shù)臄?shù)據(jù)就多大。

          我們都知道文件壓縮之后數(shù)據(jù)體積減少是很客觀的。

          響應(yīng)數(shù)據(jù)壓縮

          HTTP響應(yīng)數(shù)據(jù)一般會(huì)根據(jù)數(shù)據(jù)的類型進(jìn)行壓縮方案的處理,比如文本最常用的方案就是Gzip的壓縮方案,目前大部分的網(wǎng)站都采用這種壓縮方式。

          gzip

          瀏覽器再請(qǐng)求服務(wù)器的時(shí)候會(huì)在請(qǐng)求頭中通過(guò)Accept-Encoding字段標(biāo)識(shí)可以接收gzip壓縮方案,服務(wù)器在收到請(qǐng)求后可以獲取到這種壓縮方案,將資源壓縮后返回給瀏覽器,并且在響應(yīng)頭中加入Content-Encoding字段,值為gzip。

          如果客戶端不添加Accept-Encoding頭,服務(wù)器返回了Content-Encoding,客戶端如果支持的話也會(huì)正常解析。

          Accept-Encoding基本是瀏覽器自動(dòng)添加的。

          const zlib = require('zlib');const fs = require('fs');const rs = fs.cerateReadStream('jquery.js');const ws = fs.cerateWriteStream('jquery.js.gz');const gz = zlib.createGzip();rs.pipe(gz).pipe(ws);ws.on('error', (err) => {   console.log('失敗');})ws.on('finish', () => {   console.log('完成')})

          正常工作中g(shù)zip一般可以在nginx服務(wù)器中開啟,不需要自己編寫。還是比較簡(jiǎn)單的。

          gzip一般是針對(duì)文本文件,比如js,css,對(duì)于圖片來(lái)說(shuō)一般是在開發(fā)階段壓縮。

          請(qǐng)求數(shù)據(jù)壓縮

          HTTP2以前請(qǐng)求頭是不可以壓縮的,HTTP2引入了頭信息壓縮機(jī)制,一方面頭信息使用gzip或express壓縮后再發(fā)送。

          另一方面,客戶端和服務(wù)器同時(shí)維護(hù)一張頭信息表,通過(guò)索引字段來(lái)傳輸,減少?gòu)d信息數(shù)據(jù)體積。

          實(shí)際工作中會(huì)存在請(qǐng)求正文非常大的場(chǎng)景,比如發(fā)表長(zhǎng)篇博客,上報(bào)用于調(diào)試網(wǎng)絡(luò)數(shù)據(jù)等等,這些數(shù)據(jù)如果能在本地壓縮后再提交就可以節(jié)省網(wǎng)絡(luò)流量,減少傳輸時(shí)間。

          DFLATE是一種使用Lempel-Ziv壓縮算法的哈夫曼編碼壓縮格式。

          ZLIB是一種使用DEFLATE的壓縮格式。

          GZIP是一種使用DEFLATE的壓縮格式。

          Content-Encoding中的deflate實(shí)際上是ZLIB。

          前端發(fā)送的時(shí)候可以進(jìn)行壓縮:

          const rawBody = 'content=test';const rawLen = rawBody.length;const bufBody = new Unit8Array(rawLen);for (let i = 0; i < rawLen; i++) {   bufBody[i] = rawBody.charCodeAt(i);}const format = 'gzip';let buf;switch (format) {   case gzip': buf = window.pako.gzip(bufBody); break;}const xhr = new XMLHttpRequest();xhr.open('POST', '/service/');xhr.setRequestHeader('Content-Encoding', format);xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded')xhr.send(buf);

          服務(wù)器端進(jìn)行解壓

          const http = require('http');const zlib = require('zlib');http.createServer((req, res) => {   let zlibStream;   const encoding = req.headers['content-encoding']   switch (encoding) {       case 'gzip' : zlibStream = zlib.createGunzip(); break;   }   res.writeHead(200, { 'Content-Type': 'text/plain' });   req.pipe(zlibStream).pipe(res);}).listen(3000)

          這種壓縮一半也只適用于文本,如果數(shù)據(jù)量太大壓縮過(guò)程也是比較耗時(shí)的。

          緩存

          緩存的原理是在客戶端首次請(qǐng)求后保存一份請(qǐng)求資源的響應(yīng)副本存儲(chǔ)在客戶端中,當(dāng)用戶再次發(fā)起相同的請(qǐng)求后,如果判斷緩存命中則攔截請(qǐng)求,將之前緩存的響應(yīng)副本返回給用戶,從而避免重新向服務(wù)器發(fā)起資源請(qǐng)求。

          緩存的技術(shù)種類有很多,比如代理緩存,瀏覽器緩存,網(wǎng)關(guān)緩存,負(fù)載均衡器及內(nèi)容分發(fā)網(wǎng)絡(luò)等,大致可以分為兩類,共享緩存和私有緩存。

          共享緩存指的是緩存內(nèi)容可以被多個(gè)用戶使用,如公司內(nèi)部架設(shè)的Web代理,私有緩存是只能單獨(dú)被用戶使用的緩存,如瀏覽器緩存。

          HTTP緩存是前端開發(fā)中最常接觸的緩存機(jī)制之一,他又可細(xì)分為強(qiáng)制緩存與協(xié)商緩存,二者最大的區(qū)別在于判斷緩存命中時(shí)瀏覽器是否需要向服務(wù)器進(jìn)行詢問(wèn)。

          強(qiáng)制緩存不會(huì)去詢問(wèn),協(xié)商緩存則仍舊需要詢問(wèn)服務(wù)器。

          強(qiáng)制緩存

          對(duì)于強(qiáng)制緩存而言,如果瀏覽器判斷所請(qǐng)求的目標(biāo)資源有效命中則可直接從強(qiáng)制緩存中返回請(qǐng)求的響應(yīng),無(wú)需與服務(wù)器進(jìn)行任何通信。

          也就是說(shuō)強(qiáng)制緩存是在客戶端進(jìn)行的,這樣速度就會(huì)很快。

          強(qiáng)制緩存相關(guān)的兩個(gè)字段是expires和cache-control。

          expires是在HTTP1.0協(xié)議中聲明的用來(lái)控制緩存失效日期的時(shí)間戳字段,他由服務(wù)器指定并通過(guò)響應(yīng)頭告訴瀏覽器,瀏覽器在收到帶有該字段的響應(yīng)體后進(jìn)行緩存。

          之后瀏覽器再發(fā)送相同的請(qǐng)求便會(huì)對(duì)比expires與本地當(dāng)前的時(shí)間戳,如果當(dāng)前請(qǐng)求的本地時(shí)間戳小于expires的值,則說(shuō)明緩存還未過(guò)期,可以直接使用。

          否則緩存過(guò)期重新向服務(wù)器發(fā)送請(qǐng)求獲取響應(yīng)體。

          res.writeHEAD(200, {   Expires: new Date('2021-6-18 12: 51: 00').toUTCString(),})

          expires存在一個(gè)很大的漏洞就是對(duì)本地時(shí)間戳過(guò)分依賴,如果客戶端本地的時(shí)間與服務(wù)器時(shí)間不同步,或者客戶端時(shí)間被修改,那么緩存過(guò)期的判斷可能就無(wú)法和預(yù)期相符。

          為了解決這個(gè)問(wèn)題,HTTP1.1新增了cache-control字段來(lái)對(duì)expires的功能進(jìn)行擴(kuò)展和完善。

          cache-control的值為maxage=xxx來(lái)控制響應(yīng)資源的有效期,xxx是一個(gè)以秒為單位的時(shí)間長(zhǎng)度,表示該資源在被請(qǐng)求到的一段時(shí)間內(nèi)有效,以此便可避免服務(wù)端和客戶端時(shí)間戳不同步而造成的問(wèn)題。

          res.writeHEAD(200, {   'Cache-Control': 'maxage=1000',})

          除了maxage還可以設(shè)置其他參數(shù),比如no-cache和no-store。

          no-cache表示強(qiáng)制進(jìn)行協(xié)商緩存,對(duì)于每次發(fā)起的請(qǐng)求不會(huì)再去判斷強(qiáng)制緩存是否過(guò)期,而是直接進(jìn)行協(xié)商緩存。協(xié)商緩存后面會(huì)說(shuō)到。

          no-store表示禁止使用任何緩存策略,客戶端的每次請(qǐng)求都直接從服務(wù)器獲取。也就是無(wú)緩存。

          no-cache和no-store是互斥的,不能同時(shí)設(shè)置。

          res.writeHEAD(200, {   'Cache-Control': 'no-cache',})

          還有private和public,他們也是cache-control的一組互斥屬性值,他們用來(lái)明確響應(yīng)資源是否可被代理服務(wù)器進(jìn)行緩存。

          publicb表示響應(yīng)資源即可被瀏覽器緩存又可以被代理服務(wù)器緩存。private限制了響應(yīng)資源只能被瀏覽器緩存。

          對(duì)于應(yīng)用程序中不會(huì)改變的文件比如圖片,js,css, 字體庫(kù)等通??梢允褂霉簿彺?。

          res.writeHEAD(200, {   'Cache-Control': 'public, max-age=31600',})

          除了max-age還有s-maxage,max-age表示服務(wù)器告知瀏覽器的響應(yīng)資源的過(guò)期時(shí)長(zhǎng)。一般使用它就足夠了了。

          但如果是大型項(xiàng)目架構(gòu)通常會(huì)涉及代理服務(wù)器緩存,這就需要考慮緩存在代理服務(wù)器上的有效性問(wèn)題,這便是s-maxage存在的意義。

          他表示緩存在代理服務(wù)器上的過(guò)期時(shí)長(zhǎng),需要配合public來(lái)使用。

          cache-control能作為expires的完全替代方案,目前expires只作為兼容使用。

          協(xié)商緩存

          協(xié)商緩存就是在使用本地緩存之前,需要向服務(wù)器發(fā)起一次GET請(qǐng)求,與之協(xié)商當(dāng)前瀏覽器保存的本地緩存是否已經(jīng)過(guò)期。

          協(xié)商緩存主要解決的問(wèn)題就是在強(qiáng)制緩存下資源不更新的問(wèn)題。

          客戶端在獲取到本地緩存后需要向服務(wù)器發(fā)送一次GET請(qǐng)求,這個(gè)請(qǐng)求的請(qǐng)求頭中包含if-modified-since字段,值是響應(yīng)頭中的last-modified字段,也就是這個(gè)資源的最后修改時(shí)間。

          也就是說(shuō)客戶端請(qǐng)求資源的時(shí)候服務(wù)器會(huì)返回響應(yīng)內(nèi)容及內(nèi)容的修改時(shí)間,修改時(shí)間存在last-modified字段中。

          客戶端在請(qǐng)求的時(shí)候如果客戶端存儲(chǔ)了last-modified就將它的值放在if-modified-since字段中發(fā)送到服務(wù)器。

          服務(wù)器接收到請(qǐng)求后通過(guò)比對(duì)前端傳過(guò)來(lái)的時(shí)間和資源的修改時(shí)間,如果二者相同則說(shuō)明緩存未過(guò)期,就告訴瀏覽器直接使用緩存中的文件,如果過(guò)期了就返回對(duì)應(yīng)文件并且將新的修改日期重新返回。

          客戶端繼續(xù)緩存新的修改時(shí)間。

          const http = require('http');const fs = require('fs');const url = require(''url');http.creatServer((req, res) => {   const { pathname } = url.parse(req.url);   // 獲取文件日期   fs.stat(`www/${pathname}`, (err, stat) => {     if (err) {       res.writeHeader(404);       res.write('Not Found');       res.end();     } else {       if (req.headers['if-modified-since']) {         const oDate = new Date(req.headers['if-modified-since']);         const time_client = Math.floor(oDate.getTime() / 1000);         const time_server = Math.floor(stat.mtime.getTime() / 1000);         if (time_server > time_client) { // 服務(wù)器的文件時(shí)間大于客戶端           sendFileToClient();         } else {           res.writeHeader(304);           res.write('Not Modified');           res.end();         }       } else {         sendFileToClient();       }       function sendFileToClient() {         let rs = fs.createReadStream(`www/${pathname}`);         res.setHeader('Last-Modifyed', state.mtime.toGMTString());         rs.pipe(res);         rs.on('error', err => {           res.writeHeader(404);           res.write('Not Found');           res.end();         })       }     }   })}).listen(8080);

          上面的這種緩存方式存在兩個(gè)問(wèn)題,首先他只是根據(jù)資源最后的修改時(shí)間戳進(jìn)行判斷,如果文件沒有變更只是保存了一下修改時(shí)間也會(huì)變化。

          其次標(biāo)識(shí)時(shí)間是秒,如果修改特別快在毫秒內(nèi)完成(程序修改會(huì)有這樣的速度),那么就無(wú)法識(shí)別緩存過(guò)期。

          主要原因就是服務(wù)器無(wú)法僅依據(jù)資源修改的時(shí)間戳識(shí)別出真正的更新,進(jìn)而導(dǎo)致緩存不準(zhǔn)確。

          為了解決這個(gè)問(wèn)題從HTTP1.1規(guī)范開始新增了一個(gè)ETag的頭信息, 實(shí)體標(biāo)簽。

          其內(nèi)容主要是服務(wù)器為不同資源進(jìn)行哈希運(yùn)算生成的一個(gè)字符串,該字符串類似于文件指紋,只要文件內(nèi)容編碼存在差異,對(duì)應(yīng)的ETag標(biāo)簽值就會(huì)不同,因此可以使用ETag對(duì)文件資源進(jìn)行更精準(zhǔn)的變化感知。

          const etag = require('etag')res.setHeader('etag', etag(data));

          基于ETag發(fā)送的請(qǐng)求會(huì)在請(qǐng)求頭中以If-None-Match傳遞給服務(wù)器。

          在協(xié)商緩存中ETag并非last-modified的替代方案而是一種補(bǔ)充方案,因?yàn)樗泊嬖谝恍﹩?wèn)題。

          首先,服務(wù)器對(duì)生成文件資源的ETag需要付出額外的計(jì)算開銷,如果資源體積較大,數(shù)量較多且修改較頻繁,那么生成ETag的過(guò)程會(huì)影響服務(wù)器的性能。

          其次,ETag的值分為強(qiáng)驗(yàn)證和弱驗(yàn)證,強(qiáng)驗(yàn)證根據(jù)資源內(nèi)容進(jìn)行生成,能夠保證每個(gè)字節(jié)都相同。

          弱驗(yàn)證則根據(jù)資源的部分屬性值來(lái)生成,生成速度快但無(wú)法確保每次字節(jié)都相同。并且在服務(wù)器集群場(chǎng)景下,也會(huì)因?yàn)椴粔驕?zhǔn)確而降低協(xié)商緩存的有效性和校驗(yàn)的成功性。

          恰當(dāng)?shù)姆绞绞歉鶕?jù)具體的資源使用場(chǎng)景選擇恰當(dāng)?shù)木彺嫘r?yàn)方式。

          緩存策略

          HTTP的緩存技術(shù)主要是為了提升網(wǎng)站的性能,如果不考慮客戶端緩存容量和服務(wù)器計(jì)算能力的理想情況,我們當(dāng)然希望客戶端瀏覽器上的緩存觸發(fā)率盡可能高,留存時(shí)間盡可能長(zhǎng),同時(shí)還要ETag實(shí)現(xiàn)當(dāng)資源更新時(shí)進(jìn)行高效的重新驗(yàn)證。

          但實(shí)際情況往往是容量和計(jì)算能力都有限,因此就需要指定合適的緩存策略,利用有效的資源達(dá)到最優(yōu)的性能效果。

          明確需求邊界,力求在邊界內(nèi)做到最好。

          在使用緩存技術(shù)優(yōu)化性能的過(guò)程中,有一個(gè)問(wèn)題是不可逾越的,我們既希望緩存能在客戶端盡可能長(zhǎng)久的保存,又希望他能在資源發(fā)生修改時(shí)進(jìn)行及時(shí)更新。這是兩個(gè)互斥的需求。

          如何兼顧二者呢?

          可以將網(wǎng)站所需要的資源按照不同的類型去拆解,為不同類型的資源制定相應(yīng)的緩存策略。

          首先html文件是包含其他文件的主文件,為保證當(dāng)其發(fā)生改變能及時(shí)更新,應(yīng)該將其設(shè)置為協(xié)商緩存。

          cache-control: no-cache

          圖片文件的修改基本都是替換,同時(shí)考慮圖片文件的數(shù)量及大小可能對(duì)客戶端緩存空間造成不小的開銷,所以可以采用強(qiáng)制緩存且過(guò)期時(shí)間不宜過(guò)長(zhǎng)。

          cache-control: max-age=86400

          css樣式表屬于文本文件,可能存在的內(nèi)容不定期修改,還想使用強(qiáng)制緩存來(lái)提高重用效率,故可以考慮在樣式表文件的命名中增加指紋或版本號(hào)(一般為hash值),這樣發(fā)生修改后不同的文件便會(huì)有不同的文件指紋,也就是請(qǐng)求的url不同。

          所以css的緩存時(shí)間可以設(shè)置長(zhǎng)一些, 一年。

          cache-control: max-age=31536000

          js腳本文件可以類似樣式表的設(shè)置,采用指紋和較長(zhǎng)的過(guò)期時(shí)間,如果js中包含了用戶的私人信息而不想讓中間代理緩存,可添加private屬性。

          cache-control: private, max-age=31536000

          緩存策略就是為不同的資源進(jìn)行組合使用強(qiáng)制緩存,協(xié)商緩存及文件指紋或版本號(hào),這樣可以做到一舉多得,及時(shí)修改更新,較長(zhǎng)緩存過(guò)期時(shí)間及控制所能進(jìn)行緩存的位置。

          緩存設(shè)置需要注意不存在適用于所有場(chǎng)景下的最佳緩存策略,凡是恰當(dāng)?shù)木彺娌呗远夹枰鶕?jù)具體的場(chǎng)景考慮制定。

          緩存決策要考慮下面幾種情況。

          1)、拆分源碼,分包加載

          對(duì)于大型項(xiàng)目來(lái)說(shuō),代碼里是非常龐大的,如果發(fā)生修改的部分集中在幾個(gè)重要的模塊中,那么進(jìn)行全量的代碼更新顯然比較冗雜,因此可以考慮在代碼構(gòu)建過(guò)程中按照模塊拆分將其打包成多個(gè)單獨(dú)的文件。

          這樣在每次修改后更新提取時(shí),僅需拉取發(fā)生改變的模塊代碼包,從而大大降低了需要下載的內(nèi)容大小。

          2)、預(yù)估資源的緩存時(shí)效

          根據(jù)不同資源的不同需求特點(diǎn)來(lái)規(guī)劃響應(yīng)的緩存更新失效,為強(qiáng)制緩存指定合適的max-age,為協(xié)商緩存提供驗(yàn)證更新的ETag實(shí)體標(biāo)簽。

          3)、控制中間代理的緩存

          凡是涉及用戶隱私信息的盡量避免中間代理的緩存,如果對(duì)所有用戶響應(yīng)相同的資源,則可以考慮讓中間代理也進(jìn)行緩存。

          4)、避免網(wǎng)址的冗余

          緩存是根據(jù)請(qǐng)求資源的URL進(jìn)行的,不同的資源會(huì)有不同的URL,所以盡量不要將相同的資源設(shè)置為不同的URL。這會(huì)導(dǎo)致緩存失效。

          5)、規(guī)劃緩存的層次結(jié)構(gòu)

          不僅請(qǐng)求的資源類型,文件資源的層次結(jié)構(gòu)也會(huì)對(duì)制定緩存策略有一定的影響。

          CDN緩存

          CND全程是Content Delivery Network,內(nèi)容分發(fā)網(wǎng)絡(luò),他是構(gòu)建在現(xiàn)有網(wǎng)絡(luò)基礎(chǔ)上的虛擬智能網(wǎng)絡(luò),依靠部署在各地的邊緣服務(wù)器,通過(guò)中心平臺(tái)的負(fù)載均衡,調(diào)度及內(nèi)容分發(fā)等功能模塊,使用戶在請(qǐng)求所需訪問(wèn)的內(nèi)容時(shí)能夠就近獲取,以此來(lái)降低網(wǎng)絡(luò)阻塞,提高資源對(duì)用戶的響應(yīng)速度。

          如果沒有CDN,假設(shè)我們的服務(wù)器在北京,那么海南的用戶訪問(wèn)我們的網(wǎng)站的時(shí)候需要不遠(yuǎn)萬(wàn)里鏈接北京的服務(wù)器獲取資源,這樣的速度是比較慢的。

          CDN的工作原理就是就近響應(yīng),如果我們將資源放置在CDN上,當(dāng)海南的用戶訪問(wèn)網(wǎng)站時(shí),資源請(qǐng)求首先進(jìn)行DNS解析,這個(gè)時(shí)候DNS會(huì)詢問(wèn)CDN服務(wù)器有沒有就近的服務(wù)器,如果有就鏈接就近服務(wù)的IP地址獲取資源。

          由于DNS服務(wù)器將CDN的域名解析權(quán)交給了CNAME指向的專用DNS服務(wù)器,所以用戶輸入域名的解析最終是在CDN專用的DNS服務(wù)器上完成的。

          解析出的IP地址并非確定的CDN緩存服務(wù)器地址,而是CDN負(fù)載均衡器的地址。

          瀏覽器會(huì)重新向該負(fù)載均衡器發(fā)起請(qǐng)求,經(jīng)過(guò)對(duì)用戶IP地址的距離,所請(qǐng)求資源內(nèi)容的位置及各個(gè)服務(wù)器狀態(tài)的綜合計(jì)算,返回給用戶確定的緩存服務(wù)器IP地址。

          如果這個(gè)過(guò)程發(fā)生所需資源未找到的情況,那么此時(shí)便會(huì)依次向上一級(jí)緩存服務(wù)器繼續(xù)請(qǐng)求查詢,直至追溯到網(wǎng)站所在的跟服務(wù)器并將資源拉取到本地進(jìn)行緩存。

          雖然這個(gè)過(guò)程看起來(lái)稍微復(fù)雜一些,但是用戶是無(wú)感知的,并且能帶來(lái)比較明顯的資源加載速度的提升,因此對(duì)目前所有一線互聯(lián)網(wǎng)產(chǎn)品來(lái)說(shuō),使用CDN已經(jīng)不再是一條建議而是規(guī)定。

          CDN主要針對(duì)的是靜態(tài)資源并非適用網(wǎng)站所有的資源類型。所謂靜態(tài)資源就是不需要業(yè)務(wù)服務(wù)器參與計(jì)算的資源,比如第三方的庫(kù),js腳本文件,css樣式文件,圖片等。

          如果是動(dòng)態(tài)資源比如依賴服務(wù)端渲染的html就不適合放在CDN上。

          CDN網(wǎng)絡(luò)的核心功能包括兩點(diǎn),緩存與回源,緩存指的是將所需的靜態(tài)資源文件復(fù)制一份到CDN緩存服務(wù)器上,回源指的是如果未在CDN緩存服務(wù)器上查找到目標(biāo)資源,或者CDN緩存服務(wù)器上的資源已經(jīng)過(guò)期,則重新追溯到網(wǎng)站根服務(wù)器獲取相關(guān)資源的過(guò)程。

          CDN的優(yōu)化有很多方面,比如CDN自身的性能優(yōu)化,靜態(tài)資源邊緣化,域名合并優(yōu)化和多級(jí)緩存架構(gòu)優(yōu)化。這些可能需要前后端一起配合完成。

          一般情況CDN會(huì)和主站域名區(qū)分,這樣的好處是避免靜態(tài)資源請(qǐng)求攜帶不必要的cookie信息,還有就是考慮瀏覽器對(duì)同一域名下并發(fā)請(qǐng)求的限制。

          cookie的訪問(wèn)遵循同源策略,同一域名下的所有請(qǐng)求都會(huì)攜帶全部cookie信息,雖然cookie存儲(chǔ)空間并不大,但是如果所有資源都放在主站域名下所有的請(qǐng)求全部攜帶數(shù)據(jù)量也是很大的。

          所以將CDN服務(wù)器的域名和主站域名進(jìn)行區(qū)分是非常有價(jià)值的。

          其次因?yàn)闉g覽器對(duì)于同域名下的并發(fā)請(qǐng)求存在限制,通常Chrome的并發(fā)限制是6??梢酝ㄟ^(guò)增加類似域名的方式來(lái)提高并發(fā)請(qǐng)求數(shù)。

          當(dāng)然這種方式對(duì)緩存命中是不友好的,如果并發(fā)請(qǐng)求了相同的資源使用了不同的域名,那么之前的緩存就失去了意義。


          學(xué)習(xí)更多技能

          請(qǐng)點(diǎn)擊下方公眾號(hào)


          瀏覽 52
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  奶水饱胀一区二区三区 | 成人特级毛片全部免费播放 | 日本三区视频 | 中文字幕av无码 成人激情视频网 | 蜜芽av成人 |