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 về các thành phần trong dự á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 các bước bạn cần thực hiện để sử dụng dự án trong các dự án của mình.
Dự án gốc mùa xuân hiện đang trong giai đoạn thử nghiệm nê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 được thiết lập để tích hợp vào Spring Framework 6.0 và Spring Boot 3.0 với tính năng hỗ trợ hạng nhất, 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ù việc biên dịch đúng thời điểm đã được tối ưu hoá rất hiệu quả cho những quy trình chạy trong thời gian dài, nhưng có một số trường hợp sử dụng mà các ứng dụng được biên dịch trước thời hạn hoạt động hiệu quả hơn nữa. Chúng ta sẽ thảo luận về vấn đề này trong lớp học lập trình này.
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 gốc mùa xuân
- Triển khai một ứng dụng như vậy lên Cloud Run
Bạn cần có
- Một dự án Google Cloud Platform có tài khoản thanh toán GCP đang hoạt động
- gcloud cli hoặc truy cập vào Cloud Shell
- Các kỹ năng cơ bản về Java + XML
- Kiến thức thực hành về các lệnh Linux phổ biến
Khảo sát
Bạn sẽ sử dụng hướng dẫn này như thế nào?
Bạn đánh giá trải nghiệm sử dụng Java của mình ở mức nào?
Bạn đánh giá thế nào về trải nghiệm sử dụng các dịch vụ của Google Cloud?
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 của ứng dụng gốc cho nhà phát triển.
Để hiểu đầy đủ về Spring Native, bạn nên tìm hiểu một số công nghệ thành phần trong số này, những lợi ích mà chúng hỗ trợ cho chúng ta và cách chúng hoạt động cùng nhau trong bài viết này.
Biên dịch AOT
Khi các nhà phát triển chạy javac như bình thường tại thời điểm biên dịch, mã nguồn .java của chúng tôi 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 chạy mã của mình.
Quy 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 việc này tốn kém so với việc chạy mã gốc.
May mắn là 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 các 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 trong thời gian chạy để tránh việc diễn giải tốn kém hơn nữa.
Việc biên dịch trước khi thực thi sử dụng phương pháp ngược lại, bằng cách biên dịch tất cả các mã có thể tiếp cận thành một tệp thực thi gốc tại thời điểm biên dịch. Điều này sẽ đánh đổi tính có thể di chuyển sang hiệu suất bộ nhớ và các mức tăng hiệu suất khác trong thời gian chạy.
Tất nhiên là bạn phải đánh đổi và không phải lúc nào cũng đáng để thực hiện điều này. Tuy nhiên, tính năng biên dịch AOT có thể nổi bật trong một số trường hợp sử dụng nhất định, chẳng hạn như:
- Các ứng dụng tồn tại trong thời gian ngắn, trong đó thời gian khởi động là yếu tố quan trọng
- Môi trường bị hạn chế nghiêm ngặt về bộ nhớ, nơi JIT có thể quá tốn kém
Thực tế thú vị là việc biên dịch AOT được giới thiệu như một tính năng thử nghiệm trong JDK 9, mặc dù cách triển khai này rất tốn kém để duy trì và chưa bao giờ hoàn toàn được phát hiện. Vì vậy, nó đã bị xoá bỏ trong Java 17 và thay vào đó là 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, khả năng biên dịch hình ảnh gốc trên AOT và các tính năng polyglot cho phép các 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, đạt được những khả năng mới và luôn cải tiến những tính năng hiện có, vì vậy, tôi khuyến khích các nhà phát triển luôn chú ý theo dõi.
Một số mốc gần đây là:
- Kết quả tạo 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 lớp theo mặc định để cải thiện thời gian biên dịch polyglot ( 20/04/2021)
Gốc mùa xuân
Nói một cách đơn giản, ứng dụng 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.
Quá trình này bao gồm việc phân tích tĩnh ứng dụng của bạn tại thời điểm biên dịch để tìm tất cả cá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 "thế giới khép kín" về ứng dụng, 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ó mã mới nào được phép tải trong thời gian chạy.
Điều quan trọng cần lưu ý là tạo hình ảnh gốc là một quá trình dùng nhiều bộ nhớ, mất nhiều thời gian hơn việc biên dịch ứng dụng thông thường và gây ra các giới hạn đối với một số khía cạnh của Java.
Trong một số trường hợp, bạn không cần thay đổi mã để ứng dụng 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 như vậy, mã gốc mùa xuân thường cung cấp Gợi ý gốc để đơn giản hoá quy trình này.
3. Thiết lập/Chuẩn bị
Trước khi bắt đầu triển khai Spring Native, chúng ta sẽ cần tạo và triển khai ứng dụng để thiết lập đường cơ sở hiệu suất mà chúng ta có thể so sánh với phiên bản gốc sau này.
1. Tạo dự án
Chúng ta sẽ bắt đầu bằng cách tải ứng dụng xuố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 đầu này sử dụng Spring Boot 2.6.4, đây là phiên bản mới nhất mà dự án gốc mùa xuân hỗ trợ tại thời điểm viết.
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 trong hướng dẫn này để giảm thiểu cấu hình có liên quan.
Sau khi đã tạo 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 trong đó:
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 sinh động và giới thiệu hiệu suất Spring Native sau khi chạy nó.
Chỉnh sửa DemoApplication.java để khớp với mã này:
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 trên máy cục bộ để nắm được thời gian khởi động trước khi chúng ta chuyển đổi ứng dụng đó sang ứ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ở:
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ở
Giờ đây, khi đã 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 này với thời gian khởi động ứng dụng gốc.
Tùy 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ữ khác nhau cho 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 theo dõi trên máy của 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 sẽ được xử lý mọi vấn đề và chỉ cần chạy 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
Do 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, vốn chưa có trong kho lưu trữ trung tâm của maven.
Việc này sẽ bao gồm việc thêm các phần tử sau đây vào tệp pom.xml. Bạn có thể thực hiện việc này trong trình chỉnh sửa mà bạn chọn.
Thêm các kho lưu trữ và phần pluginRepositories sau vào pom của chúng tôi:
<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 các phần phụ thuộc
Tiếp theo, hãy thêm phần phụ thuộc gốc mùa xuân. Đây là phần phụ thuộc 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ợ
Bây giờ, hãy thêm trình bổ trợ AOT để cải thiện khả năng tương thích và kích thước 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 spring-boot-maven-plugin để bật tính năng hỗ trợ hình ảnh gốc và sử dụng trình tạo paketo để 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>
Lưu ý rằng hình ảnh trình tạo nhỏ chỉ là một trong số các lựa chọn. Đây là một lựa chọn hay cho trường hợp sử dụng của chúng ta vì nó 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.
Nếu bạn đang tạo 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ể sẽ phù hợp hơn.
5. Tạo và chạy ứng dụng gốc
Sau khi mọi thứ đã sẵn sàng, chúng ta sẽ có thể tạo hình ảnh và chạy ứng dụng gốc, đã biên dịch.
Trước khi chạy bản dựng, hãy lưu ý một số điều sau:
- Thao tác 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)
- Quá trình tạo này có thể chiếm rất nhiều bộ nhớ (vài gigabyte)
- Quá trình xây dựng này đòi hỏi trình nền Docker có thể truy cập được
- Mặc dù trong ví dụ này, chúng ta sẽ tìm hiểu 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 tạo 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 quá trình tạo bản dựng xong, chúng ta đã có thể 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 tôi đang ở vị thế tuyệt vời để quan sát cả hai vế của phương trình ứng dụng gốc.
Chúng ta đã giảm một chút thời gian và mức sử dụng bộ nhớ bổ sung vào thời gian biên dịch, nhưng đổi lại, chúng ta nhận được một ứng dụng có thể khởi động nhanh hơn nhiều và tiêu tốn í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 sự sụt giảm đáng kể:
Chúng ta cũng nê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 sửa đổi thêm để trình biên dịch AOT biết những gì ứng dụng của bạn sẽ thực hiện 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 (chẳng hạn như công việc theo lô) có thể rất phù hợp với loại khối lượng công việc này, trong khi các khối lượng công việc khác có thể có mức tăng lớn hơn.
6. Triển khai ứng dụng gốc
Để triển khai ứng dụng lên Cloud Run, chúng ta cần tải hình ảnh gốc của mình vào một 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 tôi sẽ cần đảm bảo rằng chúng tôi đã được xác thực để chuyển đến sổ đăng ký mới.
Giao diện dòng lệnh (CLI) của gcloud có thể đơn giản hoá khá nhiều quy trình đó:
gcloud auth configure-docker us-central1-docker.pkg.dev
2. Chuyển hình ảnh của chúng ta lên Artifact Registry
Tiếp theo, chúng ta sẽ gắn thẻ cho hình ảnh của mì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 đến Artifact Registry:
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 được lưu trữ trong Artifact Registry lên Cloud Run:
gcloud run deploy --image us-central1-docker.pkg.dev/$PROJECT_ID/native-image-docker-repo/quickstart-image:tag2
Vì đã xây dựng và triển khai ứng dụng dưới dạng hình ảnh gốc, nên chúng tôi có thể yên tâm rằng ứng dụng đang sử dụng hiệu quả chi phí cơ sở hạ tầng khi chạy.
Bạn có thể so sánh thời gian khởi động của ứng dụng cơ sở với thời gian khởi động của ứng dụng gốc mới này!
7. Tóm tắt/Dọn dẹp
Chúc mừng bạn đã xây dựng và triển khai ứng dụng gốc có tên là Spring trên Google Cloud!
Hy vọng hướng dẫn này sẽ khuyến khích bạn làm quen với dự án Spring Native và lưu ý đến nó nếu nó đá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ụ
Dù bạn đã tạo một dự án trê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 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 ta đã tạo, xoá hình ảnh chúng ta đã lưu trữ hoặc ngừng hoạt động 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à mang tính thử nghiệm, nhưng đã có rất nhiều tài nguyên hữu ích để giúp những người sử 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ể phù hợp với hướng dẫn này:
- Tìm hiểu thêm về Gợi ý gốc
- Tìm hiểu thêm về GraalVM
- Cách tham gia
- Lỗi hết bộ nhớ khi tạo hình ảnh gốc
- Lỗi ứng dụng không khởi động được
Giấy phép
Tác phẩm này được cấp phép theo Giấy phép chung Ghi nhận tác giả Creative Commons 2.0.