淺談 K8S 下 gRPC 負載均衡問題
本文轉載自微信公眾號「架構技術漫談」,作者LA0WAN9。轉載本文請聯系架構技術漫談公眾號。
一般來說,在 K8S 下部署服務是很簡單的事兒,但是如果部署的是一個 gRPC 服務的話,那么稍不留神就可能掉坑里,個中緣由,且聽我慢慢道來。
在 K8S 下部署服務,缺省會被分配一個地址(也就是 ClusterIP[1]),客戶端的請求會發送給它,然后再通過負載均衡轉發給后端某個 pod:
ClusterIP
如果是 HTTP/1.1 之類的服務,那么 ClusterIP 完全沒有問題;但是如果是 gRPC 服務,那么 ClusterIP 會導致負載失衡,究其原因,是因為 gRPC 是基于 HTTP/2 的,多個請求在一個 TCP 連接上多路復用,一旦 ClusterIP 和某個 pod 建立了 gRPC 連接后,因為多路復用的緣故,所以后續其它請求也都會被轉發給此 pod,結果其它 pod 則完全被忽略了。
看到這里,有的讀者可能會有疑問:HTTP/1.1 不是實現了基于 KeepAlive 的連接復用么?為什么 HTTP/1.1 的復用沒問題,而 HTTP/2 的復用就有問題?答案是 HTTP/1.1 的 復用是串行的,當請求到達的時候,如果沒有空閑連接那么就新創建一個連接,如果有空閑連接那么就可以復用,同一個時間點,連接里最多只能承載有一個請求,結果是 HTTP/1.1 可以連接多個 pod;而 HTTP/2 的復用是并行的,當請求到達的時候,如果沒有連接那么就創建連接,如果有連接,那么不管其是否空閑都可以復用,同一個時間點,連接里可以承載多個請求,結果是 HTTP/2 僅僅連接了一個 pod。
了解了 K8S 下 gRPC 負載均衡問題的來龍去脈,我們不難得出解決方案:
在 Proxy 中實現負載均衡:采用 Envoy 做代理,和每臺后端服務器保持長連接,當客戶端請求到達時,代理服務器依照規則轉發請求給后端服務器,從而實現負載均衡。
Proxy
在 Client 中實現負載均衡:把服務部署成 headless service[2],這樣服務就有了一個域名,然后客戶端通過域名訪問 gRPC 服務,DNS resolver 會通過 DNS 查詢后端多個服務器地址,然后通過算法來實現負載均衡。
Client
兩種方案的優缺點都很明顯:Proxy 方案結構清晰,客戶端不需要了解后端服務器,對架構沒有侵入性,但是性能會因為存在轉發而打折扣;Client 方案結構復雜,客戶端需要了解后端服務器,對架構有侵入性,但是性能更好。
參考資料
[1]ClusterIP: https://kubernetes.io/docs/concepts/services-networking/service/
[2]headless service: https://kubernetes.io/docs/concepts/services-networking/serv