利用Resource Timing監控資源載入速度

大雄沒了哆啦A夢發表於2019-03-04

Resource Timing API

Resource Timing API提供了讓使用者檢視一個資源從輸入url到下載下來經歷的各個過程所消耗的時間,藉此可以來衡量網站的效能。
我們可以通過Resource Timing Api監控哪個階段消耗時間比較長,然後針對該階段進行優化,比如發現一個請求的過程中伺服器返回時間過長,則需要對伺服器進行優化了。

資源請求的生命週期

瀏覽器從請求資源到資源下載下來,會經過多個階段,一個請求生命週期的主要階段包括:

  1. 重定向;如果伺服器返回302的狀態時,則會發生重定向,頁面會重定向到302響應的location屬性指定的地址去(response的Location屬性指定,例如jd.com會被跳轉到http://www.jd.com)。
  2. 讀取瀏覽器快取;如果資源的快取時間還未過期(伺服器設定的expires和cache-control還未過期),則會直接從瀏覽器快取中讀取。後續的dns查詢、tcp握手、請求的傳送都不會進行了。
  3. DNS解析;傳送http請求需要通過建立TCP連線,建立TCP連線需要直到目標機器(伺服器)的ip地址,所以需要進行dns的解析出ip,然後通過socket建立tcp連線。
  4. TCP握手;http建立在TCP上,需要先完成TCP三次握手,通過第2步的ip和已知的埠號,瀏覽器開啟一個程式建立TCP連線。在此過程中,如果使用https,則會在TCP連線的時候進行SSL握手,建立SSL連線。
  5. 請求request;瀏覽器根據url,組裝http請求,併傳送。
  6. 接收response;伺服器返回http響應的時候,瀏覽器接收到http response,然後就下載資源了。
利用Resource Timing監控資源載入速度

圖1

檢視各個階段資源載入時間

devtools

我們可以藉助chrome的devtools檢視,在network選項卡下,點選某個請求,選中Timing就可以看到devtools給我們提供的關於timing resource各個階段的詳細時間了。

圖1

圖2

Queueing: 請求被阻塞,放入等待佇列中等待。
一般以下幾個原因會被阻塞:

1、如果這個資源載入優先順序比較低,比如圖片(html/css/js的優先順序比圖片高),那麼圖片請求就會被渲染引擎阻塞,等待優先順序高的資源載入完成才從佇列中取出,等待傳送。
2、我們知道瀏覽器對同一域名下對TCP連線的併發數有限制(防止資源被消耗殆盡),chrome這邊是6,那麼如果同一域名下請求多於6的話,後面的請求就會被阻塞。
3、等待釋放TCP連線

Stalled: 等待傳送所用的時間,原因同上。

DNS Lookup:DNS查詢時間

Initail connection:建立TCP連線所用的時間

SSL:建立SSL連線所用的時間

Request sent:發出請求的時間,通常不到一毫秒

TTFB:第一位元組時間,即請求發出到接受到伺服器第一個位元組的時間,如果這個時間太長,一般有兩個原因:

1、網路太差
2、伺服器響應太慢

一般建議不要這個階段的時間不要超過200毫秒。

Content Download:資源下載時間,如果被阻塞,則這個時間會很長,或者資源過大也會導致下載時間過長。例如js執行時間過長,那麼圖片載入下來的時間就會被拉到很長。

Resource Timing Api

現代瀏覽器提供了Api讓使用者可以檢視圖1各個階段所消耗的時間,以便使用者用Api獲取資源載入過程中的具體情況,排查耗時的階段,然後進行對應的優化。

通過window.performance.getEntriesByType(`resource`)獲取所有的PerformanceResourceTiming:

if(`performance` in window) {
    // 獲取的是所有的PerformanceResourceTiming
    var resources = window.performance.getEntriesByType(`resource`)
    // 遍歷各個資源載入的時間
    resources.map((resource) => {
        // 這裡以圖片為例,判斷圖片載入的時間
        if(resource.initiatorType === `img`) {
            // duration取的是整個過程中經歷的時間,即圖1的startTime到responseEnd直接的時間,即等於resource.responseEnd - resource.startTime
            if(resource.duration > 5000) {
                // 圖片載入超過了5秒了,上報伺服器,提示圖片載入過長
                reportToServer()
            }
        }
    })
}
複製程式碼

注意,上面的程式碼需要在onload事件上面執行(onload會在圖片載入完畢以後呼叫)。

PerformanceResourceTimeing包含以下的屬性:

  • [x] initiatorType:資源的型別,有img、script、link

下面的屬性是以毫秒為單位,對應圖1

  • [x] redirectStart
  • [x] redirectEnd
  • [x] fetchStart
  • [x] domainLookupStart
  • [x] domainLookupEnd
  • [x] connectStart
  • [x] connectEnd
  • [x] secureConnectionStart
  • [x] requestStart
  • [x] responseStart
  • [x] responseEnd

所以我們得出這樣的一個計算:
檢視DNS查詢時間: domainLookupEnd – domainLookupStart
檢視TCP三次握手時間: connectEnd – connectStart
request請求時間: responseEnd – responseStart
整個過程時間: responseEnd – startTime 或者 duration

資源載入

瀏覽器渲染過程(關鍵渲染路徑)

1、根據HTML構建DOM樹
2、根據css構建CSSOM規則樹
3、根據DOM樹和CSSOM樹渲染合併渲染樹
4、根據渲染樹計算元素的位置和大小
5、將元素顯示在螢幕上

在此過程中,解析到script會阻塞dom的解析和渲染(但其他資源的下載還是並行下載的)。執行完js後又會重新構建DOM樹和CSSOM樹,再構建渲染樹,如此反覆。

利用Resource Timing監控資源載入速度

css載入

css被視為阻塞渲染的資源,不會阻塞dom的解析,但會阻塞dom的渲染。瀏覽器為了避免dom渲染完後,css樣載入完再去渲染一次,就會阻塞dom的渲染。不然會有兩次渲染,從效能上和使用者體驗上都不好。
如果把載入css放在body後,瀏覽器為了防止上面的狀況的發生,會等待css載入完才去渲染dom,這樣就會白屏了,所以建議css放在head裡去載入。
所以,css放在head載入是一個很好的優化。

js載入

js即會阻塞dom的解析,也會阻塞dom的渲染。js是單執行緒的,在執行js的時候,瀏覽器會將控制權交給js,dom解析和渲染都會阻塞。
如果把js載入放在頭部,那麼dom的解析和渲染就停止了,這樣會導致兩個問題:
1、一方面,如果這時候js要獲取dom或者操作dom都會報錯。
2、另一方面,使用者等待頁面展示出來的時間也會加長,所以建議js載入放在底部。

備註:如果都放在head中,css在前,js在後,則瀏覽器為了讓js獲取到的樣式是準確的,則會在css載入完前阻塞js的執行。如果把js寫在前,css在後,瀏覽器會預載入css,這樣的效果會比css在前阻塞後面的js執行好。

我們可以將script的載入設為非同步載入,即defer/async,這樣它就不會阻塞dom的解析和渲染。

結合以上,我們建議把css的引入放在head,把js的引入放在body之後,如果js可以非同步載入,我們可以使用非同步載入的方式。多說一句,現代瀏覽器有freload和frefetch的預載入,也可以提高頁面載入速度,有興趣的可以去查閱下資料。

圖片載入

圖片的載入不會阻塞dom的渲染,試想下,如果圖片載入會阻塞dom的渲染的話,那麼在多圖的網站(現在很常見),等待圖片全部載入出來才去渲染dom,那麼使用者體驗將會是特別的差,所以圖片的載入順序優先順序是比較低的。

參考連結:
developers.google.com/web/tools/c…
developers.google.com/web/fundame…
juejin.im/post/59c606…
juejin.im/entry/59e1d…

相關文章