如何定位并修复 HttpCore5 中的 HTTP2 流量控制问题
作者:風起
開篇吹一波阿里云性能測試服務?PTS [1] ,PTS 在 2021 年 5 月份已經上線了對 HTTP2 協議的支持(底層依賴 httpclient5),在壓測時會通過與服務端協商的結果來決定使用 HTTP1.1 或者 HTTP2 協議。
背景
寫這篇文章的原因是某天某個客戶找過來,問我們是不是不支持 HTTP2,因為他在 XX 云上購買了 2 個域名,其中一個開啟了 HTTP2,而在 PTS 壓測過程中,支持 HTTP2 的接口總是報錯:
起初懷疑是 HTTP2 支持的問題,通過在本地強制使用 HTTP2 協議,訪問淘寶主頁,發現是沒問題的,懷疑是用戶在 XX 云上的配置問題,但緊接著通過在本地 Postman、curl 以及壓測引擎強制使用 HTTP1.1 協議時都能夠正常訪問該網頁,意識到大概率是 PTS 引擎側的問題。
通過本地 debug,看到是因為請求 URL 時,客戶端窗口大小被調整為大于 2^32 -1 導致的異常。
那正好借這個機會看下這里的窗口大小指的是什么。
HTTP2 流控
提到窗口,就要提到 HTTP2 相比于 HTTP1.1 支持的新特性:流控(Flow Control),其實 HTTP1.1 依賴于傳輸層 TCP 的滑動窗口一樣可以實現流控,那么為什么 HTTP2 要在應用層再實現一個流控呢?原因在于 HTTP2 引入了流和多路復用,通過流控可以達到使多個流協同的效果。
一些流控的基本概念:
為了便于理解,先簡單列一下 HTTP2 幀的類型:
- DATA:攜帶請求或響應中的數據
- HEADERS:用于新建一個流(請求或響應),包含對應的 Headers
- PRIORITY:用于配置流的優先級
- RST_STREAM:強制結束某個流,僅用于某一端取消流,并不適用于正常流的結束
- SETTINGS:H2 建聯的一些配置
- PUSH_PROMISE:服務端推送響應到客戶端
- PING:向遠端發送一條 PING,遠端必須返回該 PING
- GOAWAY:用于某一端將要結束連接
- WINDOW_UPDATE:更新流控窗口大小
- CONTINUATION:如果 headers 過大,單個 HEADERS 幀難以攜帶,通過該幀發送額外的 headers
接下來,我們重點看下流控相關的幀,主要是 SETTING 與 WINDOW_UPDATE,在連接建立時會通過 SETTINGS 幀來調整對方的窗口大小,之后在傳輸過程中,窗口大小會隨著數據的發送逐漸減小,直到收到對方發送的 WINDOW_UPDATE 幀,從而更新窗口大小。SETTINGS 幀主要包含以下內容:
- SETTINGS_HEADER_TABLE_SIZE:HPACK(一種header壓縮算法) header 表的最大長度,默認值 4096
- SETTINGS_ENABLE_PUSH:客戶端發向服務端的配置,若設置為 true,客戶端將允許服務端推送響應,默認值 true
- SETTINGS_MAX_CONCURRENT_STREAMS:同時打開的 stream 最大數量,通常意味著同一時刻能夠同時響應的請求數量,默認無限
- SETTINGS_INITIAL_WINDOW_SIZE:流控的初始窗口大小,默認值 65535
- SETTINGS_MAX_FRAME_SIZE:對端能夠接受幀的最大長度,默認值16384
- SETTINGS_MAX_HEADER_LIST_SIZE:對端能夠接受的 header 列表最大長度,默認不限制
流控的實現如上所述,每發送一批 DATA 幀,即將窗口大小減小。需要注意的是流控僅針對 DATA 幀。
前面提到流控既可以作用于 stream 又可以作用于 connection,那具體是怎么執行的呢?connection 的流控與 上述 stream 流控邏輯類似,每次發送 DATA 幀,connection 與 stream 窗口都會減小,但不同的是,WINDOW_UPDATE 要么單獨作用于 stream,要么單獨作用于 connection(streamid 為 0 時,表示作用于 connection)。
問題定位
那么回到開篇的問題,我們以 URL ?https://www.sysgeek.cn/??為例,通過在本地做代碼 debug 發現,最終拋異常的原因在于接收到 WINDOW_UPDATE 幀后,更新后窗口大小值大于 2^32 - 1 導致拋異常:
而從這里的代碼可以看出,524288 是當前窗口大小,而delta是對方告知的 WINDOW_UPDATE 大小,通過分析,發現 524288 這個值不同于默認值 65535,那繼續看這個值是什么時間改動的:
發現是接收 SETTINGS 指令后,初始化窗口大小時修改的,但這里與?RFC 7540 [2] 的描述(connection 窗口大小僅在接收到 WINDOW_UPDATE 后才可能修改)是沖突的:
因此我們斷定是 httpcore5 的源代碼有 bug,在刪除標記的這行代碼后,請求可以正常執行了。
遺憾的是在準備給 httpcore5 提 PR 的過程中發現這個 bug 已經在 commit 中被修復了。
參考資料
[1] PTS:
https://help.aliyun.com/document_detail/145501.html
[2] RFC 7540:
https://datatracker.ietf.org/doc/html/rfc7540#section-6.9.2
? ??https://datatracker.ietf.org/doc/html/rfc7540#section-5.2??
? ??https://undertow.io/blog/2015/04/27/An-in-depth-overview-of-HTTP2.html??
? ??https://laike9m.com/blog/rfc7540-bi-ji-wu-flow-control,106/??
點擊??此處??,前往 PTS 官網查看更多~
總結
以上是生活随笔為你收集整理的如何定位并修复 HttpCore5 中的 HTTP2 流量控制问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 网关流控利器:结合 AHAS 实现 In
- 下一篇: 阿里云开源业内首个应用多活项目 AppA