做者:Vikas Kumarjvm
翻譯:Bach(才雲)ide
校對:木子(才雲)微服務
JVM 預熱是一個很是頭疼而又難解決的問題。基於 JVM 的應用程序在達到最高性能以前,須要一些時間來「預熱」。當應用程序啓動時,一般會從較低的性能開始。這歸因於像即時(JIT)編譯這些事兒,它會經過收集使用配置文件信息來優化經常使用代碼。最終這樣的負面影響是,與平均水平相比,預熱期間接收的 request 將具備很是高的響應時間。在容器化、高吞吐量、頻繁部署和自動伸縮的環境中,這個問題可能會加重。性能
在這篇文章中,咱們將討論在運行在 Kubernetes 集羣中的 Java 服務如何解決 JVM 預熱問題的經驗。測試
幾年前,咱們逐步從總體中分離出服務,開始在 Kubernetes 上進行遷移到基於微服務的體系結構。大多數新服務都是在 Java 中開發的。當咱們在印度市場上運行一個這樣的服務時,咱們第一次遇到了這個問題。咱們經過負載測試進行了一般的容量規劃過程,並肯定 N 個 Pod 足以處理超過預期的峯值流量。優化
儘管該服務在輕鬆處理高峯流量,但咱們在部署過程當中發現了問題。咱們的每一個 Pod 在高峯時間處理的 RPM 都超過 10k,而咱們使用的是 Kubernetes 滾動更新機制。在部署過程當中,服務的響應時間會激增幾分鐘,而後再穩定到一般的穩定狀態。在咱們的儀表板中,會看到相似的圖表:翻譯
與此同時,咱們開始收到來自部署時間段內的大量投訴,幾乎都關於高響應時間和超時錯誤。3d
咱們很快意識到這個問題與 JVM 預熱階段有關,但當時有其餘的重要事情,所以咱們沒有太多時間進行調查,直接嘗試了最簡單的解決方案——增長 Pod 數量,以減小每一個 Pod 的吞吐量。咱們將 Pod 數量增長了近三倍,以便每一個 Pod 在峯值處理約 4k RPM 的吞吐量。咱們還調整了部署策略,以確保一次最多滾動更新 25%(使用 maxSurge
和 maxUnavailable
參數)。這樣就解決了問題,儘管咱們的運行容量是穩定狀態所需容量的 3 倍,但咱們可以在咱們的服務中或任何相關服務中沒有問題地進行部署。code
隨着後面幾個月裏更多的遷移服務,咱們開始在其餘服務中經常看到這個問題。所以咱們決定花一些時間來調查這個問題並找到更好的解決方案。blog
在仔細閱讀了各類文章後,咱們決定嘗試一下預熱腳本。咱們的想法是運行一個預熱腳本,向服務發送幾分鐘的綜合請求,來完成 JVM 預熱,而後再容許實際流量經過。
爲了建立預熱腳本,咱們從生產流量中抓取了實際的 URL。而後,咱們建立了一個 Python 腳本,使用這些 URL 發送並行請求。咱們相應地配置了 readiness 探針的 initialDelaySeconds
,以確保預熱腳本在 Pod 爲 ready
並開始接受流量以前完成。
使人吃驚的是,儘管結果有一些改進,但並不顯著。咱們仍然常常觀察到高響應時間和錯誤。此外,預熱腳本還帶來了新的問題。以前,Pod 能夠在 40-50 秒內準備就緒,但用了腳本,它們大約須要 3 分鐘,這在部署期間成爲了一個問題,更別說在自動伸縮期間。咱們在預熱機制上作了一些調整,好比容許預熱腳本和實際流量有一個短暫的重疊期,但也沒有看到顯著的改進。最後,咱們認爲預熱腳本的收益過小了,決定放棄。
因爲預熱腳本想法失敗了,咱們決定嘗試一些啓發式技術-
GC(G一、CMS 和 並行)和各類 GC 參數
堆內存
通過幾輪實驗,咱們終於取得了突破。測試的服務配置了 Kubernetes 資源 limits:
咱們將 CPU request 和 limit 增長到 2000m,並部署服務以查看影響,能夠看到響應時間和錯誤有了巨大的改進,比預熱腳本好得多。
第一個 Deployment(大約下午 1 點)使用 2 個 CPU 配置,第二個 Deployment (大約下午 1:25)使用原來 1 個 CPU 配置
爲了進一步測試,咱們將配置升級到 3000m CPU,令咱們驚訝的是,問題徹底消失了。正以下面看到的,響應時間沒有峯值。
具備 3 個 CPU 配置的 Deployment
很快,咱們就發現問題出在 CPU 節流上。在預熱階段,JVM 須要比平均穩定狀態下更多的 CPU 時間,但 Kubernetes 資源處理機制(CGroup)根據配置的 limits,從而限制了 CPU。
有一個簡單的方法能夠驗證這一點。Kubernetes 公開了一個每一個 Pod 的指標,container_cpu_cfs_throttled_seconds_total
表示這個 Pod 從開始到如今限制了多少秒 CPU。若是咱們用 1000m 配置觀察這個指標,應該會在開始時看到不少節流,而後在幾分鐘後穩定下來。咱們使用該配置進行了部署,這是 Prometheus 中全部 Pod 的 container_cpu_cfs_throttled_seconds_total
圖:
正如預期,在容器啓動的前 5 到 7 分鐘有不少節流,大部分在 500 秒到 1000 秒之間,而後穩定下來,這證明了咱們的假設。
當咱們使用 3000m CPU 配置進行部署時,觀察到下圖:
CPU 節流幾乎能夠忽略不計(幾乎全部 Pod 都不到 4 秒)。
儘管咱們發現了這個問題的根本,但就成本而言,該解決方案並不太理想。由於有這個問題的大多數服務都已經有相似的資源配置,而且在 Pod 數量上超額配置,以免部署失敗,可是沒有一個團隊有將 CPU 的 request、limits 增長三倍並相應減小 Pod 數量的想法。這種解決方案實際上可能比運行更多的 Pod 更糟糕,由於 Kubernetes 會根據 request 調度 Pod,找到具備 3 個空閒 CPU 容量的節點比找到具備 1 個空閒 CPU 的節點要困可貴多。它可能致使集羣自動伸縮器頻繁觸發,從而向集羣添加更多節點。
咱們又回到了原點 可是此次有了一些新的重要信息。如今問題是這樣的:
在最初的預熱階段(持續幾分鐘),JVM 須要比配置的 limits(1000m)更多的 CPU(大約 3000m)。預熱後,即便 CPU limits 爲 1000m,JVM 也能夠充分發揮其潛力。Kubernetes 會使用 request 而不是 limits 來調度 Pod。咱們清楚地瞭解問題後,答案就出現了——Kubernetes Burstable QoS。
Kubernetes 根據配置的資源 request 和 limits 將 QoS 類分配給 Pod。
到目前爲止,咱們一直在經過指定具備相等值的 request 和 limits(最初是 1000m,而後是 3000m)來使用 Guaranteed QoS。儘管 Guaranteed QoS 有它的好處,但咱們不須要在整個 Pod 生命週期中獨佔 3 個 CPU,咱們只須要在最初的幾分鐘內使用它。Burstable QoS 容許咱們指定小於 limits 的 request,例如:
因爲 Kubernetes 使用 request 中指定的值來調度 Pod,它會找到具備 1000m CPU 容量的節點來調度這個 Pod。可是因爲 3000m 的 limits 要高得多,若是應用程序在任什麼時候候都須要超過 1000m 的 CPU,而且該節點上有空閒的 CPU 容量,那麼就不會在 CPU 上限制應用程序。若是可用,它最多可使用 3000m。
這很是符合咱們的問題。在預熱階段,當 JVM 須要更多的 CPU 時,它能夠獲取須要的 CPU。JVM 被優化後,能夠在 request 範圍內全速運行。這容許咱們使用集羣中的冗餘的資源(足夠可用時)來解決預熱問題,而不須要任何額外的成本。
最後,進行假設測試。咱們更改了資源配置並部署了應用程序,成功了!咱們作了更多的測試以驗證結果一致。此外,咱們監控了 container_cpu_cfs_throttled_seconds_total
指標,如下是其中一個 Deployment 的圖表:
正如咱們所看到的,這張圖與 3000m CPU 的 Guaranteed QoS 設置很是類似。節流幾乎能夠忽略不計,它證明了具備 Burstable QoS 的解決方案是有效的。
爲了使 Burstable QoS 解決方案正常工做,節點上須要有可用的冗餘資源。這能夠經過兩種方式驗證:
就 CPU 而言,節點資源未徹底耗盡;
- 工做負載未使用 request 的 100% CPU。
儘管花了一些時間,最終找到了一個成本效益高的解決方案。Kubernetes 資源限制是一個重要的概念。咱們在全部基於 Java 的服務中實現了該解決方案,部署和自動擴展都運行良好,沒有任何問題。
要點:
在爲應用程序設置資源限制時要仔細考慮。花些時間瞭解應用程序的工做負載並相應地設置 request 和 limits。瞭解設置資源限制和各類 QoS 類的含義。
經過
monitoring/alertingcontainer_cpu_cfs_throttled_seconds_total
來關注 CPU 節流。若是觀察到過多的節流,能夠調整資源限制。- 使用 Burstable QoS 時,確保在 request 中指定了穩定狀態所需的容量。
原文連接:https://tech.olx.com/improving-jvm-warm-up-on-kubernetes-1b27dd8ecd58