原文: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
訪問網站始終遵循着請求——響應模式。用戶將請求發送到遠程服務器,在一些延遲後,服務器會響應被請求的內容。apache
對網絡服務器的初始請求一般是一個 HTML 文檔。在這種狀況下,服務器會用所請求的 HTML 資源進行響應。接着瀏覽器開始對 HTML 進行解析,過程當中識別其餘資源的引用,例如樣式表、腳本和圖片。緊接着,瀏覽器對這些資源分別發起獨立的請求,等待服務器返回。npm
典型的服務器通訊(大圖)後端
這一機制的問題在於,它迫使用戶等待這樣一個過程:直到一個 HTML 文檔下載完畢後,瀏覽器才能發現和獲取頁面的關鍵資源。從而延緩了頁面渲染,拉長了頁面加載時間。
有了 Server Push,就有了解決上述問題的方案。Server Push 能讓服務器在用戶沒有明確詢問下,搶先地「推送」一些網站資源給客戶端。只要正確地使用,咱們能夠根據用戶正在訪問的頁面,給用戶發送一些即將被使用的資源。
好比說你有一個網站,全部的頁面都會在一個名爲 styles.css 的外部樣式表中,定義各類樣式。當用戶向務器請求 index.html 時,咱們能夠在發送 index.html 的同時,向用戶推送 styles.css。
使用HTTP/2 Server Push的Web服務器通訊(大圖)
相比等待服務器發送 index.html,而後等待瀏覽器請求並接收 styles.css,用戶如今只需等待1次服務器響應,就可在初次請求同時使用 index.html 和 styles.css。
能夠想象,這能夠下降頁面的渲染時間。它還解決了一些其餘問題,特別是在前端開發工做流方面。
Server Push 解決了減小關鍵內容的網絡迴路耗時問題,但這並非惟一的做用。Server Push 更像是 HTTP/1 特定優化反模式的替代方案,例如將 CSS 和 JavaScript 內聯在 HTML,以及使用 data URI 方案將二進制數據嵌入到 CSS 和 HTML 中。
這些技術在 HTTP/1 優化工做流中很是受用,是由於這樣減小了咱們所說的頁面「感知渲染時間」,也就是說在頁面總體加載時間可能不會減小的同時,對用戶而言網頁的加載速度卻顯得更快。這確實是說得通的,若是你將 CSS 內嵌到 HTML 的<style>標籤中,瀏覽器就能夠無需等待外部資源的獲取,而當即應用 HTML 中的樣式。這種概念一樣適用於內聯腳本,以及使用 data URI 方式內聯二進制數據。
內聯內容的服務器通訊(大圖)
這看起來是個不錯的方案,對吧?在 HTTP/1 的時代確實如此,由於也沒有別的選擇。而這麼作實際上也留下了惡果,即內聯的內容不能有效地被緩存。若樣式、腳本資源之外鏈及模塊形式引用,會更高效地進行緩存。當用戶訪問後續頁面須要這些資源時,能夠直接從緩存中獲取,從而省去了額外的資源請求。
優化緩存行爲(大圖)
而當咱們對內容進行內聯時,它們是沒有獨立的緩存上下文的,僅存在於所內聯文檔的上下文中。舉個在 HTML 中內聯 CSS 的例子,若是 HTML 的緩存策略,是每次訪問都向服務器拉取最新的內容,那麼內聯的CSS老是沒法緩存其內容。即便把 HTML 進行緩存,但在後續訪問的頁面內,內聯相同的 CSS 內容也是須要重複下載的。這仍是比較寬鬆的緩存策略,實際狀況中 HTML 僅有較短的緩存週期。內聯是咱們在 HTTP/1 優化方案中所作的權衡,它確實在用戶第一次訪問時很是有效,而每每第一印象是很是重要的。
這就是 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 方法)。
下面是一個 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 首部的方法是使用服務器端語言。這在你沒法修改或覆蓋服務器配置時十分有效。下面是 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>
這種語法相比一長串逗號分隔的字符串更爲方便,且達到的做用是相同的。惟一的缺點就是沒那麼緊湊,並且會多一點字節量的網絡傳輸,但提供的便利是值得的。
如今知道了如何推送資源,咱們繼續看推送是否生效。
目前,咱們已經經過 Link 首部來告訴服務器推送一些資源。剩下的問題是,咱們怎麼知道是否生效了呢?
這還要看不一樣瀏覽器的狀況。最新版本Chrome將在開發者工具的網絡發起欄中展現推送的資源。
Chrome顯示服務器推送的資源(大圖)
更進一步,若是把鼠標懸停在網絡請求瀑布圖中的資源上,將得到關於該推送資源的詳細耗時信息:
Chrome顯示推送資源的詳細耗時信息(大圖)
Firefox對推送資源則標識地沒那麼明顯。若是一個資源是被推送的,則瀏覽器開發者工具的網絡信息裏,會將其狀態顯示爲一個灰色圓點。
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 欄左側以星號標記了出來。
如今咱們知道了如何識別推送的資源,接下里具體看看對真實站點的性能有什麼實際影響。
測量任何性能提高的效果都須要很好的測試工具。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種場景的測試,咱們將結果以圖表形式作了展現。先看看各個場景的首屏渲染時間狀況:
首屏渲染時間(大圖)
讓咱們先講講圖表是如何設計的。圖中藍色部分表明了首屏渲染的平均時間,橙色部分是90%的狀況,灰色部分表明了首屏渲染的最長耗時。
接下來咱們討論結果。最慢的情形是未使用任何優化的 HTTP/2 和 HTTP/1。能夠看到,對 CSS 使用 Server Push 使頁面渲染平均速度提高了8%,而內聯 CSS 也比簡單的 HTTP/1 提高了5%速度。
當咱們儘量地推送了全部資源,圖片卻顯示出了一些異樣,首屏渲染時間有所輕微增長。在 HTTP/1 中咱們儘量內聯全部資源,性能表現和推送全部資源差很少,僅僅少了一點時間。
結論很明確:使用 Server Push,咱們能得到比 HTTP/1 中使用內聯更優的性能。但隨着推送或內聯的資源增多,提高的效果逐漸減小。
使用 Server Push 或內聯雖好,但對於首次訪問的用戶並無太大價值(譯者注:實際上對於首次訪問用戶有很大的性能提高,猜想做者這裏筆誤了)。另外,這些測試實驗是運行在較少資源的站點上,因此未必能反映出你的網站的使用狀況。
咱們再看看各項測試對 DOMContentLoaded 時間的影響:
DOMContentLoaded 時間(大圖)
數據趨勢跟剛纔看到的圖表沒太大差異,除了一個須要注意的區別:在 HTTP/1 中儘量地內聯資源,相對 DOMContentLoaded 時間很是低。可能的緣由是內聯減小了須要下載的資源數,從而保證解析器(parser)能夠不被打斷地工做。
最後再看看頁面加載時間的狀況:
頁面加載時間(大圖)
各項測量數據依然保持了先前的趨勢。僅推送 CSS 時加載時間最短。推送全部資源會偶爾致使服務遲緩,但畢竟仍是比什麼都不作表現更優。與內聯相比,Server Push 的各項狀況都是優於內聯的。
在作最後總結前,還要講講使用 Server Push 時可能遇到的問題。
Server Push 並非性能優化的萬金油,它也有一些須要注意的地方。
前面的一項測試中,我推送了不少資源,但它們加起來也只佔傳輸數據的一小部分。一次推送不少大資源的話,會形成頁面渲染及可交互時間的延遲,由於瀏覽器不但要加載 HTML 文檔,還要同時下載推送的資源。最好的作法是有選擇性地推送,樣式表文件是個不錯的開始(目前它們並非很大),接着再評估還有什麼其餘資源適合推送。
若是你有訪客統計分析,那麼這種作法也未必很差。一個好的例子是,在多頁註冊帳戶表單場景,能夠推送下一頁的註冊步驟資源。但要澄清的是,若是你不肯定用戶是否會訪問後續的頁面,千萬不要嘗試推送它的資源。有些用戶的流量是十分珍貴的,這麼作可能會致使其沒必要的損失。
有些服務器會給出不少 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