踩坑記:gRPC 異常響應

felix021發表於2023-02-12

- 起 -

前些天接到一個 Oncall,來自 Lark 的衚衕學反饋,用 gRPC 官方的 python 客戶端請求 Kitex gRPC Server,有時收到的 response 為 None。

請求的程式碼大致如下:

stub = xx_grpc.XXXServiceStub(channel)
resp = stub.SomeMethod(req)
logger.info("resp = {}".format(resp))

如果請求失敗,按 python 的尿性,這裡應該 raise 一個 exception,返回個 None 算個啥?

衚衕學補充說,從服務端的日誌來看,請求是正常收到且處理了,並且透過增加兩個環境變數再執行 client 端:

$ GRPC_TRACE=all GRPC_VERBOSITY=debug python3 test.py

可以看到 client 把 tcp 報文內容都 dump 出來了,雖然亂碼很多,但是從一些文字中可以看到,確實收到了 server 的 response:

考慮到衚衕學用的是 gRPC 官方的 client,我充分利用自己鍛鍊了多年的反思技能,先從 Kitex 查起。

- 承 -

既然這個問題能夠穩定復現,那就說明可以穩定復現這個問題。

那麼就先用 tcpdump 抓它個包:

$ tcpdump -i any tcp port 9954 -Ans 0 -w grpc.pcap

然後在 wireshark 裡開啟,結合 log 裡的資訊,透過 filter 找到有問題的這個請求:

人肉 decode 這個報文確實有點為難,好在可以在 wireshark 裡指定報文的協議為 HTTP2,然後它順手就把 gRPC 也給識別出來了:

但也沒有完全識別出來,只是把這個 gRPC payload 識別成了「Line-based text data」。將這個的「text data」儲存為 packet_bytes.bin,然後執行:

$ protoc --decode_raw < packet_bytes.bin

可以正常解碼,說明返回的資料是符合 protobuf 規範的。

而對比其他正常響應的報文,Wireshark 可以直接按照 protobuf 的編碼協議當場 decode 出來:

這說明在一些情況下, Kitex gRPC Server 確實返回了不正確的報文。

對比這兩個請求的 Wireshark 解析結果,可以看到,在 gRPC Message 下面有兩個 flag 不同:

flag正常響應異常響應
Frame Type01
Compressed Flag01

看起來非常接近真相了。

那麼問題是哪一個呢?

- 轉 -

根據官方文件 gRPC over HTTP2 ,其報文是由一系列「Length-Prefixed-Message」構成的;而每個「Length-Prefixed-Message」則由三個部分組成:

  • Compressed-Flag:0/1(1位元組無符號整數)
  • Message-Length:訊息長度(4位元組無符號整數)
  • Message:二進位制資料

異常響應的報文顯然不符合規範 —— Compressed Flag 這個位元組的值竟然是 0xAF ?

根據以前的經驗,這種髒東西大概是這兩種情況導致的:(1) 併發讀寫導致記憶體資料被寫壞了,或 (2) 複用了未清理的buf。

侯捷大佬說過「原始碼面前,了無秘密」。但是他沒說扒程式碼會這麼辛苦。此處省略800字,我終於扒出來 Kitex 的 gRPC 編碼邏輯,位於 pkg/remote/codec/protobuf/grpc.go:

在編碼一個 Data Frame 的時候,會先從 mcache 獲取一個 buf,在 [1, 5) 寫入資料的長度,並在 [5, +∞) 寫入 protobuf 編碼後的資料。

至此破案。

- 合 -

既然問題定位到了,修復起來也就很簡單了,只要將從 mcache 獲得的 buf 第一個位元組清零即可,相關 PR 已經合入 develop 分支,將會隨著下一個版本的 Kitex 一起釋出。

此外,python client 在遇到錯誤的報文時沒有任何報錯,也不是太對勁,於是我給他們提了個 issue

最後打個小廣告,歡迎大家關注 CloudWeGo (微信公眾號:CloudWeGo)—— CloudWeGo 是一套可快速構建企業級雲原生微服務架構的中介軟體集合。 它包含許多元件:Golang RPC 框架 Kitex,HTTP 框架 Hertz,Rust RPC 框架 Volo,網路庫 Netpoll,Go 語言 Thrift 編譯器 Thriftgo 等等。 透過結合社群優秀的開源產品和生態,可以快速搭建一套完善的雲原生微服務體系。

(完)

參考

相關文章