[譯] HTTP/2 Server Push 詳解

原文:https://www.smashingmagazine....php

做者:Jeremy Wagnercss

譯者按:網絡優化一直是譯者長期研究的方向,HTTP/2 的理論學習也已作了很多,隨着這項標準的推動,愈來愈多特性被你們開始使用。做爲 HTTP/2 最激動人心的特性,Server Push 在性能提高的效果被寄予了很高指望,卻因其對傳統 B/S 架構的開發模式影響較大未能普遍實踐。如何更好地使用這項能力,讓咱們跟着做者深刻探索~html

========================譯文分割線===========================前端

在過去的一年時間,HTTP/2 的出現爲關注性能的開發者帶來了顯著的變化。HTTP/2 已經再也不是咱們期待中的特性,而是伴着 Server Push(服務端推送)能力已然到來。git

除了解決常見的 HTTP/1 性能問題(好比,首部阻塞和未壓縮的報頭),HTTP/2 還提供了 Server Push 能力!服務端推送容許咱們向用戶發送一些尚未被訪問的資源。這是一種得到 HTTP/1 優化實踐(例如內聯)所帶來性能提高的優雅方式,同時也避免了原先實踐的一些缺點。github

本文中,你將瞭解什麼是 Server Push,它的工做原理與解決了哪些問題。同時你也將學習如何使用它,判斷它是否正常運做,以及它對性能的影響。讓咱們開始吧!chrome

Server Push 爲什麼物

訪問網站始終遵循着請求——響應模式。用戶將請求發送到遠程服務器,在一些延遲後,服務器會響應被請求的內容。apache

對網絡服務器的初始請求一般是一個 HTML 文檔。在這種狀況下,服務器會用所請求的 HTML 資源進行響應。接着瀏覽器開始對 HTML 進行解析,過程當中識別其餘資源的引用,例如樣式表、腳本和圖片。緊接着,瀏覽器對這些資源分別發起獨立的請求,等待服務器返回。npm

v2-9492a47e8b08e4bd8e59a00b66537b91_b.png

典型的服務器通訊(大圖)後端

這一機制的問題在於,它迫使用戶等待這樣一個過程:直到一個 HTML 文檔下載完畢後,瀏覽器才能發現和獲取頁面的關鍵資源。從而延緩了頁面渲染,拉長了頁面加載時間。

有了 Server Push,就有了解決上述問題的方案。Server Push 能讓服務器在用戶沒有明確詢問下,搶先地「推送」一些網站資源給客戶端。只要正確地使用,咱們能夠根據用戶正在訪問的頁面,給用戶發送一些即將被使用的資源。

好比說你有一個網站,全部的頁面都會在一個名爲 styles.css 的外部樣式表中,定義各類樣式。當用戶向務器請求 index.html 時,咱們能夠在發送 index.html 的同時,向用戶推送 styles.css。

v2-ad3ccde6faa35486b432b88e10b000a7_b.png

使用HTTP/2 Server Push的Web服務器通訊(大圖

相比等待服務器發送 index.html,而後等待瀏覽器請求並接收 styles.css,用戶如今只需等待1次服務器響應,就可在初次請求同時使用 index.html 和 styles.css。

能夠想象,這能夠下降頁面的渲染時間。它還解決了一些其餘問題,特別是在前端開發工做流方面。

Server Push 解決了什麼問題?

Server Push 解決了減小關鍵內容的網絡迴路耗時問題,但這並非惟一的做用。Server Push 更像是 HTTP/1 特定優化反模式的替代方案,例如將 CSS 和 JavaScript 內聯在 HTML,以及使用 data URI 方案將二進制數據嵌入到 CSS 和 HTML 中。

這些技術在 HTTP/1 優化工做流中很是受用,是由於這樣減小了咱們所說的頁面「感知渲染時間」,也就是說在頁面總體加載時間可能不會減小的同時,對用戶而言網頁的加載速度卻顯得更快。這確實是說得通的,若是你將 CSS 內嵌到 HTML 的<style>標籤中,瀏覽器就能夠無需等待外部資源的獲取,而當即應用 HTML 中的樣式。這種概念一樣適用於內聯腳本,以及使用 data URI 方式內聯二進制數據。

v2-12d9ad5a54670468907dfec0830b035b_b.png

內聯內容的服務器通訊(大圖

這看起來是個不錯的方案,對吧?在 HTTP/1 的時代確實如此,由於也沒有別的選擇。而這麼作實際上也留下了惡果,即內聯的內容不能有效地被緩存。若樣式、腳本資源之外鏈及模塊形式引用,會更高效地進行緩存。當用戶訪問後續頁面須要這些資源時,能夠直接從緩存中獲取,從而省去了額外的資源請求。

v2-cead5e85c8dfb6443b6f518212112fc1_b.png

優化緩存行爲(大圖

而當咱們對內容進行內聯時,它們是沒有獨立的緩存上下文的,僅存在於所內聯文檔的上下文中。舉個在 HTML 中內聯 CSS 的例子,若是 HTML 的緩存策略,是每次訪問都向服務器拉取最新的內容,那麼內聯的CSS老是沒法緩存其內容。即便把 HTML 進行緩存,但在後續訪問的頁面內,內聯相同的 CSS 內容也是須要重複下載的。這仍是比較寬鬆的緩存策略,實際狀況中 HTML 僅有較短的緩存週期。內聯是咱們在 HTTP/1 優化方案中所作的權衡,它確實在用戶第一次訪問時很是有效,而每每第一印象是很是重要的。

這就是 Server Push 能解決的問題。當推送資源時,咱們能得到與內聯相同的性能提高,同時保持資源的外鍊形式,從而有獨立的緩存策略。這裏有個須要注意的問題,咱們稍後再深刻探討。

我已經談了不少爲何你該考慮使用 Server Push 的緣由,也澄明瞭它能爲用戶和開發者所解決的問題。接下來讓我告訴你如何去使用它。

如何使用 Server Push

使用Server Push,一般會如下面的方式使用 Link 這個HTTP首部。

Link: </css/styles.css>; rel=preload; as=style

注意我說的是一般,上面看到的實際是預加載資源示意(resource hint)的實踐。這是個區別於 Server Push 的獨立優化方案,但大多數(並不是所有)HTTP/2的實現都將 preload 放進了 Link 首部。若是服務器或客戶端選擇不接受推送的資源,客戶端仍能夠根據指示提前獲取資源。

首部中 as=style 部分是必選的,它能告知瀏覽器推送資源的類型。在這個例子中,咱們使用了 style 來指明推送的資源是一個樣式表,你還能夠設置其餘的內容類型。值得注意的是若是省略了 as 的值,會致使瀏覽器對推送資源下載兩次,因此千萬別忘了它。

如今知道推送資源的方法了,但具體要怎樣設置 Link 首部呢?咱們有兩種方式:

  • Web服務器配置(例如,Apache httpd.conf 或.htaccess);

  • 後端語言功能(例如PHP的 header 方法)。

使用服務器配置設置 Link 首部

下面是一個 Apache 配置(經過httpd.conf或.htaccess)的例子,做用是在請求 HTML 時推送樣式資源。

<FilesMatch "\.html$">
    Header set Link "</css/styles.css>; rel=preload; as=style"
<FilesMatch>

這裏咱們使用了 FilesMatch 指令來匹配後綴爲「.html」的文件請求。當一個請求匹配這個條件時,咱們就往響應頭裏加入 Link 首部,並告知服務器推送位置在 /css/styles.css的資源。

邊注:Apache 的 HTTP/2 模塊也可使用 H2PushResource 指令啓用資源推送。該指令的文檔指出,這種方法可以早於 Link 首部方法啓用推送。根據 Apache 安裝時的不一樣設置,你也可能沒法使用此功能。本文後面會給出 Link 首部方法的性能測試結果。

截至目前,Nginx 並不支持 HTTP/2 Server Push,目前的 changelog 中沒有任何支持狀況的記錄。而隨着 Nginx HTTP/2 實現的逐漸成熟,這種狀況可能會發生變化。

使用後端代碼設置 Link 首部

另外一個設置 Link 首部的方法是使用服務器端語言。這在你沒法修改或覆蓋服務器配置時十分有效。下面是 PHP header 方法設置 Link 首部的例子:

header("Link: </css/styles.css>; rel=preload; as=style");

若是你的應用程序部署在一個共享的託管環境中,而且修改服務器的配置不太現實,那麼這個方法多是最適合你的。你可使用任何服務端語言設置這個首部。在真實使用前記得確保測試無誤,以免潛在的運行時錯誤。

多資源推送

目前看到的都是演示推送一個資源的例子,若是想一次推送更多資源呢?這麼作也是頗有道理的,對吧?畢竟頁面不止是樣式表組成的。下面來看推送多資源的例子:

Link: </css/styles.css>; rel=preload; as=style, </js/scripts.js>; rel=preload; as=script, </img/logo.png>; rel=preload; as=image

當你想推送多個資源,只要用逗號把每一個指令隔開就好了。由於資源示意是經過 Link 首部加入的,這種語法讓咱們能夠把不一樣資源的推送指令合在一塊兒。這還有個包括 preconnect 的混合推送指令示例:

Link: </css/styles.css>; rel=preload; as=style, <https://fonts.gstatic.com>; rel=preconnect

多個 Link 首部也是一樣合法的。下面是 Apache 給 HTML 配置多個 Link 首部的例子:

<FilesMatch "\.html$">
    Header add Link "</css/styles.css>; rel=preload; as=style"
    Header add Link "</js/scripts.js>; rel=preload; as=script"
<FilesMatch>

這種語法相比一長串逗號分隔的字符串更爲方便,且達到的做用是相同的。惟一的缺點就是沒那麼緊湊,並且會多一點字節量的網絡傳輸,但提供的便利是值得的。

如今知道了如何推送資源,咱們繼續看推送是否生效。

如何分辨 Server Push 是否生效

目前,咱們已經經過 Link 首部來告訴服務器推送一些資源。剩下的問題是,咱們怎麼知道是否生效了呢?

這還要看不一樣瀏覽器的狀況。最新版本Chrome將在開發者工具的網絡發起欄中展現推送的資源。

v2-4acea52158983cc06a4957b94c3bc787_b.png

Chrome顯示服務器推送的資源(大圖

更進一步,若是把鼠標懸停在網絡請求瀑布圖中的資源上,將得到關於該推送資源的詳細耗時信息:

v2-662a241ddfbdd3fd645a1ae2b6fbcaa1_b.png

Chrome顯示推送資源的詳細耗時信息(大圖

Firefox對推送資源則標識地沒那麼明顯。若是一個資源是被推送的,則瀏覽器開發者工具的網絡信息裏,會將其狀態顯示爲一個灰色圓點。

v2-447b374e19c7d36efab5256b9d536a9c_b.png

Firefox對推送資源的展現(大圖

若是你在尋找一個確保能分辨資源是否爲推送的方法,可使用 nghttp 命令行客戶端來檢查是否來自 HTTP/2 服務器,像這樣:

nghttp -ans https://jeremywagner.me

這個命令會顯示出會話中全部資源的彙總結果。推送的資源將在輸出中顯示一個星號(*),像這樣:

id  responseEnd requestStart  process code size request path
 13     +50.28ms      +1.07ms  49.21ms  200   3K /
  2     +50.47ms *   +42.10ms   8.37ms  200   2K /css/global.css
  4     +50.56ms *   +42.15ms   8.41ms  200  157 /css/fonts-loaded.css
  6     +50.59ms *   +42.16ms   8.43ms  200  279 /js/ga.js
  8     +50.62ms *   +42.17ms   8.44ms  200  243 /js/load-fonts.js
 10     +74.29ms *   +42.18ms  32.11ms  200   5K /img/global/jeremy.png
 17     +87.17ms     +50.65ms  36.51ms  200  668 /js/lazyload.js
 15     +87.21ms     +50.65ms  36.56ms  200   2K /img/global/book-1x.png
 19     +87.23ms     +50.65ms  36.58ms  200  138 /js/debounce.js
 21     +87.25ms     +50.65ms  36.60ms  200  240 /js/nav.js
 23     +87.27ms     +50.65ms  36.62ms  200  302 /js/attach-nav.js

這裏,我在本身的站點上使用了 nghttp,有五個推送的資源(至少在寫這篇文章時)。推送的資源在 requestStart 欄左側以星號標記了出來。

如今咱們知道了如何識別推送的資源,接下里具體看看對真實站點的性能有什麼實際影響。

測量 Server Push 性能

測量任何性能提高的效果都須要很好的測試工具。Sitespeed.io 是一個可從 npm 獲取的優秀工具,它能夠自動地測試頁面,收集有價值的性能數據。有了得力的工具,咱們來快速過一下測試方法吧。

測試方法

我想經過一個有意義的方法,來測量 Server Push 對網站性能的影響。爲了讓結果是有意義的,我須要創建6種獨立的場景來交叉對比。這些場景主要以兩個方面進行分隔:使用 HTTP/2 或 HTTP/1。在 HTTP/2 服務器上,咱們想測量 Server Push 在多個指標的效果。在 HTTP/1 服務器上,咱們想看看內聯資源的方法,在相同指標中對性能有什麼影響,由於內聯應該能達到和 Server Push 差很少的效果。具體場景以下:

  • 未使用 Server Push 的HTTP/2
    網站使用了 HTTP/2 協議,但沒有資源是被推送的。

  • 僅推送 CSS 的 HTTP/2
    使用了 Server Push,但僅用在了 CSS 資源。該網站的 CSS 體積比較小,通過 Brotli 壓縮後僅有2KB多一點。

  • 推送全部資源
    網站的全部資源都是推送的。包括了上面的 CSS,以及6個JS(合計 1.4KB)、5個SVG圖片(合計5.9KB)。這些資源一樣通過了壓縮處理。

  • 未內聯資源的HTTP/1
    網站只運行在 HTTP/1 上,沒有內聯任何資源,來減小請求數和加快渲染速度。

  • 只內聯 CSS
    只有網站的 CSS 被內聯了。

  • 內聯全部資源
    頁面上的全部資源都進行了內聯。CSS 和腳本是普通內聯,而 SVG 圖片是通過 Base64 編碼方式直接放入 HTML 標籤中。值得一提的是 Base64 編碼後體積比原先大了1.37倍

在每一個場景中,都使用下面的命令開始測試:

sitespeed.io -d 1 -m 1 -n 25 -c cable -b chrome -v https://jeremywagner.me

若是想知道這個命令的輸入、輸出,能夠參看文檔。簡而言之,這個命令測試了個人網站 Home - Jeremy Wagner 的主頁,使用了下面的條件:

  • 頁面中的連接沒法抓取。只測試指定的頁面。

  • 頁面測試25次

  • 使用了「有線寬帶」級的網絡配置。迴路時間(譯者注:RTT)爲28ms,下行帶寬是5000kbps,上行帶寬爲1000kbps。

  • 測試使用 Google Chrome

每項測試中收集和展現3項指標:

  • 首屏渲染時間
    頁面在瀏覽器首次展示的時間點。當咱們努力讓一個頁面「感受上」加載很快時,那麼這個指標是咱們要儘可能下降的。

  • DOMContentLoaded 時間
    這個是 HTML 完成加載與解析的時間。同步的 JavaScript 代碼會阻塞解析,並致使這個時間增長。在<script>標籤上使用 async 屬性能夠避免對解析的阻塞。

  • 頁面加載時間
    這個是整個頁面完成全部資源加載的耗時。

測試的全部因素都肯定後,讓咱們看看結果!

測試結果

通過對上述6種場景的測試,咱們將結果以圖表形式作了展現。先看看各個場景的首屏渲染時間狀況:

v2-eb702bff65679e36048b2fb25e75f799_b.png

首屏渲染時間(大圖

讓咱們先講講圖表是如何設計的。圖中藍色部分表明了首屏渲染的平均時間,橙色部分是90%的狀況,灰色部分表明了首屏渲染的最長耗時。

接下來咱們討論結果。最慢的情形是未使用任何優化的 HTTP/2 和 HTTP/1。能夠看到,對 CSS 使用 Server Push 使頁面渲染平均速度提高了8%,而內聯 CSS 也比簡單的 HTTP/1 提高了5%速度。

當咱們儘量地推送了全部資源,圖片卻顯示出了一些異樣,首屏渲染時間有所輕微增長。在 HTTP/1 中咱們儘量內聯全部資源,性能表現和推送全部資源差很少,僅僅少了一點時間。

結論很明確:使用 Server Push,咱們能得到比 HTTP/1 中使用內聯更優的性能。但隨着推送或內聯的資源增多,提高的效果逐漸減小。

使用 Server Push 或內聯雖好,但對於首次訪問的用戶並無太大價值(譯者注:實際上對於首次訪問用戶有很大的性能提高,猜想做者這裏筆誤了)。另外,這些測試實驗是運行在較少資源的站點上,因此未必能反映出你的網站的使用狀況。

咱們再看看各項測試對 DOMContentLoaded 時間的影響:

v2-fbf1208be9f09a07ff1e6cac62215fc7_b.png

DOMContentLoaded 時間(大圖

數據趨勢跟剛纔看到的圖表沒太大差異,除了一個須要注意的區別:在 HTTP/1 中儘量地內聯資源,相對 DOMContentLoaded 時間很是低。可能的緣由是內聯減小了須要下載的資源數,從而保證解析器(parser)能夠不被打斷地工做。

最後再看看頁面加載時間的狀況:

v2-0ae56510f975785d635352e1704782c3_b.png

頁面加載時間(大圖

各項測量數據依然保持了先前的趨勢。僅推送 CSS 時加載時間最短。推送全部資源會偶爾致使服務遲緩,但畢竟仍是比什麼都不作表現更優。與內聯相比,Server Push 的各項狀況都是優於內聯的。

在作最後總結前,還要講講使用 Server Push 時可能遇到的問題。

使用 Server Push 的一些建議

Server Push 並非性能優化的萬金油,它也有一些須要注意的地方。

推送過多資源

前面的一項測試中,我推送了不少資源,但它們加起來也只佔傳輸數據的一小部分。一次推送不少大資源的話,會形成頁面渲染及可交互時間的延遲,由於瀏覽器不但要加載 HTML 文檔,還要同時下載推送的資源。最好的作法是有選擇性地推送,樣式表文件是個不錯的開始(目前它們並非很大),接着再評估還有什麼其餘資源適合推送。

推送頁面之外的資源

若是你有訪客統計分析,那麼這種作法也未必很差。一個好的例子是,在多頁註冊帳戶表單場景,能夠推送下一頁的註冊步驟資源。但要澄清的是,若是你不肯定用戶是否會訪問後續的頁面,千萬不要嘗試推送它的資源。有些用戶的流量是十分珍貴的,這麼作可能會致使其沒必要的損失。

正確地配置 HTTP/2 服務

有些服務器會給出不少 Server Push 的配置選項。Apache 的 mod_http2 模塊有一些關於如何推送資源的配置選項。H2PushPriority 設置就比較有意思,雖然在個人服務器上使用了默認設置。有一些實驗性的配置能夠得到額外的性能提高。每一種 Web服務器都有其整套不一樣的實驗性配置,因此查看你的服務器手冊,看看有哪些配置能夠用起來吧!

推送資源可能不被緩存

Server Push 也有一些有損性能的的狀況,對於訪問網站的回頭客們,一些資源可能會被非必要地進行推送。有些服務器會盡量地減輕這種影響。Apache 的 mod_http2 模塊使用了 H2PushDiarySize 設置對這一點進行了一些優化。H2O 服務器有一種 Server Push 緩存感知特性,使用了 Cookie 機制來記錄推送行爲。

若是你不是使用 H2O服務器,也可使用服務端代碼實現一樣的效果,即只推送 Cookie 記錄外的資源。若是有興趣瞭解具體作法,能夠查看我在 CSS Tricks 上的文章。值得一提的是,瀏覽器能夠向服務器發送一個 RST_STREAM 幀來通知不需推送的資源。隨着時間推移,這個問題的解決將會越發優雅。

最後來總結一下以上學到的內容。

最後的思考

若是你已經將本身的網站遷移到 HTTP/2,你沒有什麼理由不使用服務器推送。若是你的網站因有過多的資源而顯得複雜,能夠從體積較小的資源開始嘗試。一個好的經驗法則是,考慮推送那些你曾經用到內聯的資源。推送 CSS 是個不錯的開始。若是感受更有冒險精神以後,就考慮推送其餘資源。要牢記在改動後測試對性能的影響。下了必定功夫後,你必定能從中有所受益。

若是你沒有用像 H2O 這樣使用緩存感知推送機制的服務器,能夠考慮用 cookie 追蹤你的用戶,只在沒有相關 cookie 的狀況下給他們推送資源。這樣能夠爲未知用戶提高着性能的同時,最小化向已知用戶的資源推送量。這不只利於性能優化,也向用戶展現了數據用量的尊重。

剩下的就須要你本身在服務器上折騰 Server Push 了,看看有哪些特性能夠對你或用戶有用吧。若是你想了解更多關於 Server Push,看看這些資源吧:

Server Push,」 「Hypertext Transfer Protocol Version 2 (HTTP/2),」 Internet Engineering Task Force
Modernizing Our Progressive Enhancement Delivery,」 Scott Jehl, Filament Group
Innovating with HTTP 2.0 Server Push,」 Ilya Grigorik

相關文章
相關標籤/搜索