一切都始于我向我們的高級軟件工程師提出的一個問題: “忘掉通信速度。你真的覺得在gRPC中開發通信比REST更好嗎?” 我不想聽到的答案立刻就來了:“絕對是的。”
在我提出這個問題之前,我一直在監控我們的服務在滾動更新和擴展Pod時出現的奇怪行為。我們的大多數微服務以往都通過REST調用進行通信,沒有任何問題。我們已經將一些這些集成遷移到了gRPC,主要是因為我們想擺脫REST的開銷。最近,我們觀察到了一些問題,都指向了同一個方向——我們的gRPC通信。當然,我們遵循了在Kube.NETes中運行gRPC而不使用服務網格的建議實踐,我們在服務器上使用了一個無頭服務對象,并在gRPC中使用了客戶端的“輪詢”負載平衡與DNS發現等。
擴展Pod數量
Kubernetes內部負載均衡器不是用于負載均衡RPC,而是用于負載均衡TCP連接。第四層負載均衡器由于其簡單性而很常見,因為它們與協議無關。但是,gRPC破壞了Kubernetes提供的連接級負載均衡。這是因為gRPC是基于HTTP/2構建的,而HTTP/2被設計為維護一個長期存在的TCP連接,該連接中的所有請求都可以在任何時間點同時處于活動狀態。這減少了連接管理的開銷。然而,在這種情況下,連接級別的負載平衡并不是非常有用,因為一旦建立了連接,就不再需要進行負載平衡。所有的請求都會固定到原始目標Pod,直到發生新的DNS發現(使用無頭服務)。這不會發生,直到至少有一個現有連接斷開。
問題示例:
- 2個客戶端(A)調用2個服務器(B)。
- 自動縮放器介入并擴展了客戶端。
- 服務器Pod負載過重,因此自動縮放器介入并增加了服務器Pod的數量,但沒有進行負載平衡。甚至可以看到新Pod上沒有傳入的流量。
- 客戶端被縮減。
- 客戶端再次擴展,但負載仍然不平衡。
- 一個服務器Pod因過載而崩潰,發生了重新發現。
- 在圖片中沒有顯示,但是當Pod恢復時,情況看起來與圖3類似,即新Pod不會接收流量。
gRPC負載均衡的示例
兩個配置解決這個問題,技術上說是一行
正如我之前提到的,我們使用“客戶端負載均衡”,并使用無頭服務對象進行DNS發現。其他選項可能包括使用代理負載均衡或實現另一種發現方法,該方法將詢問Kubernetes API而不是DNS。
除此之外,gRPC文檔提供了服務器端連接管理提案,我們也嘗試過它。
以下是我為設置以下服務器參數提供的建議,以及gRPC初始化的Go代碼片段示例:
- 將MAX_CONNECTION_AGE設置為30秒。這個時間段足夠長,可以在沒有昂貴且頻繁的連接建立過程的情況下進行低延遲通信。此外,它允許服務相對快速地響應新Pod的存在,因此流量分布將保持平衡。
- 將MAX_CONNECTION_AGE_GRACE設置為10秒。定義了連接保持活動狀態以完成未完成的RPC的最大時間。
grpc.KeepaliveParams(keepalive.ServerParameters{
MaxConnectionAge: time.Second * 30, // THIS one does the trick
MaxConnectionAgeGrace: time.Second * 10,
})
- 1.
- 2.
- 3.
- 4.
在現實世界中的行為:
gRPC配置更改應用前后的Pod數量
在gRPC配置更改后觀察到的新Pod中的網絡I/O活動
接下來是第三行
擴展問題已經解決,但另一個問題變得更加明顯。焦點轉向了客戶端在滾動更新期間出現的gRPC code=UNAVAILABLE 錯誤。奇怪的是,這只在滾動更新期間觀察到,而在單個Pod擴展事件中卻沒有觀察到。
滾動更新期間的gRPC錯誤數量
部署滾動的過程很簡單:創建一個新的副本集,創建一個新的Pod,當Pod準備就緒時,舊的Pod將從舊的副本集中終止,以此類推。每個Pod之間的啟動時間間隔為15秒。關于gRPC DNS重新發現,我們知道它僅在舊連接中斷或以GOAWAY信號結束時才會啟動。因此,客戶端每15秒開始一次新的重新發現,但獲取到了過時的DNS記錄。然后,它們不斷進行重新發現,直到成功為止。
除非不是DNS問題...
幾乎每個地方都有DNS TTL緩存?;A設施DNS具有其自己的緩存。JAVA客戶端遭受了它們默認的30秒TTL緩存,而Go客戶端通常沒有實現DNS緩存。與此相反,Java客戶端報告了數百或數千次此問題的發生。當然,我們可以縮短TTL緩存的時間,但為什么要在滾動更新期間只影響gRPC呢?
幸運的是,有一個易于實現的解決方法?;蛘吒玫卣f,解決方案:讓新Pod啟動時設置30秒的延遲。
.spec.minReadySeconds = 30
- 1.
Kubernetes部署規范允許我們設置新Pod必須處于就緒狀態的最短時間,然后才會開始終止舊Pod。在此時間之后,連接被終止,gRPC客戶端收到GOAWAY信號并開始重新發現。TTL已經過期,因此客戶端獲取到了新的、最新的記錄。
結論
從配置的角度來看,gRPC就像一把瑞士軍刀,可能不會默認適合您的基礎架構或應用程序。查看文檔,進行調整,進行實驗,并充分利用您已經擁有的資源。我相信可靠和彈性的通信應該是您的最終目標。
我還建議查看以下內容:
- Keepalives。對于短暫的內部集群連接來說可能沒有意義,但在某些其他情況下可能會有用。
- 重試。有時,值得首先進行一些退避重試,而不是通過嘗試創建新連接來過載基礎設施。
- 代碼映射。將您的gRPC響應代碼映射到眾所周知的HTTP代碼,以更好地了解發生了什么情況。
- 負載均衡。平衡是關鍵。不要忘記設置回退并進行徹底的測試。
- 服務器訪問日志(gRPC code=OK)可能會因默認設置為信息級別而太冗長??紤]將它們降低到調試級別并進行篩選。