1. 程式人生 > >H2O 中的 Cache-Aware Server Push 簡介

H2O 中的 Cache-Aware Server Push 簡介

提醒:本文最後更新於 1127 天前,文中所描述的資訊可能已發生改變,請謹慎使用。

Server Push 會在客戶端請求頁面 HTML 時,新建一個流將最重要的資源一併返回。同時,如果服務端要推送的資源瀏覽器已經快取過,客戶端會發送 RST_STREAM 幀來終止流,服務端收到這個訊號之前所傳輸的資料就造成了頻寬浪費。

簡而言之,HTTP/2 中的 Server Push 技術使得服務端收到頁面請求後,能夠將頁面所需資源(CSS、JS)通過 PUSH_PROMISE 幀推送給瀏覽器,從而減少延遲。但這帶來一個問題:PUSH_PROMISE 幀和對應的 DATA 幀離開服務端的時機非常早,如果要推送的資源瀏覽器本地已經有了快取,會導致流量的浪費。

HTTP/2 協議本身只有關於 Server Push 技術細節的描述,對於 Web 開發人員與 Web Server 之間使用何種方式約定要 Push 的資源;以及浪費流量的問題是否需要解決,如何解決,協議本身並未做出任何說明,這些都留給了 Web Server 開發者去考慮。我之前說過「這個問題可以通過在服務端記錄給每個客戶端傳送過何種資源,何時過期來優化」,本文介紹 H2O 的解決方案。

H2O(官網GitHub)是一個用 C 語言實現的輕量級 Web Server,它最近釋出的 1.5.0 版,新增了一個名為 Cache-Aware Server-Push 的技術,直譯過來意思是「可感知快取的 Server Push」。原理是在首次 Server Push 完成後,在客戶端存一個指紋,服務端後續檢查到指紋存在時,先在指紋中查詢要 Push 的資源,沒查到才推送。

原理不復雜,跟如今大家廣泛使用的「內聯資源 localStorage 儲存」方案類似,H2O 的指紋也是存在 Cookie 中,名為 h2o_casper,過期時間在 2030 年。

我們考慮一個問題,對於攜帶了 h2o_casper 指紋的請求,H2O 如何能知道哪些資源需要被推送,哪些不需要?Cookie 每個位元組都很寶貴,不可能把每個資源的檔名及對應版本都裝進去。如果 Cookie 中只存放使用者標識,那又依賴於後端的 KV 查詢服務,不符合 H2O 輕量、單一的理念。

實際上,這是一個經典的問題:在儲存空間有限的情況下,如何判斷一個元素是否存在於一個集合之內,且允許一定的誤差(Server Push 屬於錦上添花功能,沒被推送的資源瀏覽器還會走常規流程獲取)。這個問題正好可以用 Bloom Filter

(布隆過濾器)演算法解決,它能以很小的誤差作為代價換取了儲存空間的極大節省。有關 Bloom Filter 的詳細原理請看這篇《Bloom Filter 概念和原理》。

H2O 使用了一種名為 Golomb-compressed sets 的演算法,它與 Bloom Filter 非常類似,根據官方說明,它的壓縮率比 Bloom Filter 高 20-30%。關於這個演算法的詳細介紹,請看我寫的《Golomb-coded sets 原理介紹》;對應的 JS 實現和 Demo,請點選這個頁面檢視

H2O 將要推送的資源路徑 + ETag 存入一個集合,採用 Golomb-compressed sets 演算法生成指紋,並編碼為 Base64 字串存入 Cookie,之後就可以檢查某個資源路徑 + ETag 是否存在於 Cookie 指紋對應的集合之中。H2O 預設使用 13 位二進位制來存放單個資源的指紋,單資源誤判率為 1/8192。最終輸出的 h2o_casper Cookie 大小等於單個指紋大小乘以 Push 的資源個數,由於需要轉為 Base64,最後還要變大 4/3 倍。

通過 Chrome 的 HTTP/2 除錯工具對比首次和第二次訪問 H2O 官網的幀資訊,可以清楚地看出 Cache-Aware Server Push 機制產生的效果,大家可以自己動手試一下。

本文先寫到這裡,想要了解如何在 H2O 中啟用這個功能,請直接檢視官方文件。最後想說的是,HTTP/2 在優先順序、Server Push 等技術點上的實現策略完全取決於具體的服務端和客戶端,期待後續有更多讓人眼前一亮的方案出來。

--EOF--

提醒:本文最後更新於 1127 天前,文中所描述的資訊可能已發生改變,請謹慎使用。