使用 Spring Boot 3.2 和 CRaC 實現更快啟動

banq發表於2024-07-03

藉助 Spring Boot 3.2 和 Spring Framework 6.1,我們獲得了對檢查點協調恢復(CRaC) 的支援,這是一種使 Java 應用程式能夠更快啟動的機制。藉助 Spring Boot,我們可以以一種簡化的方式使用 CRaC,即啟動時自動檢查點/恢復。

這篇博文將展示一個示例,其中 Spring Boot 應用程式的啟動時間減少了 90%。

CRAC 簡介、優勢和挑戰
檢查點協調恢復 (CRaC)是 OpenJDK 中的一項功能,最初由Azul開發,旨在透過允許 Java 應用程式快速恢復到之前儲存的狀態來提高其啟動效能。CRaC 使 Java 應用程式能夠儲存其在特定時間點(檢查點)的狀態,然後在稍後從該狀態恢復。這對於快速啟動時間至關重要的場景特別有用,例如無伺服器環境、微服務,以及通常必須能夠快速擴充套件其例項並在不使用時支援縮減到零的應用程式。

 CRAC 的工作原理
1、檢查點建立:在應用程式執行期間的選定點處,將建立一個檢查點。這涉及捕獲 Java 應用程式的整個狀態,包括堆、堆疊和所有活動執行緒。然後將狀態序列化並儲存到檔案系統。在檢查點過程中,應用程式通常會暫停以確保捕獲一致的狀態。此暫停經過協調,以最大限度地減少中斷並確保應用程式可以正確恢復。

在進行檢查點之前,通常會嚮應用程式傳送一些請求以確保它已預熱,即已載入所有相關類,並且 JVM HotSpot 引擎有機會根據執行時的使用方式最佳化位元組碼。

執行檢查點的命令:

 java -XX:CRaCCheckpointTo=<some-folder> -jar my_app.jar
 # Make calls to the app to warm up the JVM...
 jcmd my_app.jar JDK.checkpoint

2、狀態恢復:當應用程式從檢查點啟動時,先前儲存的狀態將從檔案系統反序列化並重新載入到記憶體中。然後,應用程式將從檢查點的確切位置繼續執行,繞過通常的啟動順序。

從檢查點恢復的命令:
java -XX:CRaCRestoreFrom=<some-folder>

從檢查點恢復允許應用程式跳過初始啟動過程,包括類載入、預熱初始化和其他啟動例程,從而顯著減少啟動時間。

挑戰和考慮
與任何新技術一樣,CRaC 也面臨著一系列新的挑戰和考慮:

  1. 狀態管理:在建立檢查點之前,必須關閉開啟的檔案和與外部資源(如資料庫)的連線。還原後,必須重新開啟它們。CRaC 公開了一個 Java 生命週期介面,應用程式可以使用它來處理此問題,org.crac.Resource使用回撥方法beforeCheckpoint和afterRestore。
  2. 敏感資訊:儲存在 JVM 記憶體中的憑據和機密將序列化到檢查點建立的檔案中。因此,這些檔案需要受到保護。另一種方法是針對使用其他憑據的臨時環境執行檢查點命令,並在恢復時替換憑據。
  3. Linux 依賴性:檢查點技術基於 Linux 的一項功能,稱為CRIU,“使用者空間中的檢查點/恢復”。此功能僅適用於 Linux,因此在 Mac 或 Windows PC 上測試 CRaC 的最簡單方法是將應用程式打包到 Linux Docker 映像中。
  4. 需要 Linux 許可權:CRIU 需要特殊的 Linux 許可權,因此構建 Docker 映象和建立 Docker 容器的 Docker 命令也需要 Linux 許可權才能執行。
  5. 儲存開銷:儲存和管理檢查點資料需要額外的儲存資源,檢查點大小會影響恢復時間。還需要原始 jar 檔案才能從檢查點重新啟動 Java 應用程式。

SPRING BOOT 3.2 與 CRAC 整合
Spring Boot 3.2(以及底層 Spring Framework)有助於處理關閉和重新開啟與外部資源的連線。在建立檢查點之前,Spring 會停止所有正在執行的 bean,讓它們有機會在需要時關閉資源。還原後,相同的 bean 會重新啟動,從而允許 bean 重新開啟與資源的連線。

基於 Spring Boot 3.2 的應用程式唯一需要新增的是對crac- 庫的依賴。使用 Gradle,它在檔案中如下所示gradle.build:

dependencies {
    implementation 'org.crac:crac'

正常的 Spring Boot BOM 機制負責對crac依賴項進行版本控制。

Spring Boot 處理的連線自動關閉和重新開啟通常有效。不幸的是,在撰寫這篇博文時,一些 Spring 模組缺少這種支援。為了跟蹤 Spring 生態系統中 CRaC 支援的狀態,我們建立了一個專用測試專案Spring Lifecycle Smoke Tests 。當前狀態可在專案的[url=https://github.com/spring-projects/spring-lifecycle-smoke-tests/blob/main/STATUS.adoc]狀態頁面[/url]上找到。

如果需要,應用程式可以透過實現上述Resource介面來註冊要在檢查點之前和恢復之後呼叫的回撥方法。本博文中使用的微服務已擴充套件為註冊回撥方法,以演示如何使用它們。程式碼如下所示:

import org.crac.*;

public class MyApplication implements Resource {

  public MyApplication() {
    Core.getGlobalContext().register(this);
  }

  @Override
  public void beforeCheckpoint(Context<? extends Resource> context) {
    LOG.info(<font>"CRaC's beforeCheckpoint callback method called...");
  }

  @Override
  public void afterRestore(Context<? extends Resource> context) {
    LOG.info(
"CRaC's afterRestore callback method called...");
  }
}

與上面描述的預設按需替代方案相比,Spring Boot 3.2 提供了一種簡化的替代方案來設定檢查點。它被稱為啟動時自動檢查點/恢復。它透過將 JVM 系統屬性新增-Dspring.context.checkpoint=onRefresh到java -jar命令來觸發。

設定後,應用程式啟動時會自動建立檢查點。檢查點是在 Spring bean 建立但尚未啟動之後建立的,即在大多數初始化工作之後但該應用程式啟動之前。有關詳細資訊,請參閱Spring Boot 文件Spring Framework 文件

使用自動檢查點,我們無法獲得完全預熱的應用程式,並且必須在構建時指定執行時配置。這意味著生成的 Docker 映像(映象)將是特定於執行時的,並且包含來自配置的敏感資訊,例如憑據和機密。因此,Docker 映像映象必須儲存在私有且受保護的容器登錄檔中。

介紹了 CRaC 和 Spring Boot 3.2 對 CRaC 的支援後,讓我們看看如何為使用 CRaC 的 Spring Boot 應用程式建立 Docker 映象。

 使用 DOCKERFILE 建立基於 CRAC 的 DOCKER 映象
在學習如何使用 CRaC 時,我研究了幾篇關於在 Spring Boot 3.2 應用程式中使用 CRaC 的部落格文章。它們都使用相當複雜的 bash 指令碼(取決於您的 bash 經驗),使用 Docker 命令(如docker run、docker exec和)docker commit。儘管它們有效,但與使用 Dockerfile 生成 Docker 映像相比,這似乎是一種不必要的複雜解決方案。

最後,我們解決了使用 Dockerfile 構建 Spring Boot 應用程式所產生的問題,該應用程式可以使用 Docker 映象中的 CRaC 快速恢復。

生成的 Dockerfilecrac/Dockerfile-crac-automatic如下所示:

# syntax=docker/dockerfile:1.3-labs

FROM azul/zulu-openjdk:21-jdk-crac

ADD build/libs<font>/*.jar app.jar

RUN --security=insecure \
  SPRING_PROFILES_ACTIVE=docker,crac \
  java -Dspring.context.checkpoint=onRefresh \
       -XX:CRaCCheckpointTo=checkpoint -jar app.jar \
  || if [ $? -eq 137 ]; then return 0; else return 1; fi

EXPOSE 8080
ENTRYPOINT ["java", "-XX:CRaCRestoreFrom=checkpoint"]

注意:所有微服務都使用同一個 Dockerfile 來建立其 Docker 映象的 CRaC 版本。

嘗試使用自動檢查點/恢復功能進行 CRAC
執行以下命令從 GitHub 獲取原始碼,跳轉到檔案Chapter06夾,檢出分支SB3.2-crac-automatic,並確保使用 Java 21 JDK(這裡使用 Eclipse Temurin):

git clone https://github.com/PacktPublishing/Microservices-with-Spring-Boot-and-Spring-Cloud-Third-Edition.git
cd Microservices-with-Spring-Boot-and-Spring-Cloud-Third-Edition/Chapter06
git checkout SB3.2-crac-automatic
sdk use java 21.0.3-tem

從編譯微服務原始碼開始:

./gradlew build

如果尚未建立,請使用以下命令建立不安全的構建器:

docker buildx create --name insecure-builder --buildkitd-flags '--allow-insecure-entitlement security.insecure'

現在我們可以構建一個 Docker 映象,其中構建使用以下命令為每個微服務執行 CRaC 檢查點:

docker buildx --builder insecure-builder build --allow security.insecure -f crac/Dockerfile-crac-automatic -t product-composite-crac --load microservices/product-composite-service

docker buildx --builder insecure-builder build --allow security.insecure -f crac/Dockerfile-crac-automatic -t product-crac --load microservices/product-service

docker buildx --builder insecure-builder build --allow security.insecure -f crac/Dockerfile-crac-automatic -t recommendation-crac --load microservices/recommendation-service

docker buildx --builder insecure-builder build --allow security.insecure -f crac/Dockerfile-crac-automatic -t review-crac --load microservices/review-service

執行端到端測試
要啟動系統環境,我們將使用 Docker Compose。由於 CRaC 需要特殊的 Linux 許可權,因此原始碼附帶了一個特定於 CRaC 的 docker-compose 檔案。透過指定以下內容,crac/docker-compose-crac.yml為每個微服務賦予所需的許可權:CHECKPOINT_RESTORE

cap_add:
  - CHECKPOINT_RESTORE

注意: CRaC 上的幾篇部落格文章建議使用特權容器,即使用 Docker Compose 檔案啟動它們run --privleged或新增它們。這是一個非常糟糕的想法,因為控制此類容器的攻擊者可以輕鬆控制執行 Docker 的主機。有關更多資訊,請參閱 Docker 的執行時特權和 Linux 功能privileged: true文件。

CRaC 特定 Docker Compose 檔案的最後新增內容是 MySQL 的卷對映

volumes:
  - "./sql-scripts/create-tables.sql:/docker-entrypoint-initdb.d/create-tables.sql"

使用這個 Docker Compose 檔案,我們可以啟動系統環境並使用以下命令執行端到端驗證指令碼:

export COMPOSE_FILE=crac/docker-compose-crac.yml
docker compose up -d

讓我們首先驗證 CRaCafterRestore回撥方法是否被呼叫:

docker compose logs | grep "CRaC's afterRestore callback method called..."

期望類似以下內容:

...ReviewServiceApplication           : CRaC's afterRestore callback method called...
...RecommendationServiceApplication   : CRaC's afterRestore callback method called...
...ProductServiceApplication          : CRaC's afterRestore callback method called...
...ProductCompositeServiceApplication : CRaC's afterRestore callback method called...

現在,執行端到端驗證指令碼:

./test-em-all.bash

如果指令碼以類似以下內容的日誌輸出結束:

End, all tests OK: Fri Jun 28 17:40:43 CEST 2024


...這意味著所有測試執行正常,並且微服務的行為符合預期。

使用以下命令關閉系統環境:

docker compose down
unset COMPOSE_FILE


驗證微服務從 CRaC 檢查點啟動時行為正確後,我們可以將它們的啟動時間與未使用 CRaC 啟動的微服務進行比較。

總結
檢查點協調恢復 (CRaC)是 OpenJDK 中的一項強大功能,它允許 Java 應用程式從之前儲存的狀態(即檢查點)恢復,從而提高 Java 應用程式的啟動效能。藉助 Spring Boot 3.2,我們還可以使用 CRaC 建立檢查點的簡化方法,即啟動時自動檢查點/恢復。

本博文中的測試表明,啟動效能提高了 10 倍,即在使用啟動時自動檢查點/恢復時,啟動時間減少了 90% 。

這篇博文還解釋瞭如何使用 Dockerfile 構建使用 CRaC 的 Docker 映象,而不是大多數博文所建議的複雜 bash 指令碼。然而,這也帶來了一些挑戰,比如使用自定義 Docker 構建器進行特權構建,正如博文中所述。

使用在啟動時自動檢查點/恢復建立的 Docker映象需要付出代價。Docker 映象將包含執行時特定的敏感資訊,例如執行時連線資料庫的憑據。因此,必須保護它們以防止未經授權的使用。

Spring Boot 對 CRaC 的支援並未完全覆蓋 Spring 生態系統中的所有模組,因此必須採用一些解決方法,例如在使用 Spring Data JPA 時。

此外,在使用自動檢查點/啟動時恢復時,JVM HotSpot 引擎無法在檢查點之前預熱。如果處理的第一個請求的最佳執行時間很重要,那麼自動檢查點/啟動時恢復可能不是最佳選擇。
 

相關文章