在 HTTP 模式下使用 HAProxy 進行回應流呼叫期間無法接收 gRPC 錯誤

在 HTTP 模式下使用 HAProxy 進行回應流呼叫期間無法接收 gRPC 錯誤

我正在開發一個 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 StreamEnd 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。

相關內容