Quảng cáo gốc vào mùa xuân trên Google Cloud

1. Tổng quan

Trong lớp học lập trình này, chúng ta sẽ tìm hiểu về dự án Spring Native, xây dựng một ứng dụng sử dụng dự án này và triển khai ứng dụng đó trên Google Cloud.

Chúng ta sẽ tìm hiểu các thành phần của thư viện, lịch sử gần đây của dự án, một số trường hợp sử dụng và tất nhiên là các bước cần thiết để bạn sử dụng thư viện này trong dự án của mình.

Dự án Spring Native hiện đang trong giai đoạn thử nghiệm, vì vậy, bạn sẽ cần một số cấu hình cụ thể để bắt đầu. Tuy nhiên, như đã thông báo tại SpringOne 2021, Spring Native sẽ được tích hợp vào Spring Framework 6.0 và Spring Boot 3.0 với dịch vụ hỗ trợ hàng đầu. Vì vậy, đây là thời điểm hoàn hảo để xem xét kỹ hơn dự án vài tháng trước khi phát hành.

Mặc dù tính năng biên dịch đúng thời điểm đã được tối ưu hoá rất tốt cho các hoạt động như quy trình chạy trong thời gian dài, nhưng có một số trường hợp sử dụng nhất định mà các ứng dụng được biên dịch trước lại hoạt động hiệu quả hơn. Chúng ta sẽ thảo luận về những trường hợp này trong lớp học lập trình.

Bạn sẽ tìm hiểu cách

  • Sử dụng Cloud Shell
  • Bật Cloud Run API
  • Tạo và triển khai ứng dụng Spring Native
  • Triển khai ứng dụng như vậy lên Cloud Run

Bạn cần có

Khảo sát

Bạn sẽ sử dụng hướng dẫn này như thế nào?

Chỉ đọc qua Đọc và hoàn thành bài tập

Bạn đánh giá thế nào về trải nghiệm của mình với Java?

Tân binh Trung cấp Thành thạo

Bạn đánh giá trải nghiệm sử dụng các dịch vụ của Google Cloud như thế nào?

Tân binh Trung cấp Thành thạo

2. Thông tin khái quát

Dự án Spring Native sử dụng một số công nghệ để mang lại hiệu suất ứng dụng gốc cho nhà phát triển.

Để hiểu rõ Spring Native, bạn nên tìm hiểu một số công nghệ thành phần này, những gì chúng cung cấp cho chúng ta và cách chúng hoạt động cùng nhau tại đây.

Biên dịch AOT

Khi nhà phát triển chạy javac bình thường tại thời điểm biên dịch, mã nguồn .java của chúng ta sẽ được biên dịch thành các tệp .class được viết bằng mã byte. Mã byte này chỉ dành cho Máy ảo Java, vì vậy, JVM sẽ phải diễn giải mã này trên các máy khác để chúng ta có thể chạy mã.

Quá trình này mang lại cho chúng ta khả năng di chuyển chữ ký của Java – cho phép chúng ta "viết một lần và chạy ở mọi nơi", nhưng tốn kém hơn so với việc chạy mã gốc.

May mắn thay, hầu hết các hoạt động triển khai JVM đều sử dụng tính năng biên dịch đúng thời điểm để giảm thiểu chi phí diễn giải này. Điều này được thực hiện bằng cách đếm số lệnh gọi cho một hàm và nếu hàm đó được gọi đủ thường xuyên để vượt qua ngưỡng ( 10.000 theo mặc định), thì hàm đó sẽ được biên dịch thành mã gốc tại thời gian chạy để ngăn việc diễn giải tốn kém hơn.

Quá trình biên dịch trước thời gian thực hiện theo phương pháp ngược lại, bằng cách biên dịch tất cả mã có thể truy cập vào một tệp thực thi gốc tại thời điểm biên dịch. Điều này đánh đổi khả năng di chuyển để có hiệu quả bộ nhớ và các lợi ích về hiệu suất khác trong thời gian chạy.

5042e8e62a05a27.png

Tất nhiên, đây là một sự đánh đổi và không phải lúc nào cũng đáng để thực hiện. Tuy nhiên, tính năng biên dịch AOT có thể phát huy hiệu quả trong một số trường hợp sử dụng nhất định, chẳng hạn như:

  • Các ứng dụng có thời gian hoạt động ngắn, trong đó thời gian khởi động là quan trọng
  • Môi trường bị hạn chế về bộ nhớ nghiêm trọng, trong đó JIT có thể quá tốn kém

Một điều thú vị là tính năng biên dịch AOT được giới thiệu dưới dạng tính năng thử nghiệm trong JDK 9, mặc dù việc triển khai tính năng này tốn kém và không bao giờ thực sự phổ biến, nên tính năng này đã bị xoá lặng lẽ trong Java 17 để các nhà phát triển chỉ sử dụng GraalVM.

GraalVM

GraalVM là một bản phân phối JDK nguồn mở được tối ưu hoá cao, có thời gian khởi động cực nhanh, tính năng biên dịch hình ảnh gốc AOT và khả năng đa ngôn ngữ cho phép nhà phát triển kết hợp nhiều ngôn ngữ vào một ứng dụng.

GraalVM đang trong quá trình phát triển tích cực, không ngừng có thêm các tính năng mới và cải thiện các tính năng hiện có. Vì vậy, nhà phát triển nên chú ý theo dõi.

Sau đây là một số mốc quan trọng gần đây:

  • Đầu ra bản dựng hình ảnh gốc mới, thân thiện với người dùng ( 18/01/2021)
  • Hỗ trợ Java 17 ( 18/01/2022)
  • Bật tính năng biên dịch nhiều cấp theo mặc định để cải thiện thời gian biên dịch đa ngôn ngữ ( 20/4/2021)

Spring Native

Nói một cách đơn giản, Spring Native cho phép sử dụng trình biên dịch hình ảnh gốc của GraalVM để biến các ứng dụng Spring thành tệp thực thi gốc.

Quy trình này bao gồm việc thực hiện phân tích tĩnh cho ứng dụng của bạn tại thời điểm biên dịch để tìm tất cả phương thức trong ứng dụng có thể truy cập được từ điểm truy cập.

Về cơ bản, điều này tạo ra một khái niệm "thế giới khép kín" cho ứng dụng của bạn, trong đó tất cả mã được giả định là đã biết tại thời điểm biên dịch và không được phép tải mã mới trong thời gian chạy.

Điều quan trọng cần lưu ý là việc tạo hình ảnh gốc là một quá trình tốn nhiều bộ nhớ, mất nhiều thời gian hơn so với việc biên dịch một ứng dụng thông thường và áp đặt các hạn chế đối với một số khía cạnh nhất định của Java.

Trong một số trường hợp, ứng dụng không cần thay đổi mã để hoạt động với Spring Native. Tuy nhiên, một số trường hợp yêu cầu cấu hình gốc cụ thể để hoạt động đúng cách. Trong những trường hợp đó, Spring Native thường cung cấp Native Hints (Gợi ý gốc) để đơn giản hoá quy trình này.

3. Thiết lập/Công việc chuẩn bị

Trước khi bắt đầu triển khai Spring Native, chúng ta cần tạo và triển khai ứng dụng để thiết lập đường cơ sở hiệu suất mà sau này có thể so sánh với phiên bản gốc.

1. Tạo dự án

Chúng ta sẽ bắt đầu bằng cách lấy ứng dụng từ 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

Ứng dụng khởi động này sử dụng Spring Boot 2.6.4, đây là phiên bản mới nhất mà dự án spring-native hỗ trợ tại thời điểm viết bài.

Xin lưu ý rằng kể từ khi phát hành GraalVM 21.0.3, bạn cũng có thể sử dụng Java 17 cho mẫu này. Chúng ta vẫn sẽ sử dụng Java 11 cho hướng dẫn này để giảm thiểu cấu hình liên quan.

Sau khi có tệp zip trên dòng lệnh, chúng ta có thể tạo một thư mục con cho dự án và giải nén thư mục đó:

mkdir spring-native

cd spring-native

unzip ../io-native-starter.zip

2. Thay đổi mã

Sau khi mở dự án, chúng ta sẽ nhanh chóng thêm một dấu hiệu hoạt động và giới thiệu hiệu suất của Spring Native sau khi chạy dự án.

Chỉnh sửa DemoApplication.java cho khớp với nội dung sau:

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();
    }
}

Tại thời điểm này, ứng dụng cơ sở của chúng ta đã sẵn sàng hoạt động. Vì vậy, hãy tạo một hình ảnh và chạy hình ảnh đó trên máy để biết thời gian khởi động trước khi chuyển đổi hình ảnh đó thành ứng dụng gốc.

Cách tạo hình ảnh:

mvn spring-boot:build-image

Bạn cũng có thể sử dụng docker images demo để biết kích thước của hình ảnh cơ sở: 6ecb403e9af1475e.png

Cách chạy ứng dụng:

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

3. Triển khai ứng dụng cơ sở

Bây giờ, chúng ta đã có ứng dụng, chúng ta sẽ triển khai ứng dụng đó và ghi lại thời gian. Sau này, chúng ta sẽ so sánh thời gian khởi động ứng dụng gốc với thời gian khởi động ứng dụng web.

Tuỳ thuộc vào loại ứng dụng bạn đang xây dựng, có một số cách lưu trữ nội dung của bạn.

Tuy nhiên, vì ví dụ của chúng ta là một ứng dụng web rất đơn giản và dễ hiểu, nên chúng ta có thể đơn giản hoá mọi thứ và dựa vào Cloud Run.

Nếu bạn đang làm theo hướng dẫn trên máy của riêng mình, hãy nhớ cài đặt và cập nhật công cụ gcloud CLI.

Nếu đang sử dụng Cloud Shell, bạn chỉ cần chạy các lệnh sau trong thư mục nguồn:

gcloud run deploy

4. Cấu hình ứng dụng

1. Định cấu hình kho lưu trữ Maven của chúng tôi

Vì dự án này vẫn đang trong giai đoạn thử nghiệm, nên chúng ta sẽ phải định cấu hình ứng dụng để có thể tìm thấy các cấu phần phần mềm thử nghiệm không có trong kho lưu trữ trung tâm của maven.

Bạn có thể thực hiện việc này trong trình chỉnh sửa mà bạn chọn bằng cách thêm các phần tử sau vào pom.xml.

Thêm các phần kho lưu trữ và pluginRepositories sau vào pom của chúng ta:

<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. Thêm phần phụ thuộc

Tiếp theo, hãy thêm phần phụ thuộc spring-native. Phần phụ thuộc này là bắt buộc để chạy ứng dụng Spring dưới dạng hình ảnh gốc. Lưu ý: bạn không cần thực hiện bước này nếu đang sử dụng Gradle

<dependencies>
    <!-- ... -->
    <dependency>
        <groupId>org.springframework.experimental</groupId>
        <artifactId>spring-native</artifactId>
        <version>0.11.2</version>
    </dependency>
</dependencies>

3. Thêm/bật trình bổ trợ của chúng tôi

Bây giờ, hãy thêm trình bổ trợ AOT để cải thiện khả năng tương thích và mức sử dụng của hình ảnh gốc ( Đọc thêm):

<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>

Bây giờ, chúng ta sẽ cập nhật trình bổ trợ spring-boot-maven để bật tính năng hỗ trợ hình ảnh gốc và sử dụng trình tạo gói để tạo hình ảnh gốc:

<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>

Xin lưu ý rằng hình ảnh tiny builder (trình tạo nhỏ) chỉ là một trong số nhiều tuỳ chọn. Đây là một lựa chọn phù hợp cho trường hợp sử dụng của chúng ta vì có rất ít thư viện và tiện ích bổ sung, giúp giảm thiểu bề mặt tấn công.

Ví dụ: nếu bạn đang xây dựng một ứng dụng cần quyền truy cập vào một số thư viện C phổ biến hoặc bạn chưa chắc chắn về các yêu cầu của ứng dụng, thì trình tạo đầy đủ có thể phù hợp hơn.

5. Tạo và chạy ứng dụng gốc

Sau khi hoàn tất tất cả các bước, chúng ta có thể tạo hình ảnh và chạy ứng dụng gốc, được biên dịch.

Trước khi chạy bản dựng, bạn cần lưu ý một số điều sau:

  • Quá trình này sẽ mất nhiều thời gian hơn so với bản dựng thông thường (vài phút) d420322893640701.png
  • Quá trình xây dựng này có thể tốn nhiều bộ nhớ (vài gigabyte) cda24e1eb11fdbea.png
  • Quy trình xây dựng này yêu cầu có thể truy cập vào trình nền Docker
  • Mặc dù trong ví dụ này, chúng ta sẽ thực hiện quy trình theo cách thủ công, nhưng bạn cũng có thể định cấu hình các giai đoạn bản dựng để tự động kích hoạt hồ sơ bản dựng gốc.

Cách tạo hình ảnh:

mvn spring-boot:build-image

Sau khi tạo xong, chúng ta đã sẵn sàng xem ứng dụng gốc hoạt động!

Cách chạy ứng dụng:

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

Tại thời điểm này, chúng ta có thể xem cả hai bên của phương trình ứng dụng gốc.

Chúng ta đã mất một chút thời gian và sử dụng thêm bộ nhớ tại thời điểm biên dịch, nhưng đổi lại, chúng ta có một ứng dụng có thể khởi động nhanh hơn nhiều và tiêu thụ ít bộ nhớ hơn đáng kể (tuỳ thuộc vào khối lượng công việc).

Nếu chạy docker images demo để so sánh kích thước của hình ảnh gốc với hình ảnh gốc, chúng ta có thể thấy kích thước giảm đáng kể:

e667f65a011c1328.png

Chúng ta cũng cần lưu ý rằng trong các trường hợp sử dụng phức tạp hơn, bạn cần thực hiện thêm một số sửa đổi để thông báo cho trình biên dịch AOT về những việc ứng dụng của bạn sẽ làm trong thời gian chạy. Vì lý do đó, một số khối lượng công việc có thể dự đoán được (chẳng hạn như công việc theo lô) có thể rất phù hợp với việc này, trong khi một số khối lượng công việc khác có thể tăng hiệu suất nhiều hơn.

6. Triển khai ứng dụng gốc

Để triển khai ứng dụng của mình trên Cloud Run, chúng ta cần đưa hình ảnh gốc vào trình quản lý gói như Artifact Registry.

1. Chuẩn bị kho lưu trữ Docker

Chúng ta có thể bắt đầu quá trình này bằng cách tạo một kho lưu trữ:

gcloud artifacts repositories create native-image-docker-repo --repository-format=docker \
--location=us-central1 --description="Repository for our native images"

Tiếp theo, chúng ta cần đảm bảo rằng chúng ta đã xác thực để đẩy vào sổ đăng ký mới.

CLI gcloud có thể đơn giản hoá quy trình đó khá nhiều:

gcloud auth configure-docker us-central1-docker.pkg.dev

2. Đẩy hình ảnh của chúng ta vào Artifact Registry

Tiếp theo, chúng ta sẽ gắn thẻ cho hình ảnh:

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

Sau đó, chúng ta có thể sử dụng docker push để gửi tệp đó đến Cấu phần phần mềm:

docker push us-central1-docker.pkg.dev/$PROJECT_ID/native-image-docker-repo/quickstart-image:tag2

3. Triển khai lên Cloud Run

Bây giờ, chúng ta đã sẵn sàng triển khai hình ảnh đã lưu trữ trong Cấu phần phần mềm đăng ký cho Cloud Run:

gcloud run deploy --image us-central1-docker.pkg.dev/$PROJECT_ID/native-image-docker-repo/quickstart-image:tag2

Vì đã tạo và triển khai ứng dụng dưới dạng hình ảnh gốc, nên chúng ta có thể yên tâm rằng ứng dụng của mình đang sử dụng hiệu quả chi phí cơ sở hạ tầng khi chạy.

Bạn có thể tự so sánh thời gian khởi động của ứng dụng cơ sở với ứng dụng gốc mới này!

6dde63d35959b1bb.png

7. Tóm tắt/Dọn dẹp

Xin chúc mừng bạn đã xây dựng và triển khai ứng dụng Spring Native trên Google Cloud!

Hy vọng hướng dẫn này sẽ khuyến khích bạn làm quen hơn với dự án Spring Native và lưu ý đến dự án này nếu dự án này đáp ứng nhu cầu của bạn trong tương lai.

Không bắt buộc: Dọn dẹp và/hoặc tắt dịch vụ

Cho dù bạn đã tạo một dự án Google Cloud cho lớp học lập trình này hay đang sử dụng lại một dự án hiện có, hãy cẩn thận để tránh các khoản phí không cần thiết từ các tài nguyên mà chúng ta đã sử dụng.

Bạn có thể xoá hoặc tắt các dịch vụ Cloud Run mà chúng tôi đã tạo, xoá hình ảnh mà chúng tôi đã lưu trữ hoặc tắt toàn bộ dự án.

8. Tài nguyên khác

Mặc dù dự án Spring Native hiện là một dự án mới và đang thử nghiệm, nhưng đã có rất nhiều tài nguyên hữu ích để giúp những người dùng sớm khắc phục sự cố và tham gia:

Tài nguyên khác

Dưới đây là các tài nguyên trực tuyến có thể liên quan đến hướng dẫn này:

Giấy phép

Tác phẩm này được cấp phép theo Giấy phép chung Ghi công theo Creative Commons 2.0.