1. 程式人生 > >Nginx實現HTTP/2——原理、實踐與資料分析

Nginx實現HTTP/2——原理、實踐與資料分析

HTTP/2(Hypertext Transfer Protocol Version 2)無疑是下一代網際網路加速技術的基石與方向,自誕生以來經歷了00~17 共 18 版草案,目前,已經擁有了明確的RFC標準:RFC 7540RFC 7541
目前支援 HTTP/2 的瀏覽器有 Chrome 41+、Firefox 36+、Safari 9+、Windows 10 上的 IE 11 和 Edge。伺服器方面則有 F5、H2O、nghttp2 等數十種選擇,各種語言實現的都有。Nginx自1.9.5版本開始支援HTTP/2的http_v2_module模組,在這個過程中修復了一些bug,目前穩定的版本是nginx-1.10.1。另外,移動端開發可以使用

OkHttp來實現HTTP/2。
HTTP/2瀏覽器支援性,其中最差的是IE,只有11能夠支援
綜上所述,無論是移動APP還是瀏覽器,無論是客戶端還是服務端,都已經具備了支援HTTP/2的能力,HTTP/2已經是大勢所趨,筆者將基於nginx-1.10.1來實現HTTP/2,並分析HTTP/2的效能優勢,結合其特點來確定如何在線上環境中得到有效應用。

1.HTTP/2原理

如果要深入講解HTTP/2的原理,一節的內容是遠遠不夠的,後期筆者會專門來做一個HTTP/2的系列。那本節內容主要對HTTP/2的改動與優勢做簡單介紹,並且回答兩個問題(這兩個問題也曾深深困擾著我):1. 如果瀏覽器不支援HTTP/2,而服務端開啟了HTTP/2,會否有相容性影響?2. HTTP/2請求是否一定要是HTTPS請求呢?通過這些對HTTP/2有一個全面的認識。

1.1 HTTP/1的弊端

先來說說HTTP/1.1的問題,這裡請原諒筆者的囉嗦,需要把事情說清楚。
基本的HTTP/1.1是基於文字格式的,單個請求單個連線,客戶程序建立一條同伺服器程序的TCP連線,然後發出請求並讀取伺服器程序的響應。伺服器程序關閉連線表示本次響應結束。因此,頁面載入開銷是非常大的。
為此,HTTP/1.1自然不會坐以待斃,也做了一些優化,包括:

  • 瀏覽器併發控制Pipelining

瀏覽器Pipelining是將同一Host的多個HTTP請求整批提交的技術,而在傳送過程中不需先等待服務端的迴應。不同瀏覽器的併發數是不同的,比如IE是預設兩個請求。
為了使Pipelining的效益最大化,需要將請求的域名Host分散,如下圖所示:

這裡寫圖片描述
而這樣,其弊端是很明顯的,不同Host需要額外的定址消耗,會導致DNS時間增長,尤其當某些地區DNS解析不穩定時,甚至會導致大量請求的阻塞,得不償失。

  • HTTP Keep-Alive

即我們常見的:Connection:keep-alive。資料傳輸完成了保持TCP連線不斷開,等待在同域名下繼續用這個通道傳輸資料。這樣就減少了TCP的建連次數。
需要注意的TCP Keep-Alive和HTTP Keep-Alive是不同層次上的概念,TCP的keep alive是檢查當前TCP連線是否活著;HTTP的Keep-alive是要讓一個TCP連線在timeout週期內永久存活。
一般而言,HTTP Keep-Alive和Pipelining技術是相結合使用的,這樣同一個域名下的請求就能實現併發而非序列,然而,事情真的有我們想的那麼好嘛?

線頭阻塞問題:由於要求服務端返回響應資料的順序必須跟客戶端請求時的順序一致,這容易導致Head-of-line blocking:第一個請求的響應傳送影響到了後邊的請求,因為這個原因導致HTTP流水線技術對效能的提升並不明顯。
另外,由於域名是分散的,同一個站點,瀏覽器往往要保持8-10條長連線,資源消耗更大了。
綜上所述,由於HTTP/1.1協議本身的缺陷(基於文字,過於簡單),即使是已有的優化,也無法滿足日益增長的效能需求。

1.2 HTTP/2的優點

HTTP/2的優點總結為一個字,就是“”:

  • HTTP/2是完全多路複用的,而非有序並阻塞的;

  • 請求優先順序

  • 使用報頭壓縮,HTTP/2降低了開銷

  • HTTP/2實現了服務端響應的主動推送

1.2.1 多路複用

完全的多路複用是HTTP/2的核心,HTTP/2採用二進位制幀格式而非文字格式進行傳輸,單個連線內多個流(請求-響應)之間並行處理,減少網路資源佔用,可避免了TCP頻繁的開啟、關閉。如下圖所示,一言以蔽之,對一個站點,現在只需建連一次,就可以多路複用。
這裡寫圖片描述

通過Wireshark抓包,可以明顯對比看到HTTP/1.1和HTTP/2的區別:
HTTP/1.1的包是典型的一條請求發出,等待響應,響應後再發第二條請求,再等待響應。
這裡寫圖片描述
HTTP/2的包(如何用Wireshark抓HTTP/2包?請參考)是請求並行發出,同時得到響應,且不管是請求還是響應都是二進位制幀格式,而不是文字格式,實現了多路複用高併發。
這裡寫圖片描述
這裡寫圖片描述

1.2.2 請求優先順序

流的優先順序(priority)屬性建議終端(客戶端+伺服器端)需要按照優先順序值進行資源合理分配,優先順序高的需要首先處理,優先順序低的可以稍微排排隊,這樣的機制可保證重要資料優先處理。

優先順序改變:

  • 終端可在新建的流所傳遞HEADERS幀中包含優先順序priority屬性
  • 可單獨通過PRIORITY幀專門設定流的優先順序屬性

然而,具體如何應用到線上,到目前為止,還沒有看到有成熟的例子,這一點還有待研究。

1.2.3 HPACK壓縮

HTTP/2使用了報頭壓縮技術,壓縮演算法使用HPACK。可讓報頭更緊湊,更快速傳輸,有利於行動網路環境
需要注意的是,HTTP/2關注的是報頭(header)壓縮,而我們常用的gzip等是報文內容(body)的壓縮。二者不僅不衝突,且能夠一起達到更好的壓縮效果。
以一個空文件(body大小可忽略)為例,HTTP/1.1的大小為252B
這裡寫圖片描述
HTTP/2的大小為140B,壓縮了100B,接近50%。
這裡寫圖片描述

1.2.4 服務端主動推送

傳統方式:客戶端請求,伺服器響應,客戶端逐一解析需要後續請求的圖片、樣式等資源,再次一一發送資源請求
HTTP/2伺服器根據客戶端請求,計算出響應內容所包含的資源,在客戶端發起請求之前提前傳送給客戶端
這樣做,節省了客戶端主動發起請求的時間的往返時間。
需要注意的是,目前Nginx仍不支援Server Push功能

1.3 HTTP/2如何建立連線

HTTP/2請求其實並不一定要是HTTPS請求,可分為兩種:

  • h2,基於TLS之上構建的HTTP/2,作為ALPN的識別符號,兩個位元組表示,0x68, 0x32,即必須是https
  • h2c,直接在TCP之上構建的HTTP/2,缺乏安全保證,即http

然而,目前所有支援HTTP/2的瀏覽器都是基於TLS 1.2協議之上構建HTTP/2的,因此,實際應用中,PC端要使用HTTP/2必須先支援https;移動端(比如OkHttp)可以直接在http請求上實現HTTP/2

HTTP/2保留併兼容了HTTP/1.1的所有語義,但傳輸語法(或者說傳輸方式)改變。換言之,究竟是使用HTTP/2還是HTTP/1.1來傳輸資料是可以由客戶端和服務端協商的,當瀏覽器使用HTTPS傳輸時,協商是強制,封裝在TLS之上的ALPN擴充套件協議上。協商一致,則使用HTTP/2進行傳輸,若協商過程中發現服務端不支援HTTP/2,則使用HTTP/1.1進行傳輸。其過程如下所示:

這裡寫圖片描述

每個站點只需要協商一次,且協商過程是在第一次SSL握手過程中就完成的:
這裡寫圖片描述
Client Hello中詢問,你支援HTTP/2嗎?
這裡寫圖片描述
Server Hello中應答,我支援!
這裡寫圖片描述

2. Nginx實現HTTP/2

Nginx 自1.9.5版本新增了 http_v2_module 模組用於提供 HTTP/2 服務。建議測試環境可使用1.10.1後的版本,因為在此之間的版本並不是穩定的,且修復了一些HTTP/2的問題(當然,1.10.1也不是完美無缺的,比如也存在HTTP/2 POST Bug),真正生產環境建議使用Nginx 1.11.0 穩定版。

2.1 ngx_http_v2_module模組安裝

ngx_http_v2_module模組提供了對HTTP/2的支援,用了替代ngx_http_spdy_module模組。該模組不是預設構建的,所以整合的話必須重新構建Nginx並新增配置 –with-http_v2_module,並且必須支援OpenSSL version 1.0.2.以上(ALPN協議需要)。具體步驟如下:
(1)配置

./configure --with-http_ssl_module \
      --with-http_v2_module \
      --with-debug \
      --with-openssl=/path/to/openssl-1.0.2

(2)編譯並安裝

make&make install

(3)修改nginx.conf配置

server {
     listen 443 default_server ssl http2;

     ssl_certificate      server.crt;
     ssl_certificate_key  server.key;

     ...
 }

三步完成後,Nginx的443埠已經支援HTTP/2協議了。

2.2 ngx_http_v2_module模組指令說明

ngx_http_v2_module模組可以通過指令修改一系列配置,來調整HTTP/2效能:

  • http2_chunk_size
Syntax: http2_chunk_size size;
Default:    
http2_chunk_size 8k;
Context:    http, server, location

設定響應報文內容(response body)分片的最大長度。如果這個值過小,將會帶來更高的開銷,如果值過大,則會導致線頭阻塞的問題。預設大小8k。

  • http2_body_preread_size
Syntax: http2_body_preread_size size;
Default:    
http2_body_preread_size 64k;
Context:    http, server

用於解決HTTP/2 POST Bug,1.11.0版本以上有效。請求內容在被處理前儲存緩衝區的大小。1.9.5~1.10.0這個值都是預設為0的,1.11.0預設是64k。

  • http2_idle_timeout
Syntax: http2_idle_timeout time;
Default:    
http2_idle_timeout 3m;
Context:    http, server

設定空閒連線關閉的超時時間。

  • http2_max_concurrent_streams
Syntax: http2_max_concurrent_streams number;
Default:    
http2_max_concurrent_streams 128;
Context:    http, server

設定一個連線中最大併發流的數量

  • http2_max_field_size
Syntax: http2_max_field_size size;
Default:    
http2_max_field_size 4k;
Context:    http, server

限制經過HPACK壓縮後請求頭中每個欄位的最大尺寸。

  • http2_max_header_size
Syntax: http2_max_header_size size;
Default:    
http2_max_header_size 16k;
Context:    http, server

限制經過HPACK壓縮後完整請求頭的最大尺寸。

  • http2_recv_buffer_size
Syntax: http2_recv_buffer_size size;
Default:    
http2_recv_buffer_size 256k;
Context:    http

設定每一個worker的輸入緩衝區大小

  • http2_recv_timeout
Syntax: http2_recv_timeout time;
Default:    
http2_recv_timeout 30s;
Context:    http, server

設定當連線關閉後,等到客戶端是否傳送更多的資料來的超時時間。

另外,還提供了引數$http2,來表示是否使用了HTTP/2。

3. 資料分析與效果對比

使用HTTP/2的目的最終是為了提高使用者體驗,所以其效果必須以資料說話。筆者認為重點應該關注兩個方面:
1. 伺服器高負載情況,對比http、HTTPS、HTTP/2的Tps;
2. 不同瀏覽器(包括支援和不支援HTTP/2),在使用http、HTTPS、HTTP/2下的載入效能。

3.1 服務端壓測Tps對比

(1)壓測工具選擇
http和HTTPS的壓測工具我們使用Apache ab,HTTP/2的壓測工具我們使用nghttp2(極力推薦這款工具,包括了nghttp客戶端、nghttpd服務端、nghttpx代理、h2load壓測四個模組)。
(2)壓測場景
高併發情況,100使用者,10萬個請求。
請求大小250B,其中內容只有16B。

(3)壓測結果
http壓測結果,Tps 6749 req/s,平均響應效能44.451ms
同時,服務端的效能消耗主要在CPU,CPU消耗在15%。
這裡寫圖片描述
這裡寫圖片描述
HTTPS壓測結果,Tps僅僅只能達到871.98 req/s,平均響應效能114.682ms
服務端CPU的消耗非常的大,在60%(因為SSL握手需要消耗大量的CPU運算,如何緩解這個問題?SSL優化我們將在其他篇幅中詳述)
這裡寫圖片描述
這裡寫圖片描述
HTTP/2壓測結果出乎意料的好,Tps居然能達到10126.12 req/s,同時,HPACK壓縮能夠節省40%的頭部空間。
另外一方面,服務端CPU的消耗為25%,在可接受的範圍。
這裡寫圖片描述
這裡寫圖片描述

(4)結論

HTTP/2通過多路複用,減少了連線數,明顯提高了服務端的處理能力(接近1萬Tps),優於HTTP/1.1。與此同時,其對服務端CPU的資源消耗又明顯小於HTTPS。

3.2 瀏覽器載入時間對比

首先,我們在測試環境構建了一個模擬的商品促銷頁主會場,頁面大小11.2MB,請求數302個。HTTP/1.1的頁面中採用了多種HTTP/1.1的優化技術,包括Pipelining,Keep-Alive,指令碼合併等。
瀏覽器我們分別選擇了支援HTTP/2的Chrome,Firefox和不支援HTTP/2的IE瀏覽器,並使用主動撥測工具Webpagetest(自動化的頁面效能測試工具)。採集的頁面載入資料如下所示:
這裡寫圖片描述
我們可以很明顯地看到,火狐和Chrome,HTTP/2的加速效果,其中Chrome的加速效果最佳,HTTP/2(1.196s左右)較HTTPS(2.233s左右)速度提高了80%,較http(1.689s)速度提高了40%。與此同時,就IE 8來看,HTTP/2的載入時間沒有明顯的變慢(因為其實已經降級為HTTP/1.1了)
這裡寫圖片描述

結論:資料是HTTP/2加速能力最有力的說明。

4. 如何更好地使用HTTP/2

在使用HTTP/2的過程中,並不是一次性的就能看到其顯著的加速的效果,要使HTTP/2發揮作用,除了引數調優外,我們總結還需要注意兩個方面:

  • 頁面元素的相應調整

HTTP/1.1頁面提倡指令碼合併,大量圖片域名發散(image1-5)。
而HTTP/2是多路複用的,因此恰恰相反,最好做到:
(1)指令碼和樣式打散為小資源,使用同一個域名,比如http2res.XXXX.com
(2)圖片資源使用同一個域名,比如http2img.XXXX.com

  • 結合Nginx的其他效能優化配置

Nginx對SSL優化。比如約定加密套件(ssl_ciphers)、SSL Session複用(ssl_session_cache | ssl_session_tickets )、keepalive_timeout的設定等。SSL優化和HTTP/2兩者並不衝突,前者關注減少CPU消耗,後者重點是鏈路的複用。
另外,還有Nginx TCP 優化、Gzip壓縮等技術。當然,所有這些必須經過嚴格的壓測和線上驗證,這一部分的工作我們仍然在進行中。

  • 建議優先在移動端使用

為什麼建議優先在移動端應用HTTP/2呢?因為經過筆者的測試,國內的所有瀏覽器,包括360、獵豹、百度、QQ、UC都不支援HTTP\2(雖然他們都是Chrome核心,這一點真的無力吐槽,沒有抄襲到人家的精華..),所以並不能起到加速的效果。
如果真要在PC端實現,可以通過UA來判斷,對支援HTTP/2的瀏覽器指向HTTP/2加速的主機,對不支援的指向HTTP/1.1加速的主機。

以上,筆者已經簡單介紹了HTTP/2的原理,Nginx如何實現HTTP/2以及相關引數調優。並且將我們所做的測試資料分享出來以證明HTTP/2的加速效果實至名歸。還給出了一些使用HTTP/2的建議。最後想說,這只是個開始,後面還有很多挑戰,希望大家一起愈挫愈勇,群策群力!

參考文獻