在K8s上運維Java和GC的經驗教訓 - Coufal

banq發表於2021-04-28

在Wandera,大多數微服務都是用Java編寫的,並且都在全球眾多的Kubernetes叢集中執行。不久前,我們的一個團隊注意到網路間歇性問題,這會導致我們兩個邊緣服務之間的通訊失敗。
解決方法很簡單,將受影響的服務的流量故障轉移到執行狀況良好的例項上,並在一段時間後進行恢復過來,但是卻無法及時發現問題,線上程轉儲中找不到任何暗示潛在問題的資訊,後端團隊引入了一個度量標準和一個警報,以檢測邊緣服務之間的斷路(CB),以便至少我們可以及時做出反應而不會影響我們的客戶。
在Wandera,我們不僅每天以連續交付的方式多次獨立釋出服務,而且還可以透過使用內部內建的功能標記系統以類似的方式為客戶啟用功能。該系統的一部分是我們的大多數Java服務使用的通用庫,這些庫用於檢查是否為特定客戶/管理員/裝置/其他啟用了特定功能。庫是在一段時間之前編寫的,並且在過去的幾年中沒有進行任何重大更改,因此您可以稱其為經過驗證的庫。但是,就像所有內容一樣,魔鬼會隱藏得很詳細,環境或輸入引數的變化可能會導致令人驚訝的結果。這將在以後發揮作用。
 
警報是在聖誕節之前引入的,相對安靜,直到一月初。在這一點上,警報開始定期觸發,並相應地將其上報給On-Call工程師(正確的做法是)。這似乎只發生在我們當時最繁忙的美國特區中。我們嘗試更改在邊緣服務旁邊執行的本地資料庫,並向群集的工作節點中新增了更多資源,但是即使服務的響應時間開始縮短,斷路仍在發生。
我們有一些理論,但沒有確鑿的證據。例如,一種理論認為問題可能與新的資料庫後端(從嵌入式到外部)有關,這帶來了額外的延遲,並且無法很好地處理負載。我們的Prometheus指標顯示了負載下資料庫的延遲增加,而DB指標卻沒有任何增加。
儘管問題是斷斷續續的,並且影響相對較小,但我們還是決定召集一個專門tiger團隊幾天來共同關注該問題。
  • 團隊可以將問題隔離到我們的網路服務之一
  • 它以某種方式與服務的釋出或帶來的額外負載有關
  • 鎖定後,該服務將無法恢復,除非完全從負載中取出負載

在Tiger團隊對問題進行調查時,產品和後端團隊為我們的客戶啟用了幾個功能,這些功能正在增加此服務的負載。得出結論並不難-可以觀察到因果關係。啟用功能標誌會導致服務上的更多負載,進而導致鎖定。
最簡單(最討厭)的解決方案是增加分配給服務的計算資源,問題消失了,甚至顯示DB呼叫延遲的指標也更好了。
儘管如此,Tiger團隊堅持不懈地工作,並繼續分析增加資源的影響……然後問題又發生了。產品團隊為另一批美國客戶啟用了某些功能,這些功能在啟用該標記的確切時刻在我們的一個歐洲DC中觸發了相同的警報。但是,由於受影響的客戶不在歐盟執行其裝置,因此DC的負載沒有增加。
 
為何世界另一端的客戶啟用某些功能會如何影響另一端的服務?您還記得我前面提到的庫(我們用來控制功能標記的庫)嗎?這是惡作劇呼叫出錯的地方。
們透過對邊緣服務進行詳細的分析發現了這些特徵庫,但是現在為什麼會出現問題?以及它與Kubernetes的聯絡如何?
 

GC死亡
在Kubernetes中,您可以選擇檢查容器的資源使用情況,這稱為容器限制。如果在CPU上設定了限制,則會在特定的時間視窗內為容器提供CPU時間的有限部分。每當容器達到極限時,它就會從進一步的CPU週期中剝離,直到視窗關閉。這樣做是為了減少嘈雜鄰居對其他容器的影響,並保護在同一節點上執行的其他服務。
另一方面,Java具有這種垃圾回收過程,可以清理記憶體中未使用的物件,儘管執行速度(通常)很快會非常昂貴。它可能會非常昂貴,以至於可能在特定的時間範圍內耗盡整個CPU配額,而且發生如此之快,以至於使用常規指標很難觀察到。
由於兩者的結合以及庫中的故障,我們的服務成為了GC導致死亡的受害者。

  • FF檢查會產生大量垃圾(並且在解析時會佔用寶貴的CPU週期)
  • 為了使Java能夠應對,必須呼叫GC
  • 更多的GC意味著更高的服務延遲
  • 更高的延遲意味著處理請求需要更多的執行緒
  • 更多執行緒→更多上下文切換
  • 更多上下文切換→需要更多CPU週期
  • CPU達到容器限制
  • 容器被節流
  • 沒有足夠的CPU進行垃圾收集
  • 記憶體回收速度不夠快,垃圾堆積
  • 需要更多的垃圾收集意味著需要更多的CPU
  • CPU不足→CPU節流
  • CPU節流→延遲增加
  • 需要更多執行緒和GC
  • 惡性迴圈現在已經完成
  • 服務現在(幾乎)無響應

 

解決方法
為了解決這種情況,團隊分析了庫並確定了需要修復的地方。最後,這只是分析的一點微小抽象:類似java.util.Properties成java.util.HashMap導致該問題。
有了生產中的修復程式,就可以測量結果了。

  • 檢查FF所花費的CPU驟降了97%
  • 每秒分配的Java堆記憶體減少了80%
  • 最後,服務的平均響應時間縮短了60%

 

結論

  • 花費幾個小時或幾天的時間全神貫注於此問題可能是有效的
  • 在Kubernetes中對容器設定了限制後,始終會發出有關CPU節流的警報
  • 程式碼中的每個抽象都有一個代價,衡量其效果,以免失控
  • 遙測(日誌,指標,跟蹤)很重要,但在某些情況下還不夠
  • 在生產中測量profile服務很重要


 

相關文章