真实世界的 TCP HOL blocking
我們最近遇到一個問題,內部某個服務是有狀態的,有些請求需要在內部做轉發,但是請求偶爾發生超時,隨機超時,并且沒有任何規律,和 QPS 無關,一般 2s 內都恢復了。
經過兩個月排查,不斷的 narrow down, 最終排除業務代碼問題,定位 root cause 是 tcp restranmit 引起的 HOL(head of line) blocking, 己經給 aws 提了 ticket, 至于重傳的原因還在與 aws 確認中。
排查過程
我司代碼由 grab-kit 框架生成,去年 gopher china 2020 有同事做了分享,感興趣的可以去看 slide.
技術棧統一用 grpc 協義,服務發現使用 etcd, 同時 client 與 server 端也定義了大量的 Middleware, 比如 circuit breaker, logging, retry, chaos, statsd 等等,非常方便。
但也因為是框架生成的代碼,比較難排查,我們加了好多額外蹤追代碼,排除是業務代碼問題。
最終 narrow down 到 grpc 與網絡上,通過 tcpdump 抓包石錘 RC 是 tcp restranmit
client tcpdump
server tcpdump
上圖分別是 client, server 抓包的分析,可以清晰的看到 tcp 大量的重傳,那么為什么重傳會引起很多請求超時呢?
HTTP
當 http2 周邊生態穩定后,老的 http1 一定會退出歷史舞臺的,本質就是 http1 無法充分發揮 tcp 的性能。
http2 是一個徹徹底底的二進制協議,頭信息和數據包體都是二進制的,統稱為“幀”。對比 http1, 頭信息是文本編碼(ASCII編碼),數據包體可以是二進制也可以是文本。使用二進制作為協議實現方式的好處,更加靈活。在 http2 中定義了 10 種不同類型的幀。
如上圖所示,由于 http2 的數據包是亂序發送的,因此在同一個連接里會收到不同請求的 response。不同的數據包攜帶了不同的標記,用來標識它屬于哪個 response。
http2 把每個 request 和 response 的數據包稱為一個數據流(stream)。每個數據流都有自己全局唯一的編號。每個數據包在傳輸過程中都需要標記它屬于哪個數據流 ID。規定,客戶端發出的數據流,ID 一律為奇數,服務器發出的,ID 為偶數。
也就是說,對于 http1, 我們要使用連接池,或是純粹短連接,但是對于 http2 由于多路復用的存在,理論上一個 tcp 長連接就可以,我司線上一般都是 4 個。
HOL blocking
那么 http2 就沒有問題了嘛?不是的。HOL blocking 隊首阻塞原以為只活在教科書里,沒想到這次遇到了。
http1 協義可以使用 pipeline 的方式復用 tcp 連接,但是如果一個請求慢了,那么后續其它的請求只能等待,這個就算是 OSI 協義上七層的 HOL blocking
而對于 http2 來說,雖然七層由虛擬 stream 做到了多路復用,但是仍然存在四層 tcp 的阻塞,如果一個 packet 因為某種原因重傳,那么同一個 tcp 連接上后面的 requests 也會等待,直到超時重傳成功。這也就是為什么 http2 的連接理論上一個就夠了,但是實踐中還要創建多個 tcp 連接。
上圖就是 http2 模擬 HOL 的動圖,非常形像。那么如果解決這個問題呢?那就是 http3 也就是 quick based on UDP
動圖很形像了,某一個虛擬 stream 的包丟失重傳了,并不影響其它 stream 的。本質還是由于 UDP 是無狀態的,非面向連接
TODO
后續就是與 aws 一起排查重傳的原因了,懷疑兩個方向: ec2 宿主機網絡問題,os AMI 問題。
前都只能由 aws 定位了,os AMI 問題會比較麻煩,涉及 kernel tcp stack 源碼,暫時沒有其它思路了。希望能盡快 fix 這個問題。
小結
這次分享就這些,以后面還會分享更多的內容,如果感興趣,可以關注并點擊左下角的分享轉發哦(:
總結
以上是生活随笔為你收集整理的真实世界的 TCP HOL blocking的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 时钟源为什么会影响性能
- 下一篇: 事故驱动开发