衆所周知 gRPC 是基於 HTTP/2,而 HTTP/2 是基於 TCP 長鏈接的。git
k8s 自帶一套基於 DNS 的服務發現機制 —— Service。github
但基於 Service 的服務發現對於 gRPC 來講並非開箱即用的,這裏面有不少坑。算法
gRPC client 直接使用 ClusterIP Service 會致使負載不均衡。由於 HTTP/2 多個請求能夠複用一條鏈接,併發達到最大值纔會建立新的鏈接。這個最大值由 MaxConcurrentStreams 控制,Golang client 默認是100。編程
除非運氣比較好,例如併發數是 (200, 300],正好與三個不一樣 pod 創建了三條長鏈接。因此使用 ClusterIP Service 不太靠譜。後端
HTTP/1.1 默認開啓 Keepalive,也會保持長鏈接。 可是 HTTP/1.1 多個請求不會共享一個鏈接,若是鏈接池裏沒有空閒鏈接則會新建一個,通過 Service 的負載均衡,各個 pod 上的鏈接是相對均衡的。併發
長鏈接負載均衡的原理是與後端每一個 pod 都創建一個長鏈接,LB 算法選擇一個寫入請求。負載均衡
建立 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
若是不想在 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 庫 - 對接監控告警、鏈路追蹤等基礎設施工做量大 |
- 鏈路長,有性能損耗 - 下層邏輯複雜,不透明,出問題抓瞎 - 還不夠成熟 |
https://kubernetes.io/blog/20...