誰才是微服務(wù)贏家:Quarkus 與 Spring Boot
在容器時代(“Docker 時代”)Java 仍然處于領(lǐng)先地位,但哪個更好?Spring Boot 還是 Quarkus?
誰會最先進的?Spring Boot 或 Quarkus。
在容器時代(“ Docker 時代”),無論您是否在使用它,都不可否定java的活力。Java 在性能方面一直比較有優(yōu)勢,主要是因為代碼和真實機器之間的抽象層,多平臺的成本(一次編寫,隨處運行 - 還記得嗎?),其中包含 JVM -between(JVM:模擬真實機器所做的軟件機器)。
如今,使用微服務(wù)架構(gòu),也沒有任何優(yōu)勢,為總是在同一個地方和平臺上運行的東西(Docker 容器 - Linux) 環(huán)境構(gòu)建多平臺(解釋)的東西??梢浦残袁F(xiàn)在不那么重要了(可能比以往任何時候都重要),那些額外的抽象級別并不重要。
話雖如此,讓我們對在Java中生成微服務(wù)的兩種替代方案進行簡單而原始的比較:非常知名的Spring Boot和不太知名的(尚未)Quarkus。
反對者
Quarkus是什么?
一套適用于GraalVM和 HotSpot的開源技術(shù) ,用于編寫 Java 應(yīng)用程序。它提供(承諾)超快的啟動時間和更低的內(nèi)存占用。這使其成為容器和無服務(wù)器工作負載的理想選擇。它使用 Eclipse 微配置文件(JAX-RS、CDI、JSON-P),這是 Java EE 的一個子集來構(gòu)建微服務(wù)。
GraalVM 是一個通用的多語言虛擬機(JavaScript、Python、Ruby、R、Java、Scala、Kotlin)。 GraalVM (特別是 Substrate VM)使提前(AOT)編譯成為可能,將字節(jié)碼轉(zhuǎn)換為本地機器碼,從而生成可以本地執(zhí)行的二進制文件。
請記住,并非所有功能都可以在本機執(zhí)行中使用,AOT 編譯有其局限性。注意這句話(引用 GraalVM 團隊):
我們運行需要一個封閉世界假設(shè)的激進靜態(tài)分析,這意味著在運行時可訪問的所有類和所有字節(jié)碼必須在構(gòu)建時已知。
因此,例如,反射和 Java 本機接口 (JNI) 將不起作用,至少是開箱即用的(需要一些額外的工作)。您可以在本機圖像 Java 限制文檔中找到限制列表。
Spring Boot是什么?
這是真的嗎?好吧,我只想說一句(請隨意跳過),一句話:Spring Boot構(gòu)建在 Spring Framework 事實上,是一個開源框架,它提供了一種更簡單的方式來構(gòu)建、配置和運行基于 Web 的 Java 應(yīng)用程序. 使其成為微服務(wù)的良好候選者。
戰(zhàn)斗準(zhǔn)備——創(chuàng)建 Docker 鏡像
Quarkus鏡像
讓我們創(chuàng)建 Quarkus 應(yīng)用程序,以便稍后將其包裝在 Docker 映像中?;旧?,我們將做與 Quarkus入門教程相同的事情。
使用 Quarkus maven 原型創(chuàng)建項目:
mvn io.quarkus:quarkus-maven-plugin:1.0.0.CR2:create
-DprojectGroupId=ujr.combat.quarkus
-DprojectArtifactId=quarkus-echo
-DclassName="ujr.combat.quarkus.EchoResource"
-Dpath="/echo"
這將導(dǎo)致我們項目的結(jié)構(gòu),如下所示:
請注意,還創(chuàng)建了兩個示例Dockerfile (src/main/docker):一個用于普通JVM App Image,另一個用于Native App Image。
在生成的代碼中,我們只需要更改一件事,添加下面的依賴項,因為我們要生成 JSON 內(nèi)容。
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-jsonb</artifactId>
</dependency>
Quarkus 在整個 RESTEasy 項目實現(xiàn)中使用 JAX-RS 規(guī)范。
這是我們的“整個”應(yīng)用程序:
這就是全部,使用下一個命令我們可以看到應(yīng)用程序正在運行:
mvn clean compile quarkus:dev
在這種模式下,我們也開啟了熱部署,后臺編譯。讓我們做一個簡單的測試來看看:
curl -sw "\n\n" http://localhost:8080/echo/ualter | jq .
現(xiàn)在我們看到它正在工作,讓我們創(chuàng)建 Docker 映像。從這里下載 GraalVM:https://github.com/graalvm/graalvm-ce-builds/releases 。
重要的! 不要下載最新版本 19.3.0,它與Quarkus 1.0不兼容,也許 Quarkus 1.1 會?,F(xiàn)在應(yīng)該工作的版本是 GraalVM 19.2.1,得到這個。
配置其環(huán)境變量的主路徑:
## At macOS will be: export
GRAALVM_HOME=/Users/ualter/Developer/quarkus/graalvm-ce-java8-19.2.1/Contents/Home/
然后在你的環(huán)境中安裝 GraalVM 的 Native Image:
$GRAALVM_HOME/bin/gu install native-image
讓我們?yōu)楫?dāng)前平臺生成本機版本(在這種情況下,將為 macOS 生成本機可執(zhí)行文件)。
mvn package -Pnative
quarkus-echo-1.0-SNAPSHOT-runner如果一切正常,我們可以在 ./target 文件夾中找到一個名為的文件。這是您的應(yīng)用程序的可執(zhí)行二進制文件,您只需運行以下命令即可啟動它
./target/quarkus-echo-1.0-SNAPSHOT-runner:無需使用JVM(普通:java -cp app:lib/:etc App.jar*),它是一個本機可執(zhí)行二進制文件。
讓我們?yōu)槲覀兊膽?yīng)用程序生成一個 Native Docker Image。該命令將創(chuàng)建一個 Native 鏡像,即帶有 Linux 原生可執(zhí)行應(yīng)用程序的 Docker 鏡像。默認情況下,本機可執(zhí)行的文件是基于當(dāng)前平臺 (macOS) 創(chuàng)建的,因為我們知道生成的可執(zhí)行文件與容器 (Linux) 的平臺不同,我們將指示 Maven 構(gòu)建從在容器內(nèi),生成原生 docker 鏡像:
mvn package -Pnative -Dquarkus.native.container-build=true
此時,一定要有一個Docker容器運行時,一個工作環(huán)境。
該文件將是一個 64 位 Linux 可執(zhí)行文件,因此很自然,這個二進制文件無法在我們的 macOS 上運行,它是為我們的 docker 容器映像構(gòu)建的。所以,繼續(xù)前進......讓我們?nèi)ド?docker 圖像......
docker build -t ujr/quarkus-echo -f src/main/docker/Dockerfile.native .
## Testing it...
docker run -i --name quarkus-echo --rm -p 8081:8081 ujr/quarkus-echo
附帶說明,關(guān)于 Docker 映像大?。?/p>
最終的 docker 鏡像是115MB,但是你可以使用distroless 鏡像版本來制作一個很小的 Docker 鏡像。Distroless 映像僅包含您的應(yīng)用程序及其運行時依賴項,其他所有內(nèi)容(包管理器、shell 或標(biāo)準(zhǔn) Linux 發(fā)行版中常見的普通程序)都將被刪除。我們應(yīng)用程序的 Distroless 映像大小為42.3MB。該文件
./src/main/docker/Dockerfile.native-distroless有生成它的收據(jù)。
關(guān)于 Distroless Images: “*將運行時容器中的內(nèi)容嚴格限制為應(yīng)用程序所需的內(nèi)容是Google和其他在生產(chǎn)環(huán)境中使用容器多年的科技巨頭采用的最佳實踐*”
spring boot鏡像
到此,想必大家都知道如何制作一個普通的Spring Boot Docker鏡像了,我們就略過細節(jié)吧?只是一個重要的觀察,代碼是完全相同的。更好的說法是,幾乎相同,因為我們使用的是 Spring 框架注解,當(dāng)然。這是唯一的區(qū)別。您可以檢查提供的源代碼中的每個細節(jié)(下面的鏈接)。
mvn install dockerfile:build
## Testing it...
docker run --name springboot-echo --rm -p 8082:8082 ujr/springboot-echo
戰(zhàn)爭
讓我們啟動兩個容器,讓它們啟動并運行幾次,然后比較Startup Time和Memory Footprint。
在這個過程中,每個容器都被創(chuàng)建和銷毀 10 次。后來,分析了它們的啟動時間及其內(nèi)存占用。下面顯示的數(shù)字是基于所有這些測試的平均結(jié)果。
啟動時間
顯然,當(dāng)涉及到可擴展性和無服務(wù)器架構(gòu)時,這方面可能會發(fā)揮重要作用。
關(guān)于 Serverless 架構(gòu),在此模型中,通常一個臨時容器將由事件觸發(fā)以執(zhí)行任務(wù)/功能。在云環(huán)境中,價格通?;趫?zhí)行次數(shù),而不是之前購買的一些計算容量。因此,這里的 冷啟動可能會影響這種類型的解決方案,因為容器(通常)只會在執(zhí)行其任務(wù)時才處于活動狀態(tài)。
在可擴展性中,很明顯,如果需要突然向外擴展,啟動時間將定義容器完全準(zhǔn)備好(啟動并運行)以響應(yīng)呈現(xiàn)的加載場景所需的時間。
場景有多突然(需要和快速),長時間冷啟動的情況可能更糟。
讓我們看看它們在啟動時間方面的表現(xiàn):
好吧,您可能已經(jīng)注意到它是在啟動時間圖中插入的另一個測試選項。實際上,它與 Quarkus 應(yīng)用程序完全相同,但使用 JVM Docker 映像(使用 Dockerfile.jvm)生成。正如我們所看到的,即使是使用 Docker Image 和 JVM Quarkus 應(yīng)用程序的應(yīng)用程序也比 Spring Boot 具有更快的啟動時間。
毋庸置疑,Quarkus Native 應(yīng)用程序顯然是贏家,它是迄今為止啟動速度最快的應(yīng)用程序。
內(nèi)存占用
現(xiàn)在,讓我們檢查一下內(nèi)存的情況。檢查每個容器應(yīng)用程序在啟動時需要消耗多少內(nèi)存,以使自己啟動并運行,準(zhǔn)備好接收請求。
結(jié)論
總之,這就是我們在Linux Ubuntu中看到的結(jié)果:
看起來 Quarkus 贏得了這兩輪比賽(啟動時間和內(nèi)存足跡),以明顯的優(yōu)勢戰(zhàn)勝了對手 SpringBoot。
這可能會讓我們感到疑惑……也許是時候考慮一些真正的測試、經(jīng)驗,并嘗試使用 Quarkus。我們應(yīng)該看看它在現(xiàn)實生活中的表現(xiàn)如何,它如何適合我們的業(yè)務(wù)場景,以及最有用的地方。
但是,我們不要忘記缺點,正如我們在上面看到的,JVM 的某些功能在本機可執(zhí)行二進制文件中(還/很容易)無法工作。無論如何,也許是時候給 Quarkus 一個證明自己的機會了,特別是如果冷啟動問題一直困擾著你。在環(huán)境中使用一兩個配備 Quarkus 的 Pod (K8s) 怎么樣,一段時間后看看它的表現(xiàn)會很有趣,不是嗎?