使用容器中的 OpenJDK CRaC 幫助有效擴展熱應用程式實例
一、簡介
在本教程中,我們將了解檢查點協調恢復 (CRaC) ,這是一個 OpenJDK 項目,它允許我們以更短的時間啟動 Java 程式進行第一個事務。此外,我們將了解Alpaquita 容器如何讓我們輕鬆地在 Spring Boot 應用程式中實作 CRaC。
2. OpenJDK CRaC 如何解決 Java 中的緩慢預熱問題?
Java 應用程式歷來因啟動速度慢和預熱時間較長(達到穩定的峰值效能所需的時間)而受到相當多的批評。此外,它們在預熱期間消耗的計算資源比穩定運行期間所需的計算資源還要多。
這種行為很大程度上歸因於 HotSpot Java 虛擬機器 (JVM) 的基本工作方式。當應用程式啟動時,JVM 會尋找程式碼中的熱點並對其進行編譯以獲得更好的效能。但是,這需要時間和計算資源來實現:
此外,必須對應用程式的每個實例重複此操作。在微服務和無伺服器等雲端原生架構中,這個問題更加嚴重。在這裡,我們需要盡可能短的預熱時間和相當穩定的資源消耗。
如果我們可以將應用程式運行到其峰值效能並檢查該狀態會怎樣?然後,我們可以使用此檢查點來啟動應用程式的多個實例,而不必花費太多時間進行預熱。這根本就是OpenJDK CRaC API 向我們承諾的:
CRaC 是基於使用者空間中的檢查點和復原 (CRIU) ,這是一個為 Linux 實作檢查點和復原功能的專案。 CRIU 允許凍結容器或單一應用程式並從已儲存的檢查點檔案中復原它。
然而,CRaC 採用了 CRIU 的通用方法,並添加了一些增強和調整,使其適合 Java 應用程式。例如,CRaC 對應用程式的狀態施加一定的限制,以確保檢查點的一致性和安全性。
3. CRaC 採用的挑戰
CRaC 為基於 Java 的應用程式在雲端環境中提高效率提供了新的機會。在這裡,Spring 是開發基於 Java 的應用程式的流行框架之一。隨著 Spring Boot 3.2 的發布,我們現在在 Spring 框架中初步支援了 CRaC 。
但是,CRaC 並不像看起來那樣是一種可移植的解決方案。正如我們已經討論過的,CRaC 僅適用於 Linux,因為 CRIU 是 Linux 特定的功能。在其他作業系統上,CRaC 有一個用於建立和載入快照的無操作實作。
此外, CRaC 要求在拍攝快照之前關閉所有檔案和網路連線。恢復檢查點後必須重新開啟這些檔案和網路連線。這需要Java執行時間和框架的支援。
因此,我們不僅需要 Spring 的支持,還需要一個支援 CRaC 的 JDK 版本,例如 BellSoft 提供的 Liberica JDK。此外,我們需要在 Linux 發行版上運行 Spring 應用程序,例如 BellSoft 的 Alpaquita Linux。
因此,如果我們可以將我們的應用程式與在類似 Linux 的環境上運行的支援 CRaC 的 JDK 打包為可移植容器,那麼該解決方案將變得非常便攜且即插即用。這正是 BellSoft 為現代 Java 應用程式提供的承諾!
4. CRaC 與 Alpaquita 容器
BellSoft是一家 OpenJDK 供應商,為雲端原生 Java 應用程式提供端對端解決方案。作為其中的一部分,它提供了一套針對運行 Java 應用程式高度最佳化的容器。他們打包了Alpaquita Linux和Liberica JDK ,這兩者都是 BellSoft 的產品。
Alpaquita Linux 是唯一專為 Java 建置並針對雲端原生應用程式部署進行最佳化的 Linux 發行版。它透過內核優化、記憶體管理和優化的 malloc 提供更好的效能。它的基本映像大小僅為 3.28 MB!
Liberica JDK 是一個用於雲端原生 Java 部署的開源 Java 執行階段。由於支援最廣泛的體系結構和作業系統,它是真正的統一 Java 運行時。除了安全和合規之外,它還有助於建造具有成本效益和時間效率的容器。
BellSoft 管理多個公共映像,提供 JDK 類型(jre、jdk 或 jdk-all)、Java 版本(包括對最新 LTS 版本、Java 21 的支援)和 libc 類型(glibc 或 musl)的各種組合。現在,BellSoft 還提供了提供 CRaC 和 CDS(類別資料共用)的映像。
這些現成的鏡像使我們能夠將 CRaC 無縫整合到 Spring Boot 應用程式中。目前,這適用於 x86_64 架構的 JDK 17 和 21。 BellSoft 聲稱,採用 CRaC 的 Alpaquita 容器可將啟動時間加快 164 倍,並將鏡像縮小 1.1 倍。
影像大小的減少主要歸因於駐留集大小 (RSS) 的減小,RSS 是主記憶體 (RAM) 中進程佔用的記憶體部分。關鍵因素之一是帶有 CRaC 的 Liberica JDK 在檢查點之前執行完整的垃圾收集。
5. 讓事情發揮作用!
BellSsoft 的產品非常適合基於 Spring Boot 的 Java 應用程式。 Spring 推薦使用 BellSsoft Liberica JDK ,它是Spring Boot 中預設的 Java 執行時間。在我們的教程中,我們將使用 Spring Boot 應用程序,並使用 Alpaquita 容器執行 CRaC。
5.1.準備申請
在本教程中,我們將創建一個簡單的 Spring Boot 應用程式來探索 CRaC。我們將重複使用為上一個教學所建立的應用程式。在本教程中,我們將使用 Java 21 和 Spring Boot 3.2.5。 CRaC 在這個組合下運作良好。
但是,為了能夠使用 CRaC,我們需要將Maven 中央儲存庫中提供的 crac 套件新增為 Spring Boot 應用程式中的依賴項:
implementation("org.crac:crac:1.4.0")
現在,我們必須使用 Gradle 建置應用程序,在目錄“ ./build/libs ”中產生可執行 JAR:
$ ./gradlew clean build
現在我們已經創建了一個具有 CRaC 依賴項的簡單 Spring Boot 應用程序,我們需要使用支援 CRaC 的 JDK 來運行它。為此,我們將使用支援 CRaC 的 Alpaquita 容器。 BellSoft 在其Docker Hub 儲存庫上管理多個映像。
值得慶幸的是,所有支援 CRaC 的鏡像都有「 crac 」標籤。在本教程中,我們將在我們的電腦上提取一個這樣的圖像:
$ docker pull bellsoft/liberica-runtime-container:jdk-21-crac-slim-glibc
這裡,「 jdk-21-crac-slim-glibc 」是鏡像的標籤。至此,我們就可以嘗試 CRaC 的檢查點和恢復功能了。我們將了解 Alpaquita Containers 如何使其變得輕鬆且便攜。
5.2.啟動應用程式
首先,我們在「 ./build/libs 」中建立一個名為「 checkpoint 」的目錄來保存應用程式轉儲。現在,我們將使用先前提取的 Alpaquita 容器映像來運行我們在上一小節中創建的應用程式 JAR:
$ docker run -p 8080:8080 \
--rm --privileged \
-v $(pwd)/build/libs:/crac/ \
-w /crac \
-n fibonacci-crac \
bellsoft/liberica-runtime-container:jdk-21-crac-slim-glibc \
java -Xmx512m -XX:CRaCCheckpointTo=/crac/checkpoint \
-jar spring-bellsoft-0.0.1-SNAPSHOT.jar
讓我們花一些時間來理解這個命令。在這裡,我們將容器連接埠 8080 對應到主機連接埠8080 。
此外,我們已將應用程式 JAR 所在的目錄對應為容器內的捲,並將其用作工作目錄。最後,我們提供了運行 JAR 的 Java 指令以及一些必要的參數。
如果一切順利,我們應該能夠檢查容器日誌並驗證我們的應用程式確實已啟動:
2024-04-22T15:27:39.730Z INFO 129 --- [main]
com.baeldung.demo.Application : Started Application in 3.203 seconds (process running for 4.727)
現在,我們應該向應用程式執行一些請求,以便 JVM 可以獲得編譯後的熱程式碼以獲得更好的效能。不過,對於我們的簡單應用來說,這些影響可以忽略不計。
5.3.執行檢查點
此時我們已準備好執行應用程式的檢查點。但在此之前,讓我們檢查 RSS 的大小,將其與恢復後看到的內容進行比較。為此,我們需要應用程式的進程 ID (PID):
$ docker exec fibonacci-crac ps -a | pgrep spring-bellsoft
一旦我們獲得了 PID,我們就可以使用「 pmap 」指令來找出 RSS 的大小:
$ docker exec fibonacci-crac pmap -x <PID> | tail -1
total 4845016 134128 118736 0
此指令的輸出顯示 RSS 的大小(以千位元組為單位),即此處的第二個值 (134128)。
現在,讓我們在此狀態下執行應用程式的檢查點。我們可以透過使用「 jcmd 」命令來執行此操作,該命令向 JVM 發送命令以執行檢查點:
$ docker exec fibonacci-crac jcmd <PID> JDK.checkpoint
請注意,「 fibonacci-crac 」是我們在啟動容器時使用的名稱。此命令的結果是, Java 實例被轉儲並且容器被停止。應用程式轉儲由我們提到的位置的多個檔案組成:
$ ls
core-129.img core-139.img core-149.img core-198.img pagemap-129.img
core-130.img core-140.img core-150.img core-199.img pages-1.img
core-131.img core-141.img core-151.img core-200.img pstree.img
core-132.img core-142.img core-152.img dump4.log seccomp.img
core-133.img core-143.img core-154.img fdinfo-2.img stats-dump
core-134.img core-144.img core-155.img files.img timens-0.img
core-135.img core-145.img core-156.img fs-129.img
core-136.img core-146.img core-158.img ids-129.img
core-137.img core-147.img core-159.img inventory.img
core-138.img core-148.img core-160.img mm-129.img
此轉儲包括正在運行的 Java 應用程式的確切狀態以及有關堆疊、JIT 編譯的程式碼等的資訊。
5.4.從轉儲啟動應用程式
現在,我們要做的就是使用先前建立的應用程式轉儲來恢復應用程式的實例。這就像定期啟動應用程式一樣簡單:
docker run -p 8080:8080 \
--rm --privileged \
-v $(pwd)/build/libs:/crac/ \
-w /storage \
-n fibonacci-crac-from-checkpoint \
bellsoft/liberica-runtime-container:jdk-21-crac-slim-glibc \
java -XX:CRaCRestoreFrom=/crac/checkpoint
Like before, if everything goes smoothly, we should be able to verify this from the application log:
2024-04-22T16:02:21.582Z INFO 129 --- [Attach Listener]
oscsupport.DefaultLifecycleProcessor :
Spring-managed lifecycle restart completed (restored JVM running for 1494 ms)
正如我們所看到的,應用程式已恢復到建立此檢查點時的狀態。我們可以注意到恢復速度要快得多,但是對於這個簡單的應用程式來說不太明顯。
5.5.結果概覽
正如我們在獲取檢查點之前所做的那樣,讓我們在恢復後(最好是在向應用程式發出一些請求後)再次檢查 RSS 的大小:
$ docker exec fibonacci-crac-from-checkpoint pmap -x 129 | tail -1
total 5044580 120261 62728 0
正如我們所看到的,該值 (120261) 小於我們在檢查點之前註意到的值。不過,對於我們在本教程中使用的應用程式的性質來說,這一點不太明顯。
我們也可能注意到,恢復後的 RSS 在第一個請求後增加,然後達到某個穩定狀態。但是,該值仍然低於我們在獲取應用程式轉儲之前觀察到的 RSS。
RSS 的減少主要歸因於 Liberica JDK 和 CRaC 在檢查點之前執行完整的垃圾收集。恢復時,HotSpot 虛擬機器會將部分本機記憶體傳回給作業系統,其中包括 GC 期間釋放的頁面。
6. CRaC 與 GraalVM 原生鏡像
我們討論的 Java 問題從它誕生以來就一直存在。但是,直到最近我們才提出了在雲端上盡可能具有成本效益的嚴格要求。實現這一目標的關鍵推動因素之一是“縮放至零”,這意味著在不使用時自動將所有資源縮放至零。
當然,這要求我們的應用程式能夠快速啟動並開始回應請求。所以,CRaC之前的解決方案也是針對這項需求而提出的。其中, GraalVM Native Image解決了更廣泛的目標,包括緩慢的啟動時間。
因此,有必要將 CRaC 與 GraalVM Native Image 進行比較。 GraalVM Native Image 是一種提前 (AOT) 編譯器,可為 Linux、Windows 和 macOS 建立本機執行檔。 BellSoft提供了Liberica Native Image Kit來基於GraalVM生成原生鏡像:
與 CRaC 一樣,GraalVM Native Image 可以幫助顯著縮短啟動時間。但 GraalVM在記憶體使用量更少、安全性更高和應用程式檔案大小更小方面表現更好。此外,我們可以為多個作業系統產生 GraalVM Native Image。
然而,使用 GraalVM,我們無法使用一些 Java 功能,例如在執行時間載入任意類別。此外,許多可觀察性和測試框架不支援 GraalVM,因為它不允許在執行時間動態產生程式碼,而且我們無法執行 Java 代理程式。
那麼 CRaC 和 GraalVM 原生 Image 哪一個比較好呢?嗯,這兩種技術都有自己的空間。然而,GraalVM Native Image 解決了與 CRaC 相同的問題,但有更多的限制,並且可能會帶來更昂貴的故障排除體驗。
七、結論
在本教程中,我們了解了 CRaC 是什麼以及如何在雲端原生環境中利用它來發揮我們的優勢。此外,我們還審查了 BellSoft 的產品,例如支援 CRaC 的 Alpaquita Containers。最後,我們開發了一個 Spring Boot 應用程式並看到了 CRaC 的實際應用。