我正在開發一個 gRPC 應用程序,該應用程式將使用 http 模式在 HAProxy 後面提供服務。如果伺服器應用程式立即(即在發送任何回應之前)中止回應流呼叫並出現特定錯誤,則用戶端應用程式將收到CANCELLED
錯誤而不是發送的錯誤。錯誤詳細資訊將是收到 RST_STREAM,錯誤代碼為 8。我正在使用 HAProxy 2.3.2 和 grpc 1.34.0。
對於每個此類請求,HAProxy 日誌SD--
條目都會在斷開連線時的會話狀態領域,例如。
<134>Jan 9 18:09:39 1a8328663d74 haproxy[8]: 172.28.0.4:41698 [09/Jan/2021:18:09:39.346] grpc-server-fe grpc-server-fe/grpc-server-be 0/0/0/-1/+0 -1 +115 - - SD-- 1/1/0/0/0 0/0 "POST http://proxy:6000/service.Service/StreamStream HTTP/2.0"
在 HAProxy 文件中,這些標誌定義如下:
- S:TCP 會話被伺服器意外中止,或伺服器明確拒絕。
- D:會話處於DATA階段。
另外:
標清與伺服器的連線在資料傳輸期間因錯誤而中斷。這通常意味著 haproxy 在與伺服器交換資料時收到了來自伺服器的 RST 或來自中間設備的 ICMP 訊息。這可能是由伺服器崩潰或中間設備上的網路問題引起的。
知道這一點,並記住打開 HTTP 連接是為了資料流,作為一種解決方法,我嘗試在引發錯誤之前發送一條空的 gRPC 訊息。該解決方案提供了部分幫助 - 客戶端可能會收到大多數請求的錯誤代碼,但問題仍然不時發生。
下一步我使用wireshark 檢查了網路流量。以下是 gRPC 伺服器在發生立即呼叫中止事件時提供的 HTTP 回應的追蹤:
HyperText Transfer Protocol 2
Stream: SETTINGS, Stream ID: 0, Length 0
Length: 0
Type: SETTINGS (4)
Flags: 0x01
0... .... .... .... .... .... .... .... = Reserved: 0x0
.000 0000 0000 0000 0000 0000 0000 0000 = Stream Identifier: 0
Stream: HEADERS, Stream ID: 1, Length 88, 200 OK
Length: 88
Type: HEADERS (1)
Flags: 0x05
.... ...1 = End Stream: True
.... .1.. = End Headers: True
.... 0... = Padded: False
..0. .... = Priority: False
00.0 ..0. = Unused: 0x00
0... .... .... .... .... .... .... .... = Reserved: 0x0
.000 0000 0000 0000 0000 0000 0000 0001 = Stream Identifier: 1
[Pad Length: 0]
Header Block Fragment: 88400c636f6e74656e742d74797065106170706c69636174...
[Header Length: 120]
[Header Count: 4]
Header: :status: 200 OK
Header: content-type: application/grpc
Header: grpc-status: 7
Header: grpc-message: Details sent by the server
Stream: RST_STREAM, Stream ID: 1, Length 4
Length: 4
Type: RST_STREAM (3)
Flags: 0x00
0... .... .... .... .... .... .... .... = Reserved: 0x0
.000 0000 0000 0000 0000 0000 0000 0001 = Stream Identifier: 1
Error: NO_ERROR (0)
因此,伺服器發送回應標頭,其中包含有關錯誤End Stream
和End Headers
標誌設定的詳細資訊。然後用NO_ERROR
代碼關閉流。根據中提供的答案https://stackoverflow.com/questions/55511528/should-grpc-server-side-half-fitting-implicitly-terminate-the-client/55522312現階段一切正常。我還簡要回顧了RFC 7540並且找不到任何不符合 HTTP/2 協定的內容。
引用的 gRPC 伺服器 HTTP 回應後面是 HAProxy 發起的 TCP ACK
,接下來,HAProxy 將其回應分派給客戶端。
HyperText Transfer Protocol 2
Stream: HEADERS, Stream ID: 1, Length 75, 200 OK
Length: 75
Type: HEADERS (1)
Flags: 0x05
.... ...1 = End Stream: True
.... .1.. = End Headers: True
.... 0... = Padded: False
..0. .... = Priority: False
00.0 ..0. = Unused: 0x00
0... .... .... .... .... .... .... .... = Reserved: 0x0
.000 0000 0000 0000 0000 0000 0000 0001 = Stream Identifier: 1
[Pad Length: 0]
Header Block Fragment: 885f106170706c69636174696f6e2f67727063000b677270...
[Header Length: 120]
[Header Count: 4]
Header: :status: 200 OK
Header: content-type: application/grpc
Header: grpc-status: 7
Header: grpc-message: Details sent by the server
Stream: RST_STREAM, Stream ID: 1, Length 4
Length: 4
Type: RST_STREAM (3)
Flags: 0x00
0000 0000 = Unused: 0x00
0... .... .... .... .... .... .... .... = Reserved: 0x0
.000 0000 0000 0000 0000 0000 0000 0001 = Stream Identifier: 1
Error: CANCEL (8)
可以看到,標誌和HEADERS
幀的所有內容都已就位,因此錯誤詳細資訊已傳遞到客戶端,但 的程式碼RST_STREAM
已變更為CANCEL
。實際上,客戶端收到了所有預期的數據,但之後它收到了RST_STREAM(CANCEL)
映射到 gRPCCANCELLED
錯誤的意外數據,如gRPC 文檔。
在進一步調查的過程中,我參考了HAProxy原始碼。我發現代碼設定在h2_do_shutr
的函數mux_h2.c
(自訂 HAProxy 建構的實驗已經證明它確實是這個地方)。涉及的程式碼分支有以下註解:
已提供最終答复,我們不再需要此流。當伺服器在上傳結束之前回應並快速關閉(重定向、拒絕…)時,可能會發生這種情況
這些是我設法收集到的有關該問題的詳細資訊。我不完全確定問題是否出在 gRPC 核心(在 HTTP2 流處理方面過於混亂)或 HAProxy(在重寫RST_STREAM
程式碼時過於粗心)。最後一個問題是,如何調整 HAProxy 和 gRPC 核心伺服器的配置,以便在發生立即呼叫中止的情況下正常運作。重現問題的最小 HAProxy 配置如下:
global
log stdout local0
listen grpc-server-fe
bind *:6000 proto h2
mode http
log global
option logasap
option httplog
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
server grpc-server-be server:6000 proto h2
我準備了一個具有最少範例的儲存庫包含簡單的 python 客戶端和伺服器。它還包含docker-compose
網路環境,包括配置的 HAProxy。