如何減少Docker和Kubernetes中的JVM應用程序內存佔用

感謝您的閱讀!如果您想了解我在Kafka,Scala,ZIO和JVM方面的最新信息,請在Twitter和Medium中關注我。 如果有任何不清楚的地方,或者您想指出點什麼,請在下方留言。

如何減少Docker和Kubernetes中的JVM應用程序內存佔用

> Photo by Franck V. on Unsplash

最近,我設法大大減少了Kubernetes上一個廣泛使用的JVM應用程序容器的內存使用量,並節省了很多錢。 我弄清楚了哪些JVM標誌更重要,如何正確設置它們以及如何輕鬆衡量我的操作對應用程序內存使用的各個部分的影響。 這是我的懸崖筆記。

故事從一個用例開始。 我作為數據流團隊的一員在Wix工作,負責我們所有的Kafka基礎架構。 最近,我負責為Node.js服務創建Kafka客戶端代理。

用例:Kafka客戶端sidecar浪費內存

這個想法是將所有與Kafka相關的動作(例如生產和消費)從Node.js應用委託給單獨的JVM應用。 其背後的動機是,我們許多針對Kafka客戶的基礎架構都是用Scala編寫的(稱為greyhound-可以在此處找到開放源代碼版本)。有了Sidecar,Scala代碼不需要在其他代碼中重複語言。 只需要一個薄的包裝。

如何減少Docker和Kubernetes中的JVM應用程序內存佔用

一旦將Sidecar部署到生產中,我們注意到它會消耗大量內存。

如何減少Docker和Kubernetes中的JVM應用程序內存佔用

> Metric used — container_memory_working_set_bytes

從上表中可以看到,僅用於sidecar(運行openjdk 8)的內存佔用量是過去包含kafka庫的node-app容器的4-5倍。

我必須瞭解為什麼以及如何大幅減少它。

試驗生產數據

我著手創建一個模擬該特定節點應用程序的sidecar的測試應用程序,以便能夠在不影響生產的情況下自由地對其進行實驗。 該應用程序包含生產應用程序中所有生產相同主題的消費者。

作為監視內存消耗的一種方式,我使用了從我的應用程序內部的mxbeans暴露給Prometheus / Grafana的指標,例如heapMemoryUsed和nonHeapMemoryUsed,但是您也可以使用jconsole或jvisualvm(均與JDK 8或更高版本捆綁在一起)

首先,我試圖瞭解每個消費者和生產者以及gRPC客戶端(稱為節點應用程序)的影響,得出的結論是,擁有一個(或更少)一個消費者並不會影響有意義的內存佔用。 道路。

JVM堆標誌

然後,我將注意力轉向堆分配,有兩個重要的JVM標誌與堆分配有關-Xms(啟動時堆內存大小)和-Xmx(最大堆內存大小)

我嘗試了兩者的許多不同組合,並記錄了由此產生的容器內存使用情況:

如何減少Docker和Kubernetes中的JVM應用程序內存佔用

> Container overall used memory with different heap flags

通過分析有關堆標誌變化的數據得出的第一個結論是,如果Xmx高於Xms,並且您的應用程序具有很高的內存壓力,那麼分配給堆的內存幾乎肯定會繼續增長 到Xmx限制,導致容器的整體內存使用量也增加(請參見下表中的比較)。

如何減少Docker和Kubernetes中的JVM應用程序內存佔用

> Xmx >> Xms

但是,如果Xmx與Xms相同,則可以對整體內存使用量進行更多控制,因為堆不會隨時間逐漸增加(請參見下面的比較)。

如何減少Docker和Kubernetes中的JVM應用程序內存佔用

> Xmx = Xms

我從堆標誌數據得出的第二個結論是,只要您沒有看到由於垃圾回收(GC)導致JVM暫停的時間很長,就可以顯著降低Xmx —長時間超過500ms 。 我再次使用Grafana監視GC,但是您也可以使用visualgc或gceasy.io

如何減少Docker和Kubernetes中的JVM應用程序內存佔用

> benign JVM pause times due to GC

請注意為Xmx設置的數字-如果您的應用程序在消息消耗吞吐量方面有很大差異,一旦您的應用程序遇到大量傳入消息,您的應用程序將更容易受到GC風暴的影響。

Kafka相關的調整

我們的靈獅(Kafka)使用者有一個內部消息緩衝區,該緩衝區可以獲取多達200條消息。 當我將最大允許大小減小到20時,我注意到堆內存使用率的波動範圍比size = 200窄得多(總體上使用率也很低):

如何減少Docker和Kubernetes中的JVM應用程序內存佔用

> Heap memory usage pattern when bufferMax=200

如何減少Docker和Kubernetes中的JVM應用程序內存佔用

> Heap memory usage pattern when bufferMax=20

當然,減小緩衝區大小意味著該應用程序將無法很好地處理突發事件-因此這不適用於高吞吐量應用程序。 為了減輕這種情況,我將每個Pod的灰狗消費者處理程序的並行度提高了一倍。 也就是說,我將處理Kafka消息的線程數從3個增加到6個。在異常情況下,該應用將需要更多的Pod,或者必須更改最大緩衝區配置。

將Kafka Consumer fetch.max.bytes從50M減少到5M(以減少輪詢的消息總大小)對內存佔用沒有明顯的影響。 也沒有從sidecar應用程序中提取出靈緹生產者(它可以駐留在DaemonSet中,因此它將在每個K8s節點上運行)。

摘要—有助於減少內存使用的因素

我進行的優化將容器內存使用量從1000M減少到了550-600M。 以下是有助於減少佔用空間的更改:

· 保持一致的堆大小分配-Xms等於-Xmx

· 減少廢棄物品(垃圾)的數量 緩衝較少的Kafka消息

· 只要GC(新一代+老一代)所用的百分比不高(CPU時間為0.25%),GC就可以繼續降低XMX

沒有幫助的(基本上)

· 減少KafkaConsumer的fetch.max.bytes

· 刪除Kafka生產者

· 從gRPC客戶端切換到Wix的自定義json-RPC客戶端

未來的工作

· 探索GraalVM本機映像是否可以提供幫助

· 比較不同的GC實現。 (我使用過CMS,但有G1)

· 通過切換到基於開源ZIO的靈獅,減少從Kafka消費時使用的線程數。

· 減少為每個線程分配的內存(默認情況下,為每個線程分配1MB)

肯定會有更多的改進(以及第二篇博客文章)。

更多信息

Docker內存資源限制和一堆Java —博客文章

GeekOUT會議上的Java流程視頻的內存佔用量

(本文翻譯自Natan Silnitsky的文章《How to reduce your JVM app memory footprint in Docker and Kubernetes》,參考:
https://medium.com/wix-engineering/how-to-reduce-your-jvm-app-memory-footprint-in-docker-and-kubernetes-d6e030d21298)


分享到:


相關文章: