應“雲”而生的 Java 框架:構建本機可執行檔案
今天來看一下 Quarkus 構建出來的本機可執行檔案到底比 Spring 應用能快多少, 生態的成熟度不在這裡討論 。
TLDR
先上結論, 與只有一個 Controller 的Spring Web 應用做下對比。
應用啟動時間:0.012s vs 2.294s
映象大小:49MB vs 237 MB
Spring 應用映象使用 openjdk:11.0-jre-slim 作為 base 映象,大小為 220MB。
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
spring/spring-getting-started latest 5f47030c5c3f 6 minutes ago 237MB
quarkus/quarkus-getting-started distroless2 fe973c5ac172 24 minutes ago 49MB
quarkus/quarkus-getting-started distroless 6fe27dd44e86 31 minutes ago 51MB
quarkus/quarkus-getting-started ubi 8f86f5915715 58 minutes ago 132MB
Java 應用容器化的困境
雲原生世界中,應用容器化是個顯著的特點。Java 應用容器化時面臨瞭如下問題:
- 應用啟動慢:其實這是 Java 應用的問題。Java 應用佔用記憶體多;JVM 虛擬機器啟動時需要做環境的初始化、預載入大量的類、初始化執行緒等等。啟動耗時視應用情況需要幾秒,甚至可達分鐘級。較長的啟動耗時,也抑制了水平伸縮性。即使在 Serverless 這種響應耗時要求不高的場景,也會被嫌棄。
- 映象過大:其實使用了映象的分層設計,常見的一個 SpringCloud 應用的 über-jar 包可能都有 7、80MB。
- 空間佔用:雖然用了映象分層,但積少成多,也會增加儲存成本。
Quarkus 與本機映像(native image)
Quarkus 的開發遵從了容器優先的原則:
- 支援 Graal/SubstrateVM
- 構建時處理元資料
- 減少反射的使用
- 本機映像預啟動
本機映像是將 Java 程式碼提前編譯為可執行檔案(稱為本機映像)的技術。該可執行檔案包括應用程式類、其依賴項中的類、執行時間類以及 JDK 中的靜態連結本機程式碼。它不是在 Java VM 上執行,而是包括必要的元件,例如記憶體管理,執行緒排程等,這些元件來自另一個執行時系統 “Substrate VM”。“Substrate VM” 是執行時元件(例如反優化器,垃圾收集器,執行緒排程等)的名稱。與 JVM 相比,生成的程式具有更快的啟動時間和更低的執行時記憶體開銷。
如何構建本機映像
環境配置參考 上一篇文章 ,可以直接 從這裡下載原始碼 。
配置 GraalVM
之前我們使用了 sdkman 進行 GraalVM 安裝。設定 GRAALVM_HOME 環境變數:
export GRAALVM_HOME=`sdk home java 21.0.0.2.r11-grl`
使用 gu 安裝 native-image :
${GRAALVM_HOME}/bin/gu install native-image
構建本機可執行檔案
在原始碼的 pom.xml 中,我們可以看到如下的 profile :
<profiles>
<profile>
<id>native</id>
<properties>
<quarkus.package.type>native</quarkus.package.type>
</properties>
</profile>
</profiles>
我們使用這個 profile 進行本機可執行檔案的構建,整個構建耗時 幾分鐘 。
./mvnw package -Pnative
部分構建日誌:
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ quarkus-getting-started ---
[INFO]
[INFO] --- quarkus-maven-plugin:1.13.0.Final:build (default) @ quarkus-getting-started ---
[INFO] [org.jboss.threads] JBoss Threads version 3.2.0.Final
[INFO] [io.quarkus.deployment.pkg.steps.JarResultBuildStep] Building native image source jar: /Users/addo/Workspaces/private_w/quarkus-getting-started/target/quarkus-getting-started-1.0.0-SNAPSHOT-native-image-source-jar/quarkus-getting-started-1.0.0-SNAPSHOT-runner.jar
[INFO] [io.quarkus.deployment.pkg.steps.NativeImageBuildStep] Building native image from /Users/addo/Workspaces/private_w/quarkus-getting-started/target/quarkus-getting-started-1.0.0-SNAPSHOT-native-image-source-jar/quarkus-getting-started-1.0.0-SNAPSHOT-runner.jar
[INFO] [io.quarkus.deployment.pkg.steps.NativeImageBuildContainerRunner] Using docker to run the native image builder
[INFO] [io.quarkus.deployment.pkg.steps.NativeImageBuildContainerRunner] Checking image status quay.io/quarkus/ubi-quarkus-native-image:21.0.0-java11
21.0.0-java11: Pulling from quarkus/ubi-quarkus-native-image
Digest: sha256:becf08de869e707beaa5e57444b533ef93ebef15aad90c92ac660ddf7cea2b11
Status: Image is up to date for quay.io/quarkus/ubi-quarkus-native-image:21.0.0-java11
quay.io/quarkus/ubi-quarkus-native-image:21.0.0-java11
[INFO] [io.quarkus.deployment.pkg.steps.NativeImageBuildStep] Running Quarkus native-image plugin on GraalVM Version 21.0.0 (Java Version 11.0.10+8-jvmci-21.0-b06)
[INFO] [io.quarkus.deployment.pkg.steps.NativeImageBuildRunner] docker run --env LANG=C --rm -v /Users/addo/Workspaces/private_w/quarkus-getting-started/target/quarkus-getting-started-1.0.0-SNAPSHOT-native-image-source-jar:/project:z quay.io/quarkus/ubi-quarkus-native-image:21.0.0-java11 -J-Dsun.nio.ch.maxUpdateArraySize=100 -J-Djava.util.logging.manager=org.jboss.logmanager.LogManager -J-Dvertx.logger-delegate-factory-class-name=io.quarkus.vertx.core.runtime.VertxLogDelegateFactory -J-Dvertx.disableDnsResolver=true -J-Dio.netty.leakDetection.level=DISABLED -J-Dio.netty.allocator.maxOrder=1 -J-Duser.language=en -J-Duser.country=CN -J-Dfile.encoding=UTF-8 --initialize-at-build-time= -H:InitialCollectionPolicy=com.oracle.svm.core.genscavenge.CollectionPolicy\$BySpaceAndTime -H:+JNI -H:+AllowFoldMethods -jar quarkus-getting-started-1.0.0-SNAPSHOT-runner.jar -H:FallbackThreshold=0 -H:+ReportExceptionStackTraces -J-Xmx5g -H:-AddAllCharsets -H:EnableURLProtocols=http --no-server -H:-UseServiceLoaderFeature -H:+StackTrace quarkus-getting-started-1.0.0-SNAPSHOT-runner
[quarkus-getting-started-1.0.0-SNAPSHOT-runner:25] classlist: 5,859.24 ms, 0.96 GB
[quarkus-getting-started-1.0.0-SNAPSHOT-runner:25] (cap): 633.34 ms, 0.94 GB
[quarkus-getting-started-1.0.0-SNAPSHOT-runner:25] setup: 2,468.19 ms, 0.94 GB
00:06:00,437 INFO [org.jbo.threads] JBoss Threads version 3.2.0.Final
[quarkus-getting-started-1.0.0-SNAPSHOT-runner:25] (clinit): 516.65 ms, 2.23 GB
[quarkus-getting-started-1.0.0-SNAPSHOT-runner:25] (typeflow): 12,642.02 ms, 2.23 GB
[quarkus-getting-started-1.0.0-SNAPSHOT-runner:25] (objects): 11,340.37 ms, 2.23 GB
[quarkus-getting-started-1.0.0-SNAPSHOT-runner:25] (features): 525.87 ms, 2.23 GB
[quarkus-getting-started-1.0.0-SNAPSHOT-runner:25] analysis: 26,032.67 ms, 2.23 GB
[quarkus-getting-started-1.0.0-SNAPSHOT-runner:25] universe: 1,394.06 ms, 2.16 GB
[quarkus-getting-started-1.0.0-SNAPSHOT-runner:25] (parse): 2,690.38 ms, 2.16 GB
[quarkus-getting-started-1.0.0-SNAPSHOT-runner:25] (inline): 4,336.77 ms, 2.73 GB
[quarkus-getting-started-1.0.0-SNAPSHOT-runner:25] (compile): 17,580.03 ms, 2.71 GB
[quarkus-getting-started-1.0.0-SNAPSHOT-runner:25] compile: 26,152.06 ms, 2.71 GB
[quarkus-getting-started-1.0.0-SNAPSHOT-runner:25] image: 3,288.43 ms, 2.70 GB
[quarkus-getting-started-1.0.0-SNAPSHOT-runner:25] write: 1,904.64 ms, 2.70 GB
[quarkus-getting-started-1.0.0-SNAPSHOT-runner:25] [total]: 67,414.16 ms, 2.70 GB
[WARNING] [io.quarkus.deployment.pkg.steps.NativeImageBuildStep] objcopy executable not found in PATH. Debug symbols will not be separated from executable.
[WARNING] [io.quarkus.deployment.pkg.steps.NativeImageBuildStep] That will result in a larger native image with debug symbols embedded in it.
[INFO] [io.quarkus.deployment.QuarkusAugmentor] Quarkus augmentation completed in 74739ms
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 01:21 min
[INFO] Finished at: 2021-04-17T08:06:47+08:00
[INFO] ------------------------------------------------------------------------
假如構建時出現類似 Caused by: java.lang.RuntimeException: Image generation failed. Exit code was 137 which indicates an out of memory error. Consider increasing the Xmx value for native image generation by setting the "quarkus.native.native-image-xmx" property 這種報錯。需要調整下 Docker 的設定,比如筆者使用的 macOS,開啟 Docker Desktop > Preference > Resource > Advanced,將記憶體從預設的 2GB 調大,比如 8GB。
從構建日誌可以看出,構建的過程是在 quay.io/quarkus/ubi-quarkus-native-image 的容器中完成的。雖然異常提示調整 “quarkus.native.native-image-xmx” ,其實是容器記憶體太小導致的。
構建成功後,可以在 target 中找到 quarkus-getting-started-1.0.0-SNAPSHOT-runner 。這是一個可執行檔案,大小為 28MB。
嘗試執行該檔案,收到 zsh: exec format error: ./target/quarkus-getting-started-1.0.0-SNAPSHOT-runner 錯誤。因為這是一個 Linux 可執行檔案,因此我們需要在容器中執行。
構建本機映象
在原始檔的 src/main/docker 目錄中,我們可以找到 Dockerfile.native :
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.3
WORKDIR /work/
RUN chown 1001 /work \
&& chmod "g+rwX" /work \
&& chown 1001:root /work
COPY --chown=1001:root target/*-runner /work/application
EXPOSE 8080
USER 1001
CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]
執行映象
本地執行一下,可以看出啟動只需要 0.013s 。
docker run --rm -p 8080:8080 quarkus/quarkus-getting-started:latest
__ ____ __ _____ ___ __ ____ ______
--/ __ \/ / / / _ | / _ \/ //_/ / / / __/
-/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2021-04-17 00:22:27,146 INFO [io.quarkus] (main) quarkus-getting-started 1.0.0-SNAPSHOT native (powered by Quarkus 1.13.0.Final) started in 0.013s. Listening on: http://0.0.0.0:8080
2021-04-17 00:22:27,147 INFO [io.quarkus] (main) Profile prod activated.
2021-04-17 00:22:27,147 INFO [io.quarkus] (main) Installed features: [cdi, resteasy]
測試一下端點:
http :8080/hello/greeting/quarkus
HTTP/1.1 200 OK
Content-Length: 14
Content-Type: text/plain;charset=UTF-8
Hello, quarkus
看下映象的資訊,大小為 132MB,其中 base 映象 ubi-minimal 就佔了 103 MB。感覺還是有點大,是否繼續精簡一下?
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
quarkus/quarkus-getting-started latest 8f86f5915715 4 minutes ago 132MB
registry.access.redhat.com/ubi8/ubi-minimal 8.3 604ddd554fec 2 weeks ago 103MB
映象瘦身
在 src/main/docker 中還有個名為 Dockerfile.native-distroless 的Dockerfile,裡面使用了 quay.io/quarkus/quarkus-distroless-image:1.0 作為 base 映象
使用這個Dockerfile進行構建,得到的映象就小很多,只有 51MB:
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
quarkus/quarkus-getting-started distroless 6fe27dd44e86 33 seconds ago 51MB
quarkus/quarkus-getting-started ubi 8f86f5915715 27 minutes ago 132MB
quay.io/quarkus/quarkus-distroless-image 1.0 062663862a83 6 days ago 21.3MB
registry.access.redhat.com/ubi8/ubi-minimal 8.3 604ddd554fec 2 weeks ago 103MB
執行成功:
docker run --rm -p 8080:8080 quarkus/quarkus-getting-started:distroless
__ ____ __ _____ ___ __ ____ ______
--/ __ \/ / / / _ | / _ \/ //_/ / / / __/
-/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2021-04-17 00:51:26,070 INFO [io.quarkus] (main) quarkus-getting-started 1.0.0-SNAPSHOT native (powered by Quarkus 1.13.0.Final) started in 0.013s. Listening on: http://0.0.0.0:8080
2021-04-17 00:51:26,071 INFO [io.quarkus] (main) Profile prod activated.
2021-04-17 00:51:26,071 INFO [io.quarkus] (main) Installed features: [cdi, resteasy]
極致瘦身,參考了 這裡 ,我們建立 Dockerfile.native-distroless2 。
最終映象的大小為 49MB,與官方提供的 distroless base 映象變小了 2MB。
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
quarkus/quarkus-getting-started distroless2 fe973c5ac172 3 seconds ago 49MB
前面對比,用來構建 Spring 應用的 base 映象 openjdk:11.0-jre-slim 已經有 220MB,這還沒算上應用的大小。即使是 openjdk:17-alpine3.13 也有 182 MB。
原文連結:http://atbug.com/quarkus-build-native-executable-file/
如果覺得本文對你有幫助,可以關注一下我公眾號,回覆關鍵字【面試】即可得到一份Java核心知識點整理與一份面試大禮包!另有更多技術乾貨文章以及相關資料共享,大家一起學習進步!
- 真香!Github超牛X的SpringBoot vue專案開發文件 原始碼限時開源!
- Github又爆神作,阿里JVM垃圾回收全解小冊全網開源!(演算法 底層實現)
- 應“雲”而生的 Java 框架:構建本機可執行檔案
- 直擊靈魂!阿里技術官甩出內部爆款效能優化實戰筆記,理論實戰一鍵搞定!
- 來了來了!阿里內部面試參考指南(泰山版)限時開源分享,差距不止一點點!
- 香!阿里內部全套Java面試資源限時開源!差距不止一點點
- 金三銀四期間獲千萬下載量!阿里大牛熬夜45天整理出來的Java面試突擊手冊也太香了!
- 阿里高工熬夜18天碼出Java150K字面試寶典,卻遭Github全面封殺
- 完美面試寶典?終於有阿里大牛將1000道大廠Java面試真題收錄成冊,全網開源了!
- 基於SpringBoot實現,讓日誌像詩一樣有韻律
- 一戰到底!終於有阿里大牛把對標阿里P7的Java學習路線與架構進階寶典整理成冊全網開源了!
- 基礎到原始碼一鍵搞定:阿里大牛手碼651K字的JDK原始碼速成筆記太強了!
- 他來了他來了!知乎上線2小時遭瘋狂轉載的阿里Java核心框架指導手冊(2021全綵版)太香了!
- 淚目!刷完這份阿里面試突擊手冊我再也不是那個Java面試十面九敗最後只能去小公司的弟弟了!
- 牛掰,阿里金三銀四Java漲薪祕籍全網開源,卻連夜遭Github、知乎等平臺連夜下架!
- 65w字!阿里分散式開發小冊Github新開源!原理實踐雙飛
- 乾貨來襲!阿里技術官甩出2021最新SpringCloudAlibaba全解,理論實戰齊飛!
- 好傢伙!Github又現神作,阿里大牛親碼24W字面試總結也太香了!(涵蓋P5到P8所有技術點)
- Github限時開放!阿里內部強推微服務容器化參考指南我粉了
- 重磅!不容錯過的阿里內部Spring Cloud Alibaba速成手冊也太讚了(2021版)