上篇文章深入淺出:5G和HTTP裡給自己挖了一根深坑,說是要寫一篇關於HTTP/2的文章,今天來還賬了。
本文分為以下幾個部分:
- HTTP/2的背景
- HTTP/2的特點
- HTTP/2的協議分析
- HTTP/2的支援
HTTP/2簡介
HTTP/2主要是為了解決現HTTP 1.1效能不好的問題才出現的。當初Google為了提高HTTP效能,做出了SPDY,它就是HTTP/2的前身,後來也發展成為HTTP/2的標準。
HTTP/2相容HTTP 1.1,例如HTTP Method,Status code,URI以及大部分Header Fields。
HTTP/2通過以下方法減少latency,用來改進頁面載入的速度,
- HTTP Header的壓縮,採用的是HPack演算法。
- HTTP/2的Server Push,非常重要的一個特性。
- 請求的pipeline。
- 修復在HTTP 1.x的隊頭阻塞問題。
- 在單個TCP連線裡多工複用請求。
HTTP/2支援HTTP 1.1裡的大部分use case,例如桌面瀏覽器、移動瀏覽器、Web API、Web Server、代理伺服器、反向代理伺服器、防火牆和CDN等。
HTTP/2 頭部壓縮(HPack)
HPack是HTTP/2 裡HTTP頭壓縮的演算法,具體可以參看https://tools.ietf.org/html/rfc7541。下面簡單介紹一下HPack是如何工作的。
見下圖,該圖來自Google 的效能專家 Ilya Grigorik 的文章HTTP/2 is here, let's optimize!,它非常直觀地描述了 HTTP/2 中頭部壓縮的原理:
簡單說,HTTP頭壓縮需要在HTTP/2 Client和服務端之間:
- 維護一份相同的靜態表(Static Table),包含常見的頭部名稱,以及特別常見的頭部名稱與值的組合;
- 維護一份相同的動態表(Dynamic Table),可以動態地新增內容;
- 基於靜態哈夫曼碼錶的哈夫曼編碼(Huffman Coding);
在HTTP頭裡,有些key:value是固定,例如:
:method: GET
:scheme: http
在編碼時,它們直接用一個index編號代替,例如:method:GET是2,這些在一個靜態表定義。靜態表的定義如下,總共61個Header Name,點選URL https://tools.ietf.org/html/rfc7541#appendix-A檢視所有靜態表的定義。
Index | Header Name | Header Value |
---|---|---|
1 | :authority | |
2 | :method | GET |
3 | :method | POST |
4 | :path | / |
5 | :path | /index.html |
6 | :scheme | http |
7 | :scheme | https |
8 | :status | 200 |
... | ... | ... |
32 | cookie | |
... | ... | ... |
60 | via | |
61 | www-authenticate | |
使用靜態表、動態表、以及Huffman編碼可以極大地提升壓縮效果。對於靜態表裡的欄位,原來需要N個字元表示的,現在只需要一個索引即可,對於靜態、動態表中不存在的內容,還可以使用哈夫曼編碼來減小體積。HTTP/2 標準裡也給出了一份詳細的靜態哈夫曼碼錶(https://tools.ietf.org/html/rfc7541#appendix-B),它們需要內建在客戶端和服務端之中。
關於HPack的演算法和實現,後面專門抽一篇文章來寫。
HTTP/2 ALPN
HTTP/2協議裡有個negotiation的機制,讓客戶端和伺服器選擇使用HTTP 1.1還是2.0,這個是由ALPN來實現,關於ALPN,可以參看
ALPN(Transport Layer Security (TLS) Application-Layer Protocol Negotiation Extension,https://tools.ietf.org/html/rfc7301。
下面是抓包截圖,在TLS裡的Client Hello的包裡,我們可以看到ALPN裡由H2和HTTP/1.1,這就是說客戶端支援HTTP2以及HTTP 1.1.
當Server收到後,會識別Client發過來的協議列表,如果不認識就忽略掉。如果認識多個,則選擇一個最合適的協議釋出給Client。也是在Server Hello裡的ALPN返回,見下圖。
HTTP/2 Server Push機制
Server Push是HTTP 2最重要的一個特性。
在HTTP 1.1裡,在同一個 TCP 連線裡面,上一個迴應(response)傳送完了,伺服器才能傳送下一個,但在HTTP/2裡,可以將多個迴應一起傳送。
下圖是PUSH模式,當請求一個HTML時,如果HTML裡有CSS檔案,server會一併推給client,而不像在HTTP 1.1下,還需要再發一個CSS的請求。
根據上圖,從理論上PUSH模式下效能會好很多。
舉個例子解釋一下。下面是一個簡單的HTML頁面,假說是index.html 。
<html> <head> <link rel="stylesheet" href="style.css"> </head> <body> <p>This is a sample to illustrate how HTTP/2 works</p> <img src="example.png"> </body> </html>
這裡有三個檔案需要處理:該HTML頁面、CSS檔案style.css以及圖片example.png。在HTTP 1.1裡為了處理這三個檔案,Client需要發三個請求給Server。
首先,傳送一個請求index.html,
GET /index.html HTTP/1.1
Client解析該HTML檔案,繼而知道有2個style.css和example.png資原始檔下載。
Client繼續傳送2個請求下載他們。
GET /style.css HTTP/1.1
以及
GET /example.png HTTP/1.1
一般為了解決這兩個問題,像CSS檔案,可以把CSS code直接放在HTML裡,也可以把example.png轉化為base64 code嵌入在HTML裡,以上只是把外部資原始檔合併到HTML裡。
除了上述方法,還有一個優化的方法,就是Preload(預載入),可以參看這裡,https://w3c.github.io/preload/。
所以我們可以把HTML程式碼改成如下:
<link rel="preload" href="/styles.css" as="style"> <link rel="preload" href="/example.png" as="image">
那Preload是什麼意思呢?就是說下載前一個頁面時,可以把相關的資原始檔預先載入好,這樣感覺起來會快一些。但是有一個關鍵問題需要注意,即便是預載入的情況下,也不能減少HTTP請求次數。
針對上面的問題,我們引出伺服器推送(server push)。根據上面的圖,我們可以看出,Server還沒有收到Client的請求,就把各種資源推送給Client。
拿上面例子繼續舉例,當Client只請求index.html
,但是Server把index.html
、style.css
、example.png
全部傳送給瀏覽器。這樣只需要一輪 HTTP 通訊,Client就得到了全部資源。
HTTP/2的支援
現在主流的軟體都支援HTTP/2.
瀏覽器
基本上大部分瀏覽器在2015年底都支援HTTP/2了,包括Chrome、Opera、Firefox、IE 11、Safari,Edge。
在Chrome上,可以下載外掛HTTP Indicator,判斷訪問的網站是否支援HTTP/2.
也可以開啟Chrome的開發者工具,開啟Network tab,可以看到Protocol為h2的就是HTTP/2請求。如果Initiator為push的,說明開啟了Server Push模式。
常用Server軟體:
- Apache HTTPd,從版本2.4.12開始支援,通過模組mod_h2來支撐。
- Apache Tomcat,從版本8.5開始支援。
- Jetty從9.3開始支援。
- Netty從4.1開始。
- IIS在Win10和WIndows Server 2016支援。
- Ngnix從1.9.5開始支援HTTP2,但Server Push功能則在1.13.9才開始。
硬體:
- Ctrix NetScaler從11.x開始支援
- F5 BIG-IP從11.6開始。
CDN/Cloud:
- Akamai
- AWS
- Azure
- Aliyun
- Tecent Cloud
快取問題
如果開啟了Server Push模式,我們很容易意識到一個問題,那就是快取問題。Server見到HTML頁面就把外部資源push給Client,如果沒有快取,其實很浪費。為了解決這個問題,可以在第一次請求時push,後面的請求都不push了。
伺服器推送有一個很麻煩的問題。所要推送的資原始檔,如果瀏覽器已經有快取,推送就是浪費頻寬。即使推送的檔案版本更新,瀏覽器也會優先使用本地快取。下面是 Nginx 官方給出的示例,根據 Cookie 判斷是否為第一次訪問(https://www.nginx.com/blog/nginx-1-13-9-http2-server-push/)。
server { listen 443 ssl http2 default_server; ssl_certificate ssl/certificate.pem; ssl_certificate_key ssl/key.pem; root /var/www/html; http2_push_preload on; location = /demo.html { add_header Set-Cookie "session=1"; add_header Link $resources; } } map $http_cookie $resources { "~*session=1" ""; default "</style.css>; as=style; rel=preload, </image1.jpg>; as=image; rel=preload, </image2.jpg>; as=image; rel=preload";
HTTP/2的效能
有人專門做過測試,https://www.smashingmagazine.com/2017/04/guide-http2-server-push/#measuring-server-push-performance,借用該文的一張圖片,
可以看出,啟用HTTP/2後效能並未大幅度提升,所以在使用HTTP/2還是謹慎一些,如果使用不當,反而會使效能下降。
另外,Ngnix專門撰文描述7個提高HTTP/2的技巧https://www.nginx.com/blog/7-tips-for-faster-http2-performance/ 。
參考文章:
- https://en.wikipedia.org/wiki/HTTP/2
- https://tools.ietf.org/html/rfc7301
- https://tools.ietf.org/html/rfc7541 (HPack)
- http://www.ruanyifeng.com/blog/2018/03/http2_server_push.html
- https://www.nginx.com/blog/nginx-1-13-9-http2-server-push/
- https://www.smashingmagazine.com/2017/04/guide-http2-server-push/#measuring-server-push-performance
- https://www.nginx.com/blog/7-tips-for-faster-http2-performance/
- https://w3c.github.io/preload/
- http://velocityconf.com/devops-web-performance-2015/public/schedule/detail/42385