gRPC 是基于 HTTP/2 協(xié)議的蛮放,要深刻理解 gRPC饱亮,理解下 HTTP/2 是必要的壶栋。
演進
http2.0的前世是http1.0和http1.1這兩兄弟必盖。雖然之前僅僅只有兩個版本沫屡,但這兩個版本所包含的協(xié)議規(guī)范之龐大饵隙,足以讓任何一個有經(jīng)驗的工程師為之頭疼。http1.0誕生于1996年沮脖,協(xié)議文檔足足60頁金矛。之后第三年,http1.1也隨之出生勺届,協(xié)議文檔膨脹到了176頁驶俊。 不過和我們手機端app升級不同的是,網(wǎng)絡(luò)協(xié)議新版本并不會馬上取代舊版本免姿。實際上饼酿,1.0和1.1在之后很長的一段時間內(nèi)一直并存,這是由于網(wǎng)絡(luò)基礎(chǔ)設(shè)施更新緩慢所決定的养泡。今天的http2.0也是一樣嗜湃,新版協(xié)議再好也需要業(yè)界的產(chǎn)品錘煉,需要基礎(chǔ)設(shè)施逐年累月的升級換代才能普及澜掩。
HTTP站在TCP之上理解http協(xié)議之前一定要對TCP有一定基礎(chǔ)的了解购披。HTTP是建立在TCP協(xié)議之上,TCP協(xié)議作為傳輸層協(xié)議其實離應(yīng)用層并不遠肩榕。HTTP協(xié)議的瓶頸及其優(yōu)化技巧都是基于TCP協(xié)議本身的特性刚陡。比如TCP建立連接時三次握手有1.5個RTT(round-trip time)的延遲,為了避免每次請求的都經(jīng)歷握手帶來的延遲株汉,應(yīng)用層會選擇不同策略的http長鏈接方案筐乳。又比如TCP在建立連接的初期有慢啟動(slow start)的特性,所以連接的重用總是比新建連接性能要好乔妈。
http誕生之初主要是應(yīng)用于web端內(nèi)容獲取蝙云,那時候內(nèi)容還不像現(xiàn)在這樣豐富,排版也沒那么精美路召,用戶交互的場景幾乎沒有勃刨。對于這種簡單的獲取網(wǎng)頁內(nèi)容的場景波材,http表現(xiàn)得還算不錯。但隨著互聯(lián)網(wǎng)的發(fā)展和web2.0的誕生身隐,更多的內(nèi)容開始被展示(更多的圖片文件)廷区,排版變得更精美(更多的css),更復(fù)雜的交互也被引入(更多的js)贾铝。用戶打開一個網(wǎng)站首頁所加載的數(shù)據(jù)總量和請求的個數(shù)也在不斷增加隙轻。 今天絕大部分的門戶網(wǎng)站首頁大小都會超過2M,請求數(shù)量可以多達100個垢揩。另一個廣泛的應(yīng)用是在移動互聯(lián)網(wǎng)的客戶端app玖绿,不同性質(zhì)的app對http的使用差異很大。對于電商類app水孩,加載首頁的請求也可能多達10多個镰矿。對于微信這類IM,http請求可能僅限于語音和圖片文件的下載俘种,請求出現(xiàn)的頻率并不算高秤标。
HTTP/1.x存在的問題
HTTP 協(xié)議可以算是現(xiàn)階段 Web 上面最通用的協(xié)議了,在之前很長一段時間宙刘,很多應(yīng)用都是基于 HTTP/1.x 協(xié)議苍姜,HTTP/1.x 協(xié)議是一個文本協(xié)議,可讀性非常好悬包,但其實并不高效衙猪。
Parser
如果要解析一個完整的 HTTP 請求,首先我們需要能正確的讀出 HTTP header布近。HTTP header 各個 fields 使用 \r\n 分隔垫释,然后跟 body 之間使用 \r\n\r\n 分隔。解析完 header 之后撑瞧,我們才能從 header 里面的 content-length 拿到 body 的 size棵譬,從而讀取 body。
這套解析流程其實并不高效预伺,因為我們需要讀取多次订咸,才能將一個完整的 HTTP 請求給解析出來。雖然在代碼實現(xiàn)上面酬诀,有很多優(yōu)化方式脏嚷,譬如:
- 一次將一大塊數(shù)據(jù)讀取到 buffer 里面避免多次 IO read
- 讀取的時候直接匹配 \r\n 的方式流式解析
但上面的方式對于高性能服務(wù)來說,終歸還是會有開銷瞒御,其實最主要的問題在于父叙,HTTP/1.x 的協(xié)議是 文本協(xié)議,是給人看的,對機器不友好高每,如果要對機器友好屿岂,二進制協(xié)議才是更好的選擇。
Request/Response
HTTP/1.x 另一個問題就在于它的交互模式鲸匿,一個連接每次只能一問一答,也就是client 發(fā)送了 request 之后阻肩,必須等到 response带欢,才能繼續(xù)發(fā)送下一次請求。
這套機制是非常簡單烤惊,但會造成網(wǎng)絡(luò)連接利用率不高乔煞。如果需要同時進行大量的交互庄吼,client 需要跟 server 建立多條連接留夜,但連接的建立也是有開銷的劝赔,所以為了性能悯恍,通常這些連接都是長連接一直彪饶活的叹话,雖然對于 server 來說同時處理百萬連接也沒啥太大的挑戰(zhàn)叠国,但終歸效率不高驶忌。
每個 TCP 連接同時只能處理一個請求 - 響應(yīng)擂仍,瀏覽器按 FIFO 原則處理請求囤屹,如果上一個響應(yīng)沒返回,后續(xù)請求 - 響應(yīng)都會受阻逢渔。為了解決此問題肋坚,出現(xiàn)了 管線化 - pipelining 技術(shù),但是管線化存在諸多問題肃廓,比如第一個響應(yīng)慢還是會阻塞后續(xù)響應(yīng)智厌、服務(wù)器為了按序返回相應(yīng)需要緩存多個響應(yīng)占用更多資源、瀏覽器中途斷連重試服務(wù)器可能得重新處理多個請求盲赊、還有必須客戶端 - 代理 - 服務(wù)器都支持管線化
push
用 HTTP/1.x 做過推送的同學(xué)铣鹏,大概就知道有多么的痛苦,因為 HTTP/1.x 并沒有推送機制角钩。所以通常兩種做法:
- Long polling 方式吝沫,也就是直接給 server 掛一個連接,等待一段時間(譬如 1 分鐘)递礼,如果 server 有返回或者超時惨险,則再次重新 poll。
- Web-socket脊髓,通過 upgrade 機制顯式的將這條 HTTP 連接變成裸的 TCP辫愉,進行雙向交互。
無壓縮
Header 內(nèi)容将硝,而且每次請求 Header 不會變化太多恭朗,沒有相應(yīng)的壓縮傳輸優(yōu)化方案
明文傳輸不安全
HTTP/2
雖然 HTTP/1.x 協(xié)議可能仍然是當(dāng)今互聯(lián)網(wǎng)運用最廣泛的協(xié)議屏镊,但隨著 Web 服務(wù)規(guī)模的不斷擴大,HTTP/1.x 越發(fā)顯得捉緊見拙痰腮,我們急需另一套更好的協(xié)議來構(gòu)建我們的服務(wù),于是就有了 HTTP/2而芥。
HTTP/2 是一個二進制協(xié)議,這也就意味著它的可讀性幾乎為 0膀值,但幸運的是棍丐,我們還是有很多工具,譬如 Wireshark沧踏, 能夠?qū)⑵浣馕龀鰜怼?/p>
在了解 HTTP/2 之前歌逢,需要知道一些通用術(shù)語:
- Stream: 一個雙向流,一條連接可以有多個 streams翘狱。
- Message: 也就是邏輯上面的 request秘案,response。
- Frame::數(shù)據(jù)傳輸?shù)淖钚挝涣市佟C總€ Frame 都屬于一個特定的 stream 或者整個連接阱高。一個 message 可能有多個 frame 組成。
Frame Format
Frame 是 HTTP/2 里面最小的數(shù)據(jù)傳輸單位历等,一個 Frame 定義如下:
+-----------------------------------------------+
| Length (24) |
+---------------+---------------+---------------+
| Type (8) | Flags (8) |
+-+-------------+---------------+-------------------------------+
|R| Stream Identifier (31) |
+=+=============================================================+
| Frame Payload (0...) ...
+---------------------------------------------------------------+
- Length:也就是 Frame 的長度讨惩,默認最大長度是 16KB,如果要發(fā)送更大的 Frame寒屯,需要顯式的設(shè)置 max frame size荐捻。
- Type:Frame 的類型,譬如有 DATA寡夹,HEADERS处面,PRIORITY 等。
- Flag 和 R:保留位菩掏,可以先不管魂角。
- Stream Identifier:標(biāo)識所屬的 stream,如果為 0智绸,則表示這個 frame 屬于整條連接野揪。
- Frame Payload:根據(jù)不同 Type 有不同的格式。
可以看到瞧栗,F(xiàn)rame 的格式定義還是非常的簡單.
HTTP/2 通過 stream 支持了連接的多路復(fù)用斯稳,提高了連接的利用率。Stream 有很多重要特性:
- 一條連接可以包含多個 streams迹恐,多個 streams 發(fā)送的數(shù)據(jù)互相不影響挣惰。
- Stream 可以被 client 和 server 單方面或者共享使用。
- Stream 可以被任意一段關(guān)閉。
- Stream 會確定好發(fā)送 frame 的順序憎茂,另一端會按照接受到的順序來處理
- Stream 用一個唯一 ID 來標(biāo)識珍语。
這里在說一下 Stream ID,如果是 client 創(chuàng)建的 stream竖幔,ID 就是奇數(shù)板乙,如果是 server 創(chuàng)建的,ID 就是偶數(shù)赏枚。ID 0x00 和 0x01 都有特定的使用場景亡驰。Stream ID 不可能被重復(fù)使用,如果一條連接上面 ID 分配完了饿幅,client 會新建一條連接。而 server 則會給 client 發(fā)送一個 GOAWAY frame 強制讓 client 新建一條連接戒职。
Priority
因為一條連接允許多個 streams 在上面發(fā)送 frame栗恩,那么在一些場景下面,我們還是希望 stream 有優(yōu)先級洪燥,方便對端為不同的請求分配不同的資源磕秤。譬如對于一個 Web 站點來說,優(yōu)先加載重要的資源捧韵,而對于一些不那么重要的圖片啥的市咆,則使用低的優(yōu)先級。
我們還可以設(shè)置 Stream Dependencies再来,形成一棵 streams priority tree蒙兰。假設(shè) Stream A 是 parent,Stream B 和 C 都是它的孩子芒篷,B 的 weight 是 4搜变,C 的 weight 是 12,假設(shè)現(xiàn)在 A 能分配到所有的資源针炉,那么后面 B 能分配到的資源只有 C 的 1/3挠他。
Flow Control
HTTP/2 也支持流控,如果 sender 端發(fā)送數(shù)據(jù)太快篡帕,receiver 端可能因為太忙殖侵,或者壓力太大,或者只想給特定的 stream 分配資源镰烧,receiver 端就可能不想處理這些數(shù)據(jù)拢军。譬如,如果 client 給 server 請求了一個視頻拌滋,但這時候用戶暫停觀看了朴沿,client 就可能告訴 server 別在發(fā)送數(shù)據(jù)了。
雖然 TCP 也有 flow control,但它僅僅只對一個連接有效果赌渣。HTTP/2 在一條連接上面會有多個 streams魏铅,有時候,我們僅僅只想對一些 stream 進行控制坚芜,所以 HTTP/2 單獨提供了流控機制览芳。Flow control 有如下特性:
- Flow control 是單向的。Receiver 可以選擇給 stream 或者整個連接設(shè)置 window size鸿竖。
- Flow control 是基于信任的沧竟。Receiver 只是會給 sender 建議它的初始連接和 stream 的 flow control window size。
- Flow control 不可能被禁止掉缚忧。當(dāng) HTTP/2 連接建立起來之后悟泵,client 和 server 會交換 SETTINGS frames,用來設(shè)置 flow control window size闪水。
這里需要注意糕非,HTTP/2 默認的 window size 是 64 KB,這個值在實際中可能偏小球榆。
HPACK
在一個 HTTP 請求里面朽肥,我們通常在 header 上面攜帶很多該請求的元信息,用來描述要傳輸?shù)馁Y源以及它的相關(guān)屬性持钉。在 HTTP/1.x 時代衡招,我們采用純文本協(xié)議,并且使用 \r\n 來分隔每强,如果我們要傳輸?shù)脑獢?shù)據(jù)很多始腾,就會導(dǎo)致 header 非常的龐大. 。另外舀射,多數(shù)時候窘茁,在一條連接上面的多數(shù)請求,其實 header 差不了多少脆烟,譬如我們第一個請求可能 GET /a.txt山林,后面緊接著是 GET /b.txt,兩個請求唯一的區(qū)別就是 URL path 不一樣邢羔,但我們?nèi)匀灰獙⑵渌械?fields 完全發(fā)一遍驼抹。
HTTP/2 為了結(jié)果這個問題,使用了 HPACK. HPACK 提供了一個靜態(tài)和動態(tài)的 table拜鹤,靜態(tài) table 定義了通用的 HTTP header fields框冀,譬如 method,path 等敏簿。發(fā)送請求的時候明也,只要指定 field 在靜態(tài) table 里面的索引宣虾,雙方就知道要發(fā)送的 field 是什么了。 對于動態(tài) table温数,初始化為空绣硝,如果兩邊交互之后,發(fā)現(xiàn)有新的 field撑刺,就添加到動態(tài) table 上面鹉胖,這樣后面的請求就可以跟靜態(tài) table 一樣,只需要帶上相關(guān)的 index 就可以了够傍。同時甫菠,為了減少數(shù)據(jù)傳輸?shù)拇笮。褂?Huffman 進行編碼冕屯。
對比
http2.0的格式定義更接近tcp層的方式寂诱,這張二機制的方式十分高效且精簡。length定義了整個frame的開始到結(jié)束安聘,type定義frame的類型(一共10種)刹衫,flags用bit位定義一些重要的參數(shù),stream id用作流控制搞挣,剩下的payload就是request的正文了。
雖然看上去協(xié)議的格式和http1.x完全不同了音羞,實際上http2.0并沒有改變http1.x的語義囱桨,只是把原來http1.x的header和body部分用frame重新封裝了一層而已.
連接共享
http2.0要解決的一大難題就是多路復(fù)用(MultiPlexing),即連接共享。
在一個 TCP 連接上嗅绰,我們可以向?qū)Ψ讲粩喟l(fā)送幀舍肠,每幀的 stream identifier 的標(biāo)明這一幀屬于哪個流,然后在對方接收時窘面,根據(jù) stream identifier 拼接每個流的所有幀組成一整塊數(shù)據(jù)翠语。
把 HTTP/1.1 每個請求都當(dāng)作一個流,那么多個請求變成多個流财边,請求響應(yīng)數(shù)據(jù)分成多個幀肌括,不同流中的幀交錯地發(fā)送給對方,這就是 HTTP/2 中的多路復(fù)用酣难。
上面協(xié)議解析中提到的stream id就是用作連接共享機制的.一個request對應(yīng)一個stream并分配一個id谍夭,這樣一個連接上可以有多個stream,每個stream的frame可以隨機的混雜在一起憨募,接收方可以根據(jù)stream id將frame再歸屬到各自不同的request里面紧索。多路復(fù)用(MultiPlexing),這個功能相當(dāng)于是長連接的增強菜谣,每個request可以隨機的混雜在一起珠漂,接收方可以根據(jù)request的id將request再歸屬到各自不同的服務(wù)端請求里面晚缩。另外多路復(fù)用中,也支持了流的優(yōu)先級(Stream dependencies)媳危,允許客戶端告訴server哪些內(nèi)容是更優(yōu)先級的資源荞彼,可以優(yōu)先傳輸。
header壓縮
前面提到過http1.x的header由于cookie和user agent很容易膨脹济舆,而且每次都要重復(fù)發(fā)送卿泽。http2.0使用encoder來減少需要傳輸?shù)膆eader大小,通訊雙方各自cache一份header fields表滋觉,既避免了重復(fù)header的傳輸签夭,又減小了需要傳輸?shù)拇笮 8咝У膲嚎s算法可以很大的壓縮header椎侠,減少發(fā)送包的數(shù)量從而降低延遲第租。
重置連接表現(xiàn)更好
很多app客戶端都有取消圖片下載的功能場景,對于http1.x來說我纪,是通過設(shè)置tcp segment里的reset flag來通知對端關(guān)閉連接的慎宾。這種方式會直接斷開連接,下次再發(fā)請求就必須重新建立連接浅悉。http2.0引入RST_STREAM類型的frame趟据,可以在不斷開連接的前提下取消某個request的stream,表現(xiàn)更好术健。
Server Push
Server Push的功能前面已經(jīng)提到過汹碱,http2.0能通過push的方式將客戶端需要的內(nèi)容預(yù)先推送過去,所以也叫“cache push”荞估。另外有一點值得注意的是咳促,客戶端如果退出某個業(yè)務(wù)場景,出于流量或者其它因素需要取消server push勘伺,也可以通過發(fā)送RST_STREAM類型的frame來做到跪腹。
流量控制
每個 http2 流都擁有自己的公示的流量窗口,它可以限制另一端發(fā)送數(shù)據(jù)飞醉。對于每個流來說冲茸,兩端都必須告訴對方自己還有足夠的空間來處理新的數(shù)據(jù),而在該窗口被擴大前冒掌,另一端只被允許發(fā)送這么多數(shù)據(jù)噪裕。
流 - Stream
流只是一個邏輯上的概念,代表 HTTP/2 連接中在客戶端和服務(wù)器之間交換的獨立雙向幀序列股毫,每個幀的 Stream Identifier 字段指明了它屬于哪個流膳音。
流有以下特性:
- 單個 h2 連接可以包含多個并發(fā)的流,兩端之間可以交叉發(fā)送不同流的幀
- 流可以由客戶端或服務(wù)器來單方面地建立和使用铃诬,或者共享
- 流可以由任一方關(guān)閉
- 幀在流上發(fā)送的順序非常重要祭陷,最后接收方會把相同 Stream Identifier (同一個流) 的幀重新組裝成完整消息報文
二進制分幀層
HTTP/2 所有性能增強的核心在于新的二進制分幀層苍凛,它定義了如何封裝 HTTP 消息并在客戶端與服務(wù)器之間傳輸。
這里所謂的“層”兵志,指的是位于套接字接口與應(yīng)用可見的高級 HTTP API 之間一個經(jīng)過優(yōu)化的新編碼機制:HTTP 的語義(包括各種動詞醇蝴、方法、標(biāo)頭)都不受影響想罕,不同的是傳輸期間對它們的編碼方式變了悠栓。 HTTP/1.x 協(xié)議以換行符作為純文本的分隔符,而 HTTP/2 將所有傳輸?shù)男畔⒎指顬楦〉南⒑蛶醇郏⒉捎枚M制格式對它們編碼惭适。
這樣一來,客戶端和服務(wù)器為了相互理解楼镐,都必須使用新的二進制編碼機制:HTTP/1.x 客戶端無法理解只支持 HTTP/2 的服務(wù)器癞志,反之亦然。 不過不要緊框产,現(xiàn)有的應(yīng)用不必擔(dān)心這些變化凄杯,因為客戶端和服務(wù)器會替我們完成必要的分幀工作。
數(shù)據(jù)流秉宿、消息和幀
新的二進制分幀機制改變了客戶端與服務(wù)器之間交換數(shù)據(jù)的方式戒突。 為了說明這個過程,我們需要了解 HTTP/2 的三個概念:
- 數(shù)據(jù)流:已建立的連接內(nèi)的雙向字節(jié)流描睦,可以承載一條或多條消息妖谴。
- 消息:與邏輯請求或響應(yīng)消息對應(yīng)的完整的一系列幀。
- 幀:HTTP/2 通信的最小單位酌摇,每個幀都包含幀頭,至少也會標(biāo)識出當(dāng)前幀所屬的數(shù)據(jù)流嗡载。
這些概念的關(guān)系總結(jié)如下:
- 所有通信都在一個 TCP 連接上完成窑多,此連接可以承載任意數(shù)量的雙向數(shù)據(jù)流。
- 每個數(shù)據(jù)流都有一個唯一的標(biāo)識符和可選的優(yōu)先級信息洼滚,用于承載雙向消息
- 每條消息都是一條邏輯 HTTP 消息(例如請求或響應(yīng))埂息,包含一個或多個幀。
-
幀是最小的通信單位遥巴,承載著特定類型的數(shù)據(jù)千康,例如 HTTP 標(biāo)頭、消息負載等等铲掐。 來自不同數(shù)據(jù)流的幀可以交錯發(fā)送拾弃,然后再根據(jù)每個幀頭的數(shù)據(jù)流標(biāo)識符重新組裝。
簡言之摆霉,HTTP/2 將 HTTP 協(xié)議通信分解為二進制編碼幀的交換豪椿,這些幀對應(yīng)著特定數(shù)據(jù)流中的消息奔坟。所有這些都在一個 TCP 連接內(nèi)復(fù)用。 這是 HTTP/2 協(xié)議所有其他功能和性能優(yōu)化的基礎(chǔ)搭盾。
請求與響應(yīng)復(fù)用
在 HTTP/1.x 中咳秉,如果客戶端要想發(fā)起多個并行請求以提升性能,則必須使用多個 TCP 連接鸯隅。
HTTP/2 中新的二進制分幀層突破了這些限制澜建,實現(xiàn)了完整的請求和響應(yīng)復(fù)用:客戶端和服務(wù)器可以將 HTTP 消息分解為互不依賴的幀,然后交錯發(fā)送蝌以,最后再在另一端把它們重新組裝起來炕舵。
快照捕捉了同一個連接內(nèi)并行的多個數(shù)據(jù)流。 客戶端正在向服務(wù)器傳輸一個 DATA 幀(數(shù)據(jù)流 5)饼灿,與此同時幕侠,服務(wù)器正向客戶端交錯發(fā)送數(shù)據(jù)流 1 和數(shù)據(jù)流 3 的一系列幀。因此碍彭,一個連接上同時有三個并行數(shù)據(jù)流晤硕。
將 HTTP 消息分解為獨立的幀,交錯發(fā)送庇忌,然后在另一端重新組裝是 HTTP 2 最重要的一項增強舞箍,事實上,這個機制會在整個網(wǎng)絡(luò)技術(shù)棧中引發(fā)一系列連鎖反應(yīng)皆疹,從而帶來巨大的性能提升疏橄,讓我們可以
- 并行交錯地發(fā)送多個請求,請求之間互不影響略就。
- 并行交錯地發(fā)送多個響應(yīng)捎迫,響應(yīng)之間互不干擾。
- 使用一個連接并行發(fā)送多個請求和響應(yīng)
數(shù)據(jù)流優(yōu)先級
將 HTTP 消息分解為很多獨立的幀之后表牢,我們就可以復(fù)用多個數(shù)據(jù)流中的幀窄绒,客戶端和服務(wù)器交錯發(fā)送和傳輸這些幀的順序就成為關(guān)鍵的性能決定因素。 為了做到這一點崔兴,HTTP/2 標(biāo)準(zhǔn)允許每個數(shù)據(jù)流都有一個關(guān)聯(lián)的權(quán)重和依賴關(guān)系:
- 可以向每個數(shù)據(jù)流分配一個介于 1 至 256 之間的整數(shù)彰导。
- 每個數(shù)據(jù)流與其他數(shù)據(jù)流之間可以存在顯式依賴關(guān)系。
數(shù)據(jù)流依賴關(guān)系和權(quán)重的組合讓客戶端可以構(gòu)建和傳遞“優(yōu)先級樹”敲茄,表明它傾向于如何接收響應(yīng)位谋。 反過來,服務(wù)器可以使用此信息通過控制 CPU堰燎、內(nèi)存和其他資源的分配設(shè)定數(shù)據(jù)流處理的優(yōu)先級掏父,在資源數(shù)據(jù)可用之后,帶寬分配可以確保將高優(yōu)先級響應(yīng)以最優(yōu)方式傳輸至客戶端秆剪。
HTTP/2 內(nèi)的數(shù)據(jù)流依賴關(guān)系通過將另一個數(shù)據(jù)流的唯一標(biāo)識符作為父項引用進行聲明损同;如果忽略標(biāo)識符翩腐,相應(yīng)數(shù)據(jù)流將依賴于“根數(shù)據(jù)流”。 聲明數(shù)據(jù)流依賴關(guān)系指出膏燃,應(yīng)盡可能先向父數(shù)據(jù)流分配資源茂卦,然后再向其依賴項分配資源。 換句話說组哩,“請先處理和傳輸響應(yīng) D等龙,然后再處理和傳輸響應(yīng) C”。
共享相同父項的數(shù)據(jù)流(即伶贰,同級數(shù)據(jù)流)應(yīng)按其權(quán)重比例分配資源蛛砰。 例如,如果數(shù)據(jù)流 A 的權(quán)重為 12黍衙,其同級數(shù)據(jù)流 B 的權(quán)重為 4泥畅,那么要確定每個數(shù)據(jù)流應(yīng)接收的資源比例,請執(zhí)行以下操作:
將所有權(quán)重求和:4 + 12 = 16
將每個數(shù)據(jù)流權(quán)重除以總權(quán)重:A = 12/16, B = 4/16
因此琅翻,數(shù)據(jù)流 A 應(yīng)獲得四分之三的可用資源位仁,數(shù)據(jù)流 B 應(yīng)獲得四分之一的可用資源;數(shù)據(jù)流 B 獲得的資源是數(shù)據(jù)流 A 所獲資源的三分之一方椎。
我們來看一下上圖中的其他幾個操作示例聂抢。 從左到右依次為:
數(shù)據(jù)流 A 和數(shù)據(jù)流 B 都沒有指定父依賴項,依賴于顯式“根數(shù)據(jù)流”棠众;A 的權(quán)重為 12琳疏,B 的權(quán)重為 4。因此闸拿,根據(jù)比例權(quán)重:數(shù)據(jù)流 B 獲得的資源是 A 所獲資源的三分之一空盼。
數(shù)據(jù)流 D 依賴于根數(shù)據(jù)流;C 依賴于 D新荤。 因此我注,D 應(yīng)先于 C 獲得完整資源分配。 權(quán)重不重要迟隅,因為 C 的依賴關(guān)系擁有更高的優(yōu)先級。
數(shù)據(jù)流 D 應(yīng)先于 C 獲得完整資源分配励七;C 應(yīng)先于 A 和 B 獲得完整資源分配智袭;數(shù)據(jù)流 B 獲得的資源是 A 所獲資源的三分之一。
數(shù)據(jù)流 D 應(yīng)先于 E 和 C 獲得完整資源分配掠抬;E 和 C 應(yīng)先于 A 和 B 獲得相同的資源分配吼野;A 和 B 應(yīng)基于其權(quán)重獲得比例分配。
如上面的示例所示两波,數(shù)據(jù)流依賴關(guān)系和權(quán)重的組合明確表達了資源優(yōu)先級瞳步,這是一種用于提升瀏覽性能的關(guān)鍵功能闷哆,網(wǎng)絡(luò)中擁有多種資源類型,它們的依賴關(guān)系和權(quán)重各不相同单起。 不僅如此抱怔,HTTP/2 協(xié)議還允許客戶端隨時更新這些優(yōu)先級,進一步優(yōu)化了瀏覽器性能嘀倒。 換句話說屈留,我們可以根據(jù)用戶互動和其他信號更改依賴關(guān)系和重新分配權(quán)重。
每個來源一個連接
有了新的分幀機制后测蘑,HTTP/2 不再依賴多個 TCP 連接去并行復(fù)用數(shù)據(jù)流灌危;每個數(shù)據(jù)流都拆分成很多幀,而這些幀可以交錯碳胳,還可以分別設(shè)定優(yōu)先級勇蝙。 因此,所有 HTTP/2 連接都是永久的挨约,而且僅需要每個來源一個連接味混,隨之帶來諸多性能優(yōu)勢。
大多數(shù) HTTP 傳輸都是短暫且急促的烫罩,而 TCP 則針對長時間的批量數(shù)據(jù)傳輸進行了優(yōu)化惜傲。 通過重用相同的連接,HTTP/2 既可以更有效地利用每個 TCP 連接贝攒,也可以顯著降低整體協(xié)議開銷盗誊。 不僅如此,使用更少的連接還可以減少占用的內(nèi)存和處理空間隘弊,也可以縮短完整連接路徑(即哈踱,客戶端、可信中介和源服務(wù)器之間的路徑) 這降低了整體運行成本并提高了網(wǎng)絡(luò)利用率和容量梨熙。 因此开镣,遷移到 HTTP/2 不僅可以減少網(wǎng)絡(luò)延遲,還有助于提高通量和降低運行成本咽扇。
流控制
流控制是一種阻止發(fā)送方向接收方發(fā)送大量數(shù)據(jù)的機制邪财,以免超出后者的需求或處理能力:發(fā)送方可能非常繁忙、處于較高的負載之下质欲,也可能僅僅希望為特定數(shù)據(jù)流分配固定量的資源树埠。 例如,客戶端可能請求了一個具有較高優(yōu)先級的大型視頻流嘶伟,但是用戶已經(jīng)暫停視頻怎憋,客戶端現(xiàn)在希望暫停或限制從服務(wù)器的傳輸,以免提取和緩沖不必要的數(shù)據(jù)绊袋。 再比如毕匀,一個代理服務(wù)器可能具有較快的下游連接和較慢的上游連接,并且也希望調(diào)節(jié)下游連接傳輸數(shù)據(jù)的速度以匹配上游連接的速度來控制其資源利用率癌别;等等皂岔。
上述要求會讓您想到 TCP 流控制嗎?您應(yīng)當(dāng)想到這一點规个;因為問題基本相同(請參閱流控制)凤薛。 不過,由于 HTTP/2 數(shù)據(jù)流在一個 TCP 連接內(nèi)復(fù)用诞仓,TCP 流控制既不夠精細缤苫,也無法提供必要的應(yīng)用級 API 來調(diào)節(jié)各個數(shù)據(jù)流的傳輸。 為了解決這一問題墅拭,HTTP/2 提供了一組簡單的構(gòu)建塊活玲,這些構(gòu)建塊允許客戶端和服務(wù)器實現(xiàn)其自己的數(shù)據(jù)流和連接級流控制:
- 流控制具有方向性。 每個接收方都可以根據(jù)自身需要選擇為每個數(shù)據(jù)流和整個連接設(shè)置任意的窗口大小谍婉。
- 流控制基于信用舒憾。 每個接收方都可以公布其初始連接和數(shù)據(jù)流流控制窗口(以字節(jié)為單位),每當(dāng)發(fā)送方發(fā)出 DATA 幀時都會減小穗熬,在接收方發(fā)出 WINDOW_UPDATE 幀時增大镀迂。
- 流控制無法停用。 建立 HTTP/2 連接后唤蔗,客戶端將與服務(wù)器交換 SETTINGS 幀探遵,這會在兩個方向上設(shè)置流控制窗口。 流控制窗口的默認值設(shè)為 65,535 字節(jié)妓柜,但是接收方可以設(shè)置一個較大的最大窗口大邢浼尽(2^31-1 字節(jié)),并在接收到任意數(shù)據(jù)時通過發(fā)送 WINDOW_UPDATE 幀來維持這一大小棍掐。
HTTP/2 未指定任何特定算法來實現(xiàn)流控制藏雏。 不過,它提供了簡單的構(gòu)建塊并推遲了客戶端和服務(wù)器實現(xiàn)作煌,可以實現(xiàn)自定義策略來調(diào)節(jié)資源使用和分配掘殴,以及實現(xiàn)新傳輸能力。
例如粟誓,應(yīng)用層流控制允許瀏覽器僅提取一部分特定資源奏寨,通過將數(shù)據(jù)流流控制窗口減小為零來暫停提取,稍后再行恢復(fù)努酸。 換句話說,它允許瀏覽器提取圖像預(yù)覽或首次掃描結(jié)果杜恰,進行顯示并允許其他高優(yōu)先級提取繼續(xù)获诈,然后在更關(guān)鍵的資源完成加載后恢復(fù)提取仍源。
服務(wù)器推送
HTTP/2 新增的另一個強大的新功能是,服務(wù)器可以對一個客戶端請求發(fā)送多個響應(yīng)舔涎。 換句話說笼踩,除了對最初請求的響應(yīng)外,服務(wù)器還可以向客戶端推送額外資源(圖 12-5)亡嫌,而無需客戶端明確地請求嚎于。
注:HTTP/2 打破了嚴(yán)格的請求-響應(yīng)語義,支持一對多和服務(wù)器發(fā)起的推送工作流挟冠,在瀏覽器內(nèi)外開啟了全新的互動可能性于购。
為什么在瀏覽器中需要一種此類機制呢?一個典型的網(wǎng)絡(luò)應(yīng)用包含多種資源知染,客戶端需要檢查服務(wù)器提供的文檔才能逐個找到它們肋僧。 那為什么不讓服務(wù)器提前推送這些資源,從而減少額外的延遲時間呢控淡? 服務(wù)器已經(jīng)知道客戶端下一步要請求什么資源嫌吠,這時候服務(wù)器推送即可派上用場。
事實上掺炭,如果您在網(wǎng)頁中內(nèi)聯(lián)過 CSS辫诅、JavaScript,或者通過數(shù)據(jù) URI 內(nèi)聯(lián)過其他資產(chǎn)(請參閱資源內(nèi)聯(lián))涧狮,那么您就已經(jīng)親身體驗過服務(wù)器推送了炕矮。 對于將資源手動內(nèi)聯(lián)到文檔中的過程,我們實際上是在將資源推送給客戶端勋篓,而不是等待客戶端請求吧享。 使用 HTTP/2,我們不僅可以實現(xiàn)相同結(jié)果譬嚣,還會獲得其他性能優(yōu)勢钢颂。 推送資源可以進行以下處理:
由客戶端緩存
在不同頁面之間重用
與其他資源一起復(fù)用
由服務(wù)器設(shè)定優(yōu)先級
被客戶端拒絕
PUSH_PROMISE 101
所有服務(wù)器推送數(shù)據(jù)流都由 PUSH_PROMISE 幀發(fā)起,表明了服務(wù)器向客戶端推送所述資源的意圖拜银,并且需要先于請求推送資源的響應(yīng)數(shù)據(jù)傳輸殊鞭。 這種傳輸順序非常重要:客戶端需要了解服務(wù)器打算推送哪些資源,以免為這些資源創(chuàng)建重復(fù)請求尼桶。 滿足此要求的最簡單策略是先于父響應(yīng)(即操灿,DATA 幀)發(fā)送所有 PUSH_PROMISE 幀,其中包含所承諾資源的 HTTP 標(biāo)頭泵督。
在客戶端接收到 PUSH_PROMISE 幀后趾盐,它可以根據(jù)自身情況選擇拒絕數(shù)據(jù)流(通過 RST_STREAM 幀)。 (例如,如果資源已經(jīng)位于緩存中救鲤,便可能會發(fā)生這種情況久窟。) 這是一個相對于 HTTP/1.x 的重要提升。 相比之下本缠,使用資源內(nèi)聯(lián)(一種受歡迎的 HTTP/1.x“優(yōu)化”)等同于“強制推送”:客戶端無法選擇拒絕斥扛、取消或單獨處理內(nèi)聯(lián)的資源。
使用 HTTP/2丹锹,客戶端仍然完全掌控服務(wù)器推送的使用方式稀颁。 客戶端可以限制并行推送的數(shù)據(jù)流數(shù)量;調(diào)整初始的流控制窗口以控制在數(shù)據(jù)流首次打開時推送的數(shù)據(jù)量楣黍;或完全停用服務(wù)器推送匾灶。 這些優(yōu)先級在 HTTP/2 連接開始時通過 SETTINGS 幀傳輸,可能隨時更新锡凝。
標(biāo)頭壓縮
每個 HTTP 傳輸都承載一組標(biāo)頭粘昨,這些標(biāo)頭說明了傳輸?shù)馁Y源及其屬性。 在 HTTP/1.x 中窜锯,此元數(shù)據(jù)始終以純文本形式骡男,通常會給每個傳輸增加 500–800 字節(jié)的開銷策橘。如果使用 HTTP Cookie凛捏,增加的開銷有時會達到上千字節(jié)箩朴。 (請參閱測量和控制協(xié)議開銷。) 為了減少此開銷和提升性能驾孔,HTTP/2 使用 HPACK 壓縮格式壓縮請求和響應(yīng)標(biāo)頭元數(shù)據(jù)芍秆,這種格式采用兩種簡單但是強大的技術(shù):
- 這種格式支持通過靜態(tài)霍夫曼代碼對傳輸?shù)臉?biāo)頭字段進行編碼,從而減小了各個傳輸?shù)拇笮 ?/li>
- 這種格式要求客戶端和服務(wù)器同時維護和更新一個包含之前見過的標(biāo)頭字段的索引列表(換句話說翠勉,它可以建立一個共享的壓縮上下文)妖啥,此列表隨后會用作參考,對之前傳輸?shù)闹颠M行有效編碼对碌。
利用霍夫曼編碼荆虱,可以在傳輸時對各個值進行壓縮,而利用之前傳輸值的索引列表朽们,我們可以通過傳輸索引值的方式對重復(fù)值進行編碼怀读,索引值可用于有效查詢和重構(gòu)完整的標(biāo)頭鍵值對。
作為一種進一步優(yōu)化方式骑脱,HPACK 壓縮上下文包含一個靜態(tài)表和一個動態(tài)表:靜態(tài)表在規(guī)范中定義菜枷,并提供了一個包含所有連接都可能使用的常用 HTTP 標(biāo)頭字段(例如,有效標(biāo)頭名稱)的列表叁丧;動態(tài)表最初為空啤誊,將根據(jù)在特定連接內(nèi)交換的值進行更新岳瞭。 因此,為之前未見過的值采用靜態(tài) Huffman 編碼蚊锹,并替換每一側(cè)靜態(tài)表或動態(tài)表中已存在值的索引寝优,可以減小每個請求的大小。