k8s 服務發現 以及 gRPC 長鏈接負載均衡

衆所周知 gRPC 是基於 HTTP/2,而 HTTP/2 是基於 TCP 長鏈接的。git

k8s 自帶一套基於 DNS 的服務發現機制 —— Service。github

但基於 Service 的服務發現對於 gRPC 來講並非開箱即用的,這裏面有不少坑。算法

錯誤姿式 ClusterIP Service

gRPC client 直接使用 ClusterIP Service 會致使負載不均衡。由於 HTTP/2 多個請求能夠複用一條鏈接,併發達到最大值纔會建立新的鏈接。這個最大值由 MaxConcurrentStreams 控制,Golang client 默認是100。編程

除非運氣比較好,例如併發數是 (200, 300],正好與三個不一樣 pod 創建了三條長鏈接。因此使用 ClusterIP Service 不太靠譜。後端

爲何 HTTP/1.1 不受影響

HTTP/1.1 默認開啓 Keepalive,也會保持長鏈接。 可是 HTTP/1.1 多個請求不會共享一個鏈接,若是鏈接池裏沒有空閒鏈接則會新建一個,通過 Service 的負載均衡,各個 pod 上的鏈接是相對均衡的。併發

正確姿式

長鏈接負載均衡的原理是與後端每一個 pod 都創建一個長鏈接,LB 算法選擇一個寫入請求。負載均衡

gRPC client LB 配合 Headless Service

建立 Headless Service 後,k8s 會生成 DNS 記錄,訪問 Service 會返回後端多個 pod IP 的 A 記錄,這樣應用就能夠基於 DNS 自定義負載均衡。less

在 grpc-client 指定 headless service 地址爲 dns:/// 協議,DNS resolver 會經過 DNS 查詢後端多個 pod IP,而後經過 client LB 算法來實現負載均衡。這些 grpc-go 這個庫都幫你作了。編程語言

conn, err := grpc.DialContext(ctx, "dns:///"+headlessSvc,
    grpc.WithInsecure(),
    grpc.WithBalancerName(roundrobin.Name),
    grpc.WithBlock(),
)

完整代碼參考:https://github.com/win5do/go-...ide

Proxy LB 或 ServiceMesh

若是不想在 client 代碼中來作 LB,能夠使用 Envoy 或 Nginx 反向代理。

Proxy Server 與後端多個 pod 保持長鏈接,需配置對應模塊來識別 HTTP/2 請求,根據 LB 算法選擇一個 pod 轉發請求。

Istio 作長鏈接 LB 不要求 Headless Service,由於網格控制器不會直接使用 Service,而是獲取 Service 背後 Endpoint(IP)配置到 Envoy 中。

Proxy 若是自身有多個 replicas,則 proxy 與 client 之間也有長鏈接的問題,這就套娃了。

但 Istio 等服務網格會爲每一個 pod 注入一個專用的 Envoy 代理做爲 sidecar,因此問題不大。

gRPC 使用 Envoy proxy 可參考 Google Cloud 的教程:

https://github.com/GoogleClou...

對比

client 和 proxy 兩種方式的對比其實就是 侵入式服務治理 vs 網格化服務治理

侵入式 網格化
優勢 - 性能好,沒有屢次轉發
- 邏輯清晰,開發人員能很快定位問題
- 基礎設施下沉,邏輯一致,方便跨語言異構
- 非侵入,應用無感知
- 服務治理生態繁榮
缺點 - 多種編程語言須要分別開發 client 庫
- 對接監控告警、鏈路追蹤等基礎設施工做量大
- 鏈路長,有性能損耗
- 下層邏輯複雜,不透明,出問題抓瞎
- 還不夠成熟

Reference

https://kubernetes.io/blog/20...

https://zhuanlan.zhihu.com/p/...

https://zhuanlan.zhihu.com/p/...

相關文章
相關標籤/搜索