1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

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

        共 11989字,需瀏覽 24分鐘

         ·

        2022-01-03 11:46

        點(diǎn)擊上方?前端Q,關(guān)注公眾號(hào)

        回復(fù)加群,加入前端Q技術(shù)交流群


        DNS解析

        當(dāng)瀏覽器從第三方服務(wù)器請(qǐng)求資源時(shí),必須先將該跨域域名解析為IP地址,然后瀏覽器才能發(fā)出請(qǐng)求,此過程稱為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%的圖片加載速度提成。
        一般來說前端與華中與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è)面中通過link標(biāo)簽來實(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)過DNS解析獲取到IP之后就要進(jìn)行TCP的鏈接進(jìn)行數(shù)據(jù)傳輸。

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

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

        為了解決這個(gè)問題,有些瀏覽器在請(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ī)范的做法是客戶端在最后一個(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)操作來說,意味著,服務(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ì)頭阻塞。

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

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

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

        HTTP2協(xié)議

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

        HTTP2主要用于解決HTTP1.1效率不高的問題,他不叫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定了一近十種幀,為將來的高級(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)處理過程非常耗時(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ú)一無二的編號(hào),數(shù)據(jù)包發(fā)送的時(shí)候,都必須標(biāo)記數(shù)據(jù)流ID,用來區(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)過請(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ù)資源

        通過壓縮傳輸數(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)求頭中通過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ì)于圖片來說一般是在開發(fā)階段壓縮。

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

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

        另一方面,客戶端和服務(wù)器同時(shí)維護(hù)一張頭信息表,通過索引字段來傳輸,減少?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ù)量太大壓縮過程也是比較耗時(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)行詢問。

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

        強(qiáng)制緩存

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

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

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

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

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

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

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

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

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

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

        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)制緩存是否過期,而是直接進(jìn)行協(xié)商緩存。協(xié)商緩存后面會(huì)說到。

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

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

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

        還有private和public,他們也是cache-control的一組互斥屬性值,他們用來明確響應(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)資源的過期時(shí)長(zhǎng)。一般使用它就足夠了了。

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

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

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

        協(xié)商緩存

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

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

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

        也就是說客戶端請(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)求后通過比對(duì)前端傳過來的時(shí)間和資源的修改時(shí)間,如果二者相同則說明緩存未過期,就告訴瀏覽器直接使用緩存中的文件,如果過期了就返回對(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è)問題,首先他只是根據(jù)資源最后的修改時(shí)間戳進(jìn)行判斷,如果文件沒有變更只是保存了一下修改時(shí)間也會(huì)變化。

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

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

        為了解決這個(gè)問題從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ù)器對(duì)生成文件資源的ETag需要付出額外的計(jì)算開銷,如果資源體積較大,數(shù)量較多且修改較頻繁,那么生成ETag的過程會(huì)影響服務(wù)器的性能。

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

        弱驗(yàn)證則根據(jù)資源的部分屬性值來生成,生成速度快但無法確保每次字節(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)化性能的過程中,有一個(gè)問題是不可逾越的,我們既希望緩存能在客戶端盡可能長(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)制緩存且過期時(shí)間不宜過長(zhǎng)。

        cache-control: max-age=86400

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

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

        cache-control: max-age=31536000

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

        cache-control: private, max-age=31536000

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

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

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

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

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

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

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

        根據(jù)不同資源的不同需求特點(diǎn)來規(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ù)器,通過中心平臺(tái)的負(fù)載均衡,調(diào)度及內(nèi)容分發(fā)等功能模塊,使用戶在請(qǐng)求所需訪問的內(nèi)容時(shí)能夠就近獲取,以此來降低網(wǎng)絡(luò)阻塞,提高資源對(duì)用戶的響應(yīng)速度。

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

        CDN的工作原理就是就近響應(yīng),如果我們將資源放置在CDN上,當(dāng)海南的用戶訪問網(wǎng)站時(shí),資源請(qǐng)求首先進(jìn)行DNS解析,這個(gè)時(shí)候DNS會(huì)詢問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)過對(duì)用戶IP地址的距離,所請(qǐng)求資源內(nèi)容的位置及各個(gè)服務(wù)器狀態(tài)的綜合計(jì)算,返回給用戶確定的緩存服務(wù)器IP地址。

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

        雖然這個(gè)過程看起來稍微復(fù)雜一些,但是用戶是無感知的,并且能帶來比較明顯的資源加載速度的提升,因此對(duì)目前所有一線互聯(lián)網(wǎng)產(chǎn)品來說,使用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)過期,則重新追溯到網(wǎng)站根服務(wù)器獲取相關(guān)資源的過程。

        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的訪問遵循同源策略,同一域名下的所有請(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。可以通過增加類似域名的方式來提高并發(fā)請(qǐng)求數(shù)。

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

        • 本文作者:隱冬

        • 本文鏈接:https://zhiqianduan.com/javascript/資源請(qǐng)求速度優(yōu)化.html


        往期推薦


        2021 TWeb 騰訊前端技術(shù)大會(huì)精彩回顧(附PPT)
        面試題:說說事件循環(huán)機(jī)制(滿分答案來了)
        專心工作只想搞錢的前端女程序員的2020

        最后


        • 歡迎加我微信,拉你進(jìn)技術(shù)群,長(zhǎng)期交流學(xué)習(xí)...

        • 歡迎關(guān)注「前端Q」,認(rèn)真學(xué)前端,做個(gè)專業(yè)的技術(shù)人...

        點(diǎn)個(gè)在看支持我吧
        瀏覽 59
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

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

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            无码大片最新资源 | 成人片毛片A片免费网站老女人 | 九热精品视频 | 让人看了下面流水的视频 | 五月天成人在线 | 中文字幕不卡视频 | 黄色免费在线观看视频 | 亚洲国产操逼 | 丁香花五月婷婷 | 无码一 |