在 Google Cloud 中使用 Spring Native

1. 總覽

在本程式碼研究室中,我們會學習 Spring Native 專案、建構使用該專案的應用程式,並部署至 Google Cloud。

以下將介紹相關元件、專案的近期記錄、部分用途,以及在專案中使用專案所需的步驟。

Spring Native 專案目前仍在實驗階段,因此需要進行一些設定才能開始使用。不過,如 2021 年 SpringOne 公告所述,Spring Native 已與 Spring Framework 6.0 和 Spring Boot 3.0第一級支援整合,因此現在正是在推出專案前幾個月,先仔細看看專案。

雖然及時編譯已針對長時間執行程序等項目進行最佳化,但在某些用途中,預先編譯的應用程式效能較佳,我們會在程式碼研究室中討論這點。

在接下來的研究室中

  • 使用 Cloud Shell
  • 啟用 Cloud Run API
  • 建立及部署 Spring Native 應用程式
  • 將這類應用程式部署至 Cloud Run

軟硬體需求

問卷調查

您會如何使用這個教學課程?

僅供閱讀 閱讀並完成練習

您覺得 Java 的使用體驗如何?

新手 中級 還算容易

針對使用 Google Cloud 服務的經驗,您會給予什麼評價?

新手 中級 還算容易

2. 背景

Spring Native 專案運用數種技術,為開發人員提供原生應用程式效能。

如要瞭解 Spring Native 的幾項要素技術、這些技術能為我們帶來什麼好處,以及兩者搭配運作的方式,我們大可助您一臂之力。

AOT 編譯

當開發人員在正常編譯期間執行 javac 時,我們的 .java 原始碼會編譯成以位元碼編寫的 .class 檔案。只有 Java 虛擬機器可以理解此位元碼,因此 JVM 必須在其他機器上解讀這個程式碼,才能執行我們的程式碼。

透過這個程序,Java 的特徵碼可攜性- 讓我們「編寫一次並在任何地方執行」,但和執行原生程式碼相比的成本高昂。

幸運的是,大部分的 JVM 實作都會使用及時編譯,以降低此解讀成本。方法是計算函式的叫用次數。如果叫用頻率足以通過閾值 ( 預設為 10,000),則會在執行階段編譯為原生程式碼,以防止進一步的高成本解讀。

預先編譯會採取相反的方法,在編譯期間將所有可連線的程式碼編譯至原生執行檔。這不僅有益於可攜性,也能提高記憶體效率,以及在執行階段提升效能。

5042e8e62a05a27.png

這當然是得權衡利器,而且不一定值得一試。不過,AOT 編譯能在某些特定用途中發揮效用,例如:

  • 運作時間較短且會影響啟動時間的應用程式
  • 記憶體有限的環境,其中 JIT 可能太昂貴

事實上,AOT 編譯是 JDK 9 中的實驗功能,但這個實作項目維護成本高昂且從未遭到攻擊,因此在 Java 17 中完全移除,希望開發人員只使用 GraalVM

GraalVM

GraalVM 是經過高度最佳化的開放原始碼 JDK 發行版本,具備極快速的啟動時間、AOT 原生圖像編譯和 Polyglot 功能,可讓開發人員將多種語言混成單一應用程式。

GraalVM 目前正在積極開發,會持續加入新功能並持續改善現有功能,因此我建議開發人員隨時留意最新消息。

新的里程碑為:

  • 易於使用的全新原生映像檔建構輸出內容 ( 2021-01-18)
  • 支援 Java 17 ( 2022-01-18)
  • 預設啟用多層級編譯,以縮短 Polyglot 的編譯時間 ( 2021-04-20)

春季原生廣告

簡單來說,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 Project 在本文撰寫時支援的的最新版本。

請注意,自推出 GraalVM 21.0.3 以來,此範例也可以使用 Java 17。本教學課程仍會使用 Java 11,將相關的設定降到最低。

在指令列取得 ZIP 後,我們可以為專案建立子目錄,並將資料夾解壓縮至該目錄:

mkdir spring-native

cd spring-native

unzip ../io-native-starter.zip

2. 程式碼變更

專案開啟後,我們會快速加入生命徵象,並在執行後展示春季原生成效。

請按照下列方式編輯 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 來瞭解基準圖片的大小:6ecb403e9af1475e.png

如要執行應用程式:

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 中,而您可以在所選編輯器中進行。

將下列存放區和 pluginRepositories 部分新增至模組:

<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 應用程式的必要項目。注意:如果您使用的是 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>

請注意,小型建構工具映像檔只是選項之一。這很適合我們的用途,因為其中有極少數的程式庫和公用程式,有助於將受攻擊面降至最低。

舉例來說,如果您要建構的應用程式需要存取某些常見 C 程式庫,或者您不確定應用程式的需求,那麼使用完整建構工具或許會更為適合。

5. 建構並執行原生應用程式

完成上述所有步驟後,我們應該就能建構映像檔並執行我們編譯的原生應用程式。

執行建構之前,請注意下列事項:

  • 這項作業的執行時間比一般版本更長 (幾分鐘)。d420322893640701.png
  • 這項建構程序可能會佔用大量記憶體 (幾 GB)cda24e1eb11fdbea.png
  • 必須能夠連線至 Docker Daemon,才能執行這項建構程序
  • 在這個範例中,我們會手動完成整個程序,您也可以設定建構階段,自動觸發原生建構設定檔

如何建立映像檔:

mvn spring-boot:build-image

建構完成後,就可以查看原生應用程式的實際運作情形!

如要執行應用程式:

docker run --rm -p 8080:8080 demo:0.0.1-SNAPSHOT

現階段,我們可以看到原生應用程式方程式的兩邊。

我們在編譯時會得到一些時間和額外的記憶體用量,但就像這樣,我們得到的應用程式可以更快啟動,並消耗更多的記憶體 (視工作負載而定)。

如果我們執行 docker images demo 來比較原生圖片大小與原始圖片的大小,結果會大幅減少:

e667f65a011c1328.png

還須注意,在較複雜的用途中,您還需要進行其他修改,才能告知 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 CLI 可以大幅簡化這項程序:

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

應用程式是建立並部署為原生映像檔,可以放心,應用程式也能在執行時充分利用基礎架構成本。

您可以自行決定基準應用程式的啟動時間,以及這個新的原生應用程式。

6dde63d35959b1bb.png

7. 摘要/清除

恭喜您在 Google Cloud 上建構及部署 Spring Native 應用程式!

希望本教學課程可鼓勵您更加熟悉 Spring Native 專案,並留意日後的需求。

選用:清除及/或停用服務

無論您是在這個程式碼研究室中建立 Google Cloud 專案,還是重複使用現有專案,都請謹慎避免所用資源產生不必要的費用。

您可以刪除或停用我們建立的 Cloud Run 服務、刪除託管的映像檔,或是關閉整個專案。

8. 其他資源

雖然 Spring Native 專案目前是全新的實驗專案,但目前已有豐富的資源,可協助早期採用者排解問題及參與相關活動:

其他資源

以下為可能與本教學課程相關的線上資源:

授權

這項內容採用的是創用 CC 姓名標示 2.0 通用授權。