HTTP/2筆記之流和多路復用
零。前言
本部分將講解HTTP/2協議中對流的定義和使用,其實就是在說HTTP/2是若何做到多路復用的。
一。流和多路復用的關系
1. 流的概念
流(Stream),服務器和客戶端在HTTP/2連接內用于交換幀數據的獨立雙向序列,邏輯上可看做一個較為完整的交互處理單元,即表達一次完整的資源請求-響應數據交換流程;一個業務處理單元,在一個流內進行處理完畢,這個流生命周期完結。
特點如下:
- 一個HTTP/2連接可同時保持多個打開的流,任一端點交換幀
- 流可被客戶端或服務器單獨或共享創建和使用
- 流可被任一端關閉
- 在流內發送和接收數據都要按照順序
- 流的標識符自然數表示,1~2^31-1區間,有創建流的終端分配
- 流與流之間邏輯上是并行、獨立存在
2. 多路復用
流的概念提出是為了實現多路復用,在單個連接上實現同時進行多個業務單元數據的傳輸。邏輯圖如下:
實際傳輸可能是這樣的:
只看到幀(Frame),沒有流(Stream)嘛。
需要抽象化一些,就好理解了:
- 每一個幀可看做是一個學生,流可以認為是組(流標識符為幀的屬性值),一個班級(一個連接)內學生被分為若干個小組,每一個小組分配不同的具體任務。
- HTTP/1.* 一次請求-響應,建立一個連接,用完關閉;每一個小組任務都需要建立一個班級,多個小組任務多個班級,1:1比例
- HTTP/1.1 Pipeling解決方式為,若干個小組任務排隊串行化單線程處理,后面小組任務等待前面小組任務完成才能獲得執行機會,一旦有任務處理超時等,后續任務只能被阻塞,毫無辦法,也就是人們常說的線頭阻塞
- HTTP/2多個小組任務可同時并行(嚴格意義上是并發)在班級內執行。一旦某個小組任務耗時嚴重,但不會影響到其它小組任務正常執行
- 針對一個班級資源維護要比多個班級資源維護經濟多了,這也是多路復用出現的原因
這樣簡單梳理,就有些小清晰了。
3. 流的組成
流的概念提出,就是為了實現多路復用。影響因素:
- 流的優先級(priority)屬性建議終端(客戶端+服務器端)需要按照優先級值進行資源合理分配,優先級高的需要首先處理,優先級低的可以稍微排排隊,這樣的機制可保證重要數據優先處理。
- 流的并發數(或者說同一時間存在的流的個數)初始環境下不少于100個
- 流量控制閥協調網絡帶寬資源利用,由接收端提出發送端遵守其規則
- 流具有完整的生命周期,從創建到最終關閉,經歷不同階段
流總體組成如下:
搞清楚了流和多路復用之間關系,下面稍微深入一點,學習流的一些細節。
二。流的屬性
1. 流狀態/生命周期
幀的行為以及END_STREAM標志位都會對流的狀態的產生變化。因為流由各個端獨立創建,沒有協商,消極后果就是(兩端無法匹配的流的狀態)導致發送完畢RST_STREAM幀之后“關閉”狀態受限,因為幀的傳輸和接收需要一點時間。
幀的狀態列表:
- idle,所有流的開始狀態值
- 發送/接收HEADERS幀,進入open狀態
- PUSH_PROMISE幀只能在已有流上發送,導致創建的本地推送流處于"resereved(local)"狀態
- 在已有流上接收PUSH_PORMISE幀,導致本地預留一個流處于"resereved(remote)"狀態
- HEADERS/PUSH_PROMISE幀以及后面的零個或多個CONTINUATION幀,只要攜帶有END_STREAM標志位,流狀態將進入"half closed"狀態
- 只能接收HEADERS和PRIORITY,否則報PROTOCOL_ERROR類型連接錯誤
-
reserved,為推送保留一個流稍后使用
- reserved (local),服務器端發送完PUSH_PROMISE幀本地預留的一個用于推送流所處于的狀態
- 只能發送HEADERS、RST_STREAM、PRIORITY幀
- 只能接收RST_STREAM、PRIORITY、WINDOW_UPDATE幀
-
reserved (remote),客戶端接收到PUSH_PROMISE幀,本地預留的一個用于接收推送流所處于的狀態
- 只能發送WINDOW_UPDATE、RST_STREAM、PRIORITY幀
- 只能接收RST_STREAM、PRIORITY、HEADERS幀
不滿足條件,需要報PROTOCOL_ERROR類型連接錯誤
- reserved (local),服務器端發送完PUSH_PROMISE幀本地預留的一個用于推送流所處于的狀態
- open,用于兩端發送幀,需要發送數據的對等端需要遵守流量控制的通告。
- 每一端可以發送包含END_STREAM標志位的幀,導致流進入"half closed"狀態
- 每一端都可以發送RST_STREAM幀,流進入"closed"狀態
-
half closed
- half closed (local),發送包含有END_STREAM標志位幀的一端,流進入本地半關閉狀態
- 不能發送WINDOW_UPDATE,PRIORITY和RST_STREAM幀
- 可以接收到任何類型幀
- 接收者可以忽略WINDOW_UPDATE幀,后續可能會馬上接收到包含有END_STREAM標志位幀
- 接收到優先級PRIORITY幀,可用來變更依賴流的優先級順序,有些小復雜了
- 一旦接收到包含END_STREAM標志位的幀,將進入"closed"狀態
-
half closed (remote),接收到包含有END_STREAM標志位幀的一端,流進入遠程半關閉狀態
- 對流量控制窗口可不用維護
- 只能接收RST_STREAM、PRIORITY、WINDOW_UPDATE幀,否則報STREAM_CLOSED流錯誤
- 終端可以發送任何類型幀,但需要遵守對端的當前流的流量控制限制
- 一旦發送包含END_STREAM標志位的幀,將進入"closed"狀態
一旦接收或發送RST_STREAM幀,流將進入"closed"狀態。
- half closed (local),發送包含有END_STREAM標志位幀的一端,流進入本地半關閉狀態
- closed,流的最終關閉狀態
- 只允許發送PRIORITY幀,對依賴關閉的流進行重排序
- 終端接收RST_STREAM幀之后,只能接收PRIORITY幀,否則報STREAM_CLOSED流錯誤
- 接收的DATA/HEADERS幀包含有END_STREAM標志位,在一個很短的周期內可以接收WINDOW_UPDATE或RST_STREAM幀;超時后需要作為錯誤對待
- 終端必須忽略WINDOW_UPDATE或RST_STREAM幀
- 終端發送RST_STREAM幀之后,必須忽略任何接收到的幀
- 在RST_STREAM幀被發送之后收到的流量受限DATA幀,轉向流量控制窗口連接處理。盡管這些幀可以被忽略,因為他們是在發送端接收到RST_STREAM之前發送的,但發送端會認為這些幀與流量控制窗口不符。
- 終端在發送RST_STREAM之后接收PUSH_PROMISE幀,盡管相關流已被重置,但推送幀也能使流變成“保留”狀態。因此,可用RST_STREAM幀關閉一個不想要的承諾流
要求如下:
- 針對具體狀態中出現沒有允許出現的幀,需要作為協議錯誤(PROTOCOL_ERROR)類型的連接錯誤處理
- 在流的任何狀態下,PRIORITY幀都可以被發送或接收
- 未知幀可以被忽略
2. 流標識符
- 31個字節表示無符號的整數,1~2^31-1
- 客戶端創建的流以奇數表示,服務器端創建流以偶數表示
- 0x0用來表示連接控制信息流,不能夠創建新流
- 通過http/1.1 101 協議切換升級切換到HTTP/2,0x1所指代流處于"half closed(local)",不能用于創建新流
- 新建流的標識符要大于已有流和預留的流的標識符
- 新建流第一次被使用時,低于此標識符的并且處于空閑"idle"狀態的流都會被關閉
- 已使用的流標識符不能被再次使用
- 終端的流標識符若被耗盡的情況下
- 若是客戶端,需要關閉連接,創建新的連接創建新流
- 若是服務器端,需要發送一個GOAWAY幀通知客戶端,強迫其打開一個新連接
3. 流的并發數量
- 每一端都可以發送包含有SETTINGS_MAX_CONCURRENT_STREAMS參數的SETTINGS幀限制對等端流的最大并發量
- 對等端接收之后遵守終端最大并發量限制約定
- 狀態為"open"或"half closed"的流需要計入限制總數
- 保留態"reserved"流不算入限制總數內
- 終端接收到HEADERS幀導致創建的流總數超過限制,需要響應PROTOCOL_ERROR或REFUSED_STREAM錯誤,具體哪一種錯誤,需要根據終端是否可以檢測得到允許自動重復重試
- 終端想降低SETTINGS_MAX_CONCURRENT_STREAMS設置的活動流的上限,若低于當前已經打開流的數值,可以選擇光比溢出的流或者允許流繼續存在直到完成
4. 流的優先級
流的優先級在于允許終端向對端表達所期待的給予具體流更多資源支持的意見的表達,不能保證對端一定會遵守,非強制性需求建議;默認值16。在資源有限時,可以保證基本數據的傳輸。
優先級改變:
- 終端可在新建的流所傳遞HEADERS幀中包含優先級priority屬性
- 可單獨通過PRIORITY幀專門設置流的優先級屬性
5. 流依賴
- 流與流之間存在依賴、被依賴關系。所有流默認依賴流0x0;推送流依賴于傳輸PUSH_PROMISE的關聯流。
- 依賴權重值1~256區間,對于依賴同一父級的子節點,應該根據權重比列進行分配資源。
- 對于依賴同一個父級流的子節點被指定相關權重值,以及可用資源的分配比重。子節點之間順序不固定。
A A / \ ==> /|\ B C B D C
- 一旦設置獨家專屬標志(exclusive flag)將為現有依賴插入一個水平的依賴關系,其父級流只能被插入的新流所依賴。比如流D設置專屬標志并依賴于流A:
A A | / \ ==> D B C / \ B C
- 流的依賴樹形模型,底層的流只能等到上層流被關閉或無法正常運轉/失效時,才會被分配到資源
- 流無法依賴自身,否則為PROTOCOL_ERROR流錯誤
- 在流依賴樹形模型中,父節點優先級,以及專屬依賴流的加入等,都會導致已有優先級重排序
? ? ? ? | / \ | | A D A D D / \ / / \ / \ | B C ==> F B C ==> F A OR A / \ | / \ /|\ D E E B C B C F | | | F E E (intermediate) (non-exclusive) (exclusive)
6. 流優先級狀態管理
- 流的依賴樹形模型,任一節點被移除,都需要重建優先級順序,重新分配資源
- 終端建議在流關閉一段時間內保留優先級信息,減少潛在的指派錯誤
- 處于"idle"狀態流可被指派默認優先級16,這時可以變成其它流的父節點,可以指派新的優先級值
- 終端持有的流優先級信息不受SETTINGS_MAX_CONCURRENT_STREAMS限制,但可能會造成終端狀態維護負擔,其數量可以被限制不多于SETTINGS_MAX_CONCURRENT_STREAMS所定義數量
- 優先級狀態信息的維持在負載較高時可以被丟棄,以減少資源占用。
- 終端若有能力保留足夠狀態,在接收到PRIORITY幀目的修改已被關閉流的優先級時,可以為其子節點重建優先級順序
7. 流量控制
多路復用會引入資源競爭,流量控制可以保證流之間不會嚴重影響到彼此。流量控制通過使用WINDOW_UPDATE幀實現,可作用于單個流以及整個的連接。一些原則如下:
- 逐跳,具有方向性
- 不能夠被禁止
- 初始窗口值為65535字節,針對單個流,以及整個連接都有效
- 基于WINDOW_UPDATE幀傳輸實現,接收端通告對端準備在流/連接上接收的字節數
- 接收端完全控制權限,接受端可通告針對流/連接的窗口值,發送者需要遵守
- 目前只有DATA幀可被流量控制,僅針對其有效負載計算;超出窗口值,其負載可以為空
需要注意事項:
- 流量控制是為解決線頭阻塞問題,同時在資源約束情況下保護一些操作順利進行,針對單個連接,某個流可能被阻塞或處理緩慢,但同時不會影響到其它流上正在傳輸的數據
- 雖然流量控制可以用來限制一個對等端消耗的內存,但若在不知道網絡帶寬延遲乘積的情況下可能未必能夠充分利用好網絡資源
- 流量控制機制很復雜,需要考慮大量的細節,實現很困難
三。小結
HTTP/2規范中所定義的流概念、屬性很復雜,在請求量很大以及應對海量并發的情況下,整個連接的流量控制+單個流的流量控制+流的狀態+流優先級屬性+優先級的狀態+流依賴樹形模型等一系列新特性,可能會造成:
- 服務器端/客戶端單個連接內存占用過高,維護一個長連接的成本比以往多了若干倍
- 流量控制是一個復雜功能,實現不好會導致一端流量窗口值已被耗盡,需要等待客戶端發送新的流控窗口值,若有熱數據進行發送,需要等待成本,無形中增加了額外的交互步驟
- 流依賴和優先級重排序等,無形中增加了程序的復雜度,處理不好觸發潛在BUG
- 為了性能和內存考慮,很多知名應用不見得有動力實現全部特性,流的一些高級特性畢竟有些過于理想化,諸如當前實現列表:https://github.com/http2/http2-spec/wiki/Implementations,可以看出一二
- 實際非瀏覽器環境,諸如HTTP API等,實際上僅需要部分關鍵特性,這屬于情理之中的選擇
- 凡是狀態皆需要維護,無論橫向還是縱向的擴展都需要倍加注意;無狀態才是最有利于擴展
posted on 2015-03-19 10:15 nieyong 閱讀(19734) 評論(0) 編輯 收藏 所屬分類: HTTP