HTTP/2 中的 Server Push 討論

本文不討論 Server Push 是什么,也不討論如何使用 Server Push。

之前我在《開始使用 HTTP/2》這篇文章中寫到:

如果服務端想要推送的資源本地已經緩存過,客戶端會發送 RST_STREAM 告訴服務端不要再傳了。不過根據我的觀察,H2O 服務端在收到 RST_STREAM 之前,已經發出了資源,造成了流量的浪費。具體原因,我正在向 H2O 作者求證。

我給 H2O 項目提了一個 issues 詢問此事,得到了 H2O 的作者 Kazuho Oku,aria2 的作者 Tatsuhiro Tsujikawa,以及《High-Performance Browser Networking》的作者 Ilya Grigorik 的回答。基本搞明白了這個問題,本文記錄一下。

Kazuho Oku 的回答:

Because the HEADERS frame has left the server before the RST_STREAM sent from the client reaches the server.

這是因為客戶端發出的 RST_STREAM 幀到達服務端之前,那些 HEADERS 幀(注:HTTP2_SESSION_RECV_HEADERS 和 HTTP2_SESSION_RECV_DATA)已經離開了服務端。

The issue is known to the protocol designers of HTTP/2, and my understanding is that there is a discussion on how the client should notify the server if it already has the asset cached. Until such mechanism gets introduced, we cannot fix the issue (or in other words you should better not use push if the negative impact of a cached content being pushed outweighs the positive impact of pushing the content every time).

HTTP/2 協議制定者已經知道這個問題的存在,我認為應該討論一下客戶端已經緩存資源時,如何通知服務端。直到 HTTP/2 協議定義相應的機制前,我們無法修復這個問題(換句話說,如果緩存過的資源每次都被 Push 這件事所帶來的弊端大于好處,你就不應該使用這個功能)。

Tatsuhiro Tsujikawa 的回答:

It is a race condition, between server starts sending pushed data, and it receives RST_STREAM.

服務端開始 Push 數據與收到 RST_STREAM 幀這二者是競爭狀態。

If server receives RST_STREAM while sending pushed data, it will stop sending remaining data.

如果服務端正在 Push 數據時收到了 RST_STREAM 幀,它會停止發送剩余數據。

People argue the wasted bandwidth when server push is involved. That is by design.

人們爭論啟用 Server Push 所帶來的帶寬浪費。但設計就是如此。

Server push is asynchronous, and just send server's discretion, without client's confirmation, to eliminate latency penalty. There will be novel use cases for server push, but here we discuss the use case for ordinal web pages. In this case, server push is introduced to eliminate ugly inlining we used to avoid additional HTTP request because in HTTP/1.1 request is expensive.

Server Push 是異步的,會發送服務端認為需要發送的內容,不需要客戶端確認,目的是消除網絡延遲。今后肯定會有關于 Server Push 的新穎用法出現,但這里我們要討論在現有網頁中使用 Server Push。這時候,Server Push 的作用是取代「由于 HTTP/1.1 的連接數很寶貴,為避免產生額外 HTTP 請求而引入的」資源內聯----它太丑陋了。

We mean inlining here is data URI of images in html page, or CSS, to reduce the number of requests.

我們這里說的內聯,是指將圖片通過 Data URI 的方式內置在 HTML 或者 CSS 里,作用是減少請求數。

In HTTP/2, requests are cheap now, and we don't have to do inlining. But without inlining there could be latency while browser discovers link and requests to the server. Server push is useful in this situation. You can push contents we used to inlined in html. Now we are no worse than HTTP/1.1 inlining, even if we "waste" bandwidth. In HTTP/1.1, these assets are always inlined, so they are always wasted. Also data URI is horrible, just inflating bytes. Now in server push, we have separate cache space, and if client only wants to download html, without formally inlined assets, it can do so by disabling push. And we allow client to cancel this push, although it may too late in some situation.

在 HTTP/2 里,請求變得非常廉價,我們不再需要內聯了。但是如果沒有內聯,瀏覽器在發現外鏈資源并發送請求之前,會有一段時間的延遲。這時候 Server Push 就變得非常有用了。之前需要內聯在 HTML 里的資源現在可以改由服務端 Push。現在即使「浪費」了帶寬,也不比 HTTP/1 的內聯差。因為在 HTTP/1.1 里,那些資源總是會被內聯,總會被浪費。另外,Data URI 非常可怕,因為編碼后字節數會變多。現在有了 Server Push,每個資源都有自己單獨的緩存,如果客戶端只想下載沒有內聯資源的 HTML,可以通過禁用 Push 來實現。我們也允許客戶端取消 Push,盡管有時候已經太遲了。

But still we just improving situation, not worsening it.

但是這一切仍然是讓情況變得更好,而不是更糟糕。

Ilya Grigorik 的回答:

+1 to everything @tatsuhiro-t said.

+1 完全同意樓上所說。

Also, note that the client can limit the server in what it's allow to send: (a) the client can limit max number of push streams, and (b) it can set a low flow control window to limit how much data the server is allowed to send in first RTT. Between these two the client has all the controls to govern how and where push is used -- e.g. if you're in data savings mode, you may want to tweak either or both of those. Beyond that, the server can also be smarter about when it pushes resources -- e.g. if it pushes a cacheable resource on first session hit, and then receives a second session hit on same connection (which is within cache lifetime of first push), then it can omit pushing that resources.. and so forth.

同時,請注意客戶端可以限制服務端能 Push 什么:1)客戶端可以限制 Push 流的最大數目;2)客戶端可以設置一個很低的流量控制窗口,來限制服務端在第一個往返內發送的數據大小。基于這兩點,客戶端可以完全控制 Push 怎么用和用在哪 ----例如,如果你在使用省流模式,你可能會調整前面兩個策略之一或者全部。除此之外,服務端在 Push 資源時可以更聰明一些----例如,如果服務端在第一個 Session 里 Push 了一個可被緩存的資源,然后又在同一個連接上收到第二個 Session(在第一次 Push 資源的緩存生命周期內),這時候就可以不再 Push 這個資源了。。諸如此類。

我的理解:

頁面有一些很重要的資源需要盡快加載,例如核心的 css 文件。瀏覽器加載它們的延遲來自多方面,這幾個是 HTTP/2 能解決的:1)同域名并發連接數限制造成的阻塞時間;3)瀏覽器從 HTML 中找出外鏈資源這段時間;3)瀏覽器發起請求到服務端收到請求這段時間。

HTTP/2 的多路復用特性消除了同域名并發連接數限制;Server Push 則會在客戶端請求頁面 HTML 時,新建流將最重要的資源一并返回。同時,如果服務端要推送的資源瀏覽器已經緩存過,客戶端會發送 RST_STREAM 幀來終止流,服務端收到這個信 號之前所傳輸的數據就造成了帶寬浪費。這個問題可以通過在服務端記錄給每個客戶端發送過何種資源,何時過期來優化。實際上,大多數時候帶寬都不是瓶頸,延遲才是首先需 要考慮的。

另外,W3C 的 Preload 文檔中,定義了一種通過響應頭部盡早告訴瀏覽器需要加載哪些資源的方案。這個方案非常輕量,幾乎沒有副作用,也推薦使用。

專題「HTTP/2 相關」的其他文章 ?

  • 談談 Nginx 的 HTTP/2 POST Bug (Aug 20, 2016)
  • 為什么我們應該盡快支持 ALPN? (May 18, 2016)
  • 談談 HTTP/2 的協議協商機制 (Apr 14, 2016)
  • 使用 nghttp2 調試 HTTP/2 流量 (Mar 07, 2016)
  • 從啟用 HTTP/2 導致網站無法訪問說起 (Jan 17, 2016)
  • 基于 HTTP/2 的 WEB 內網穿透實現 (Nov 23, 2015)
  • HTTP/2:新的機遇與挑戰 (Nov 22, 2015)
  • HTTP/2 頭部壓縮技術介紹 (Oct 25, 2015)
  • 使用 Wireshark 調試 HTTP/2 流量 (Oct 24, 2015)
  • H2O 中的 Cache-Aware Server Push 簡介 (Oct 21, 2015)

所屬標簽

無標簽

25选5玩法中奖