1. 總覽
在本程式碼研究室中,我們將瞭解 Spring Native 專案,並建構使用該專案的應用程式,然後在 Google Cloud 上部署該應用程式。
我們將介紹其元件、專案的近期歷史記錄、部分用途,當然還有在專案中使用這項功能所需的步驟。
Spring Native 專案目前處於實驗階段,因此需要進行一些特定設定才能開始使用。不過,如同在 SpringOne 2021 宣布的內容,Spring Native 將整合至 Spring Framework 6.0 和 Spring Boot 3.0,並提供第一級支援,因此在發布前幾個月,正是仔細研究該專案的最佳時機。
雖然即時編譯已針對長時間執行程序等項目進行最佳化,但在某些用途中,預先編譯的應用程式效能會更好,我們會在程式碼研究室中討論這項議題。
在接下來的研究室中
- 使用 Cloud Shell
- 啟用 Cloud Run API
- 建立及部署 Spring Native 應用程式
- 將這類應用程式部署至 Cloud Run
軟硬體需求
- Google Cloud Platform 專案 (含有效 GCP 帳單帳戶)
- 已安裝 gcloud CLI,或可存取 Cloud Shell
- 基本的 Java + XML 技能
- 具備常見 Linux 指令的實務知識
問卷調查
您要如何使用這個教學課程?
您對使用 Java 的體驗評價為何?
請評估你使用 Google Cloud 服務的體驗。
2. 背景
Spring Native 專案會運用多項技術,為開發人員提供原生應用程式效能。
如要全面瞭解 Spring Native,建議您先瞭解其中幾項元件技術,以及這些技術可為我們帶來哪些好處,以及如何搭配運作。
AOT 編譯
當開發人員在編譯時正常執行 javac 時,.java 原始碼會編譯成以位元碼編寫的 .class 檔案。這個位元碼只能由 Java 虛擬機器解讀,因此 JVM 必須在其他機器上解讀這個程式碼,才能執行我們的程式碼。
這個程序可讓 Java 提供簽名可攜性,讓我們可以「一次編寫,隨處執行」,但與執行原生程式碼相比,這個程序的成本較高。
幸好,大多數 JVM 實作方式都會使用即時編譯,以減輕這類解譯成本。這項功能會計算函式的叫用次數,如果叫用次數足以達到閾值 ( 預設為 10,000),就會在執行階段編譯為原生程式碼,以免進一步解讀成本高昂。
提前編譯則採用相反的方法,在編譯期間將所有可存取的程式碼編譯為原生可執行檔。這麼做可在執行階段換取記憶體效率和其他效能提升。
這當然是一個取捨,而且不一定值得採取。不過,在某些用途上,AOT 編譯可發揮效用,例如:
- 啟動時間至關重要的短暫應用程式
- 記憶體受限的環境,JIT 可能會耗費過多資源
有趣的是,AOT 編譯在 JDK 9 中推出時,是作為實驗功能,但這項實作維護成本高昂,且從未廣泛採用,因此在 Java 17 中悄悄移除,以便開發人員只使用 GraalVM。
GraalVM
GraalVM 是經過高度最佳化的開放原始碼 JDK 發行版本,可提供極快的啟動時間、AOT 原生映像檔編譯,以及多語言功能,讓開發人員在單一應用程式中混合多種語言。
GraalVM 目前正積極開發中,不斷新增功能並改善現有功能,因此建議開發人員密切留意相關動態。
以下是近期的里程碑:
- 全新的使用者友善原生圖片建構輸出內容 ( 2021-01-18)
- Java 17 支援 ( 2022-01-18)
- 預設啟用多層級編譯功能,以改善多語言編譯時間 ( 2021-04-20)
Spring Native
簡單來說,Spring Native 可讓您使用 GraalVM 的原生圖像編譯器,將 Spring 應用程式轉換為原生可執行檔。
這個程序會在編譯期間對應用程式執行靜態分析,找出應用程式中可從進入點存取的所有方法。
這基本上會為應用程式建立「封閉式」概念,在這種情況下,系統會假設所有程式碼在編譯時已知,且不允許在執行階段載入新程式碼。
請注意,原生圖片產生作業是記憶體密集型程序,所需時間比編譯一般應用程式還長,且會對 Java 的特定部分施加限制。
在某些情況下,應用程式不必變更程式碼即可與 Spring Native 搭配運作。不過,在某些情況下,您必須設定特定原生設定才能正常運作。在這種情況下,Spring Native 通常會提供原生提示,簡化這項程序。
3. 設定/預先作業
開始實作 Spring Native 前,我們需要建立及部署應用程式,以便建立效能基準,以利日後與原生版本進行比較。
1. 建立專案
首先,我們會從 start.spring.io 取得應用程式:
curl https://start.spring.io/starter.zip -d dependencies=web \ -d javaVersion=11 \ -d bootVersion=2.6.4 -o io-native-starter.zip
這個啟動應用程式使用 Spring Boot 2.6.4,這是 spring-native 專案支援的最新版本。
請注意,自 GraalVM 21.0.3 發布以來,您也可以使用 Java 17 執行此範例。我們仍會在本教學課程中使用 Java 11,以減少涉及的設定。
在指令列中取得 ZIP 檔案後,我們可以為專案建立子資料夾,然後解壓縮該資料夾:
mkdir spring-native cd spring-native unzip ../io-native-starter.zip
2. 程式碼變更
開啟專案後,我們會快速新增生命徵象,並在執行時展示 Spring Native 的效能。
編輯 DemoApplication.java,使其符合以下內容:
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.Duration;
import java.time.Instant;
@RestController
@SpringBootApplication
public class DemoApplication {
private static Instant startTime;
private static Instant readyTime;
public static void main(String[] args) {
startTime = Instant.now();
SpringApplication.run(DemoApplication.class, args);
}
@GetMapping("/")
public String index() {
return "Time between start and ApplicationReadyEvent: "
+ Duration.between(startTime, readyTime).toMillis()
+ "ms";
}
@EventListener(ApplicationReadyEvent.class)
public void ready() {
readyTime = Instant.now();
}
}
此時,我們的基準應用程式已準備就緒,因此您可以自行建構映像檔並在本機執行,以便瞭解啟動時間,然後再將應用程式轉換為原生應用程式。
如要建構映像檔,請按照下列步驟操作:
mvn spring-boot:build-image
您也可以使用 docker images demo
來瞭解基準圖片的大小:
如要執行應用程式,請按照下列步驟操作:
docker run --rm -p 8080:8080 demo:0.0.1-SNAPSHOT
3. 部署基準應用程式
有了應用程式後,我們會部署應用程式並記下時間,以便稍後與原生應用程式的啟動時間進行比較。
視您要建構的應用程式類型而定,您可以選擇多種不同的內容代管服務。
不過,由於本範例是相當簡單、直接的網頁應用程式,因此我們可以簡化程序,並仰賴 Cloud Run。
如果您要在自己的電腦上操作,請務必安裝並更新 gcloud CLI 工具。
如果您使用的是 Cloud Shell,系統會自動處理所有事物,您只需在來源目錄中執行下列指令即可:
gcloud run deploy
4. 應用程式設定
1. 設定 Maven 存放區
由於這個專案仍處於實驗階段,我們必須設定應用程式,才能找到 Maven 中央存放區中沒有的實驗構件。
這項操作會在 pom.xml 中加入下列元素,您可以使用所選編輯器執行這項操作。
將下列 repositories 和 pluginRepositories 區段新增至 pom:
<repositories>
<repository>
<id>spring-release</id>
<name>Spring release</name>
<url>https://repo.spring.io/release</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-release</id>
<name>Spring release</name>
<url>https://repo.spring.io/release</url>
</pluginRepository>
</pluginRepositories>
2. 新增依附元件
接下來,請新增 spring-native 依附元件,這是將 Spring 應用程式做為原生映像檔執行時所需的依附元件。注意:如果您使用 Gradle,則不需要執行這個步驟
<dependencies>
<!-- ... -->
<dependency>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-native</artifactId>
<version>0.11.2</version>
</dependency>
</dependencies>
3. 新增/啟用我們的外掛程式
接著新增 AOT 外掛程式,改善原生圖片的相容性和占用空間 ( 詳情):
<plugins>
<!-- ... -->
<plugin>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-aot-maven-plugin</artifactId>
<version>0.11.2</version>
<executions>
<execution>
<id>generate</id>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
現在,我們將更新 spring-boot-maven-plugin,啟用原生映像檔支援功能,並使用 paketo 建構工具建構原生映像檔:
<plugins>
<!-- ... -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<builder>paketobuildpacks/builder:tiny</builder>
<env>
<BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>
</env>
</image>
</configuration>
</plugin>
</plugins>
請注意,tiny builder 圖片只是多種選項之一。這對我們的用途來說是個不錯的選擇,因為它幾乎沒有額外的程式庫和公用程式,有助於盡量減少攻擊面。
舉例來說,如果您正在建構的應用程式需要存取某些常見的 C 程式庫,或是您尚不確定應用程式的需求,則 full-builder 可能更適合。
5. 建構並執行原生應用程式
完成所有設定後,我們應該就能建構映像檔,並執行原生編譯應用程式。
執行建構作業前,請留意下列事項:
- 這項作業的時間會比一般建構作業長 (幾分鐘)
- 這項建構程序可能會占用大量記憶體 (幾 GB)
- 這個建構程序需要 Docker 守護程式可供存取
- 雖然本例會手動執行這個程序,但您也可以設定建構階段,自動觸發原生建構設定檔。
如要建構映像檔,請按照下列步驟操作:
mvn spring-boot:build-image
建構完成後,我們就可以查看原生應用程式的實際運作情形!
如要執行應用程式,請按照下列步驟操作:
docker run --rm -p 8080:8080 demo:0.0.1-SNAPSHOT
在這個階段,我們可以清楚瞭解原生應用程式方程式的兩個方面。
雖然在編譯期間會耗費一些時間和額外記憶體,但換來的是,應用程式可大幅加快啟動速度,並大幅減少記憶體用量 (取決於工作負載)。
如果我們執行 docker images demo
來比較原生圖片與原始圖片的大小,可以發現前者大幅縮小:
另外,請注意,在更複雜的用途中,您需要進行額外修改,才能讓 AOT 編譯器瞭解應用程式在執行階段的運作方式。因此,某些可預測的工作負載 (例如批次工作) 可能非常適合這項功能,但其他工作負載可能需要更大的提升。
6. 部署原生應用程式
如要將應用程式部署到 Cloud Run,我們必須將原生映像檔放入 Artifact Registry 等套件管理工具。
1. 準備 Docker 存放區
我們可以透過建立存放區來開始這項程序:
gcloud artifacts repositories create native-image-docker-repo --repository-format=docker \
--location=us-central1 --description="Repository for our native images"
接下來,我們要確認已驗證,可將資料推送至新的註冊中心。
gcloud 命令列介面可大幅簡化這項程序:
gcloud auth configure-docker us-central1-docker.pkg.dev
2. 將映像檔推送至 Artifact Registry
接下來,我們會為圖片加上標記:
export PROJECT_ID=$(gcloud config list --format 'value(core.project)')
docker tag demo:0.0.1-SNAPSHOT \
us-central1-docker.pkg.dev/$PROJECT_ID/native-image-docker-repo/quickstart-image:tag2
然後,我們可以使用 docker push
將其傳送至 Artifact Registry:
docker push us-central1-docker.pkg.dev/$PROJECT_ID/native-image-docker-repo/quickstart-image:tag2
3. 部署至 Cloud Run
我們現在可以將儲存在 Artifact Registry 中的映像檔部署至 Cloud Run:
gcloud run deploy --image us-central1-docker.pkg.dev/$PROJECT_ID/native-image-docker-repo/quickstart-image:tag2
由於我們已以原生映像檔的形式建構及部署應用程式,因此可以放心,應用程式在執行時會充分利用基礎架構成本。
歡迎自行比較基準應用程式與這個新的原生應用程式,看看兩者的啟動時間有何差異!
7. 摘要/清理
恭喜您在 Google Cloud 上建構及部署 Spring Native 應用程式!
希望這份教學課程能讓您更熟悉 Spring Native 專案,並在日後需要時考慮使用此專案。
選用:清除及/或停用服務
無論您是為了這個程式碼研究室建立 Google Cloud 專案,還是要重複使用現有專案,都請小心避免產生不必要的資源費用。
8. 其他資源
雖然 Spring Native 專案目前是全新的實驗性專案,但我們已提供豐富的優質資源,協助早期採用者排解問題並參與其中:
其他資源
以下是與本教學課程相關的線上資源:
授權
這項內容採用的授權為 Creative Commons 姓名標示 2.0 通用授權。