領先一步
VMware 提供培訓和認證,助您加速進步。
瞭解更多隨著Spring Framework 6.1和Spring Boot 3.2全面可用性的臨近,我們想分享Spring團隊正在為開發者最佳化應用程式執行時效率所做的幾項工作的概述。
我們將涵蓋以下技術和用例
如果您喜歡觀看影片而不是閱讀部落格文章,我們推薦觀看 Devoxx Belgium 2023 的“Spring Framework 6: Strategic Themes”演示。
讓我們從最重要的問題開始:為什麼我們應該關注提高雲工作負載的執行時效率?第一個原因可能是成本最佳化。我們都希望以更便宜的方式執行我們的應用程式。更便宜的託管通常意味著使用更少的 CPU、更少的記憶體、更少的資源,這使得我們的工作負載更具可持續性。我們還生活在一個執行應用程式可能以某種方式涉及 Kubernetes 和容器的世界中,這通常需要額外關注 Java 虛擬機器啟動時間、預熱時間和記憶體管理。
Spring 團隊的目標是允許各種選項(其中一些可以組合使用)來最佳化生產中數百萬 Spring 工作負載的執行時佔用空間和可伸縮性。我們的目標是儘可能減少您的 Spring 應用程式所需的更改量,以便利用這些改進,但當然通常會涉及權衡,我們將盡可能明確地說明這些權衡。希望這能為您提供足夠的資訊,以便您更清楚地瞭解它如何應用於您的組織、您的應用程式,並檢視哪些權衡對您的上下文來說是值得的。
利用這些執行時效率改進的常見要求是升級到基於 Spring Framework 6 的 Spring Boot 3,它具有 Java 17 基線,並且需要從 Java EE (javax 包) 過渡到 Jakarta EE (jakarta 包)。當您進行此類升級時,將為您提供一套新的執行時效率功能。
讓我們從 Java 21 中剛剛釋出的技術開始。虛擬執行緒旨在降低以簡單且流行的每請求執行緒方式編寫的伺服器應用程式的成本,以實現接近最優的硬體利用率。
虛擬執行緒使 I/O 阻塞成本低廉,因此非常適合 Servlet 堆疊上的 Spring Web MVC 應用程式。Spring MVC 可以充分利用 Tomcat 或 Jetty 等具有虛擬執行緒設定的新執行時特性。這在大多數用例中不需要程式碼更改,並且可以自然地適應,以提供最佳效能,而無需微調執行緒池配置。
我們也聽到了 Spring 社群的反饋,他們要求我們不僅在維護模式下的 RestTemplate 和響應式 WebClient 之間做出選擇。因此,我們決定在 Spring Framework 6.1 中引入一個名為 RestClient 的“虛擬執行緒友好型現代 HTTP 客戶端”(當然,在沒有虛擬執行緒的情況下也是一個有吸引力的選項)。Spring Cloud Gateway 和 Spring 產品組合中的相關基礎設施同樣可以從虛擬執行緒設定以及 Spring MVC 中受益,提供一致的整體體驗。
那麼,這對 WebFlux 和響應式堆疊意味著什麼呢?
我們有意選擇具有獨立的阻塞和響應式堆疊,以充分利用 WebFlux 伺服器中的響應式特性,並使 Spring Web MVC 堆疊(迄今為止 start.spring.io 上使用最頻繁的 Web 堆疊)儘可能精簡,採用常規的阻塞執行緒架構。Servlet 容器上的 Spring MVC 非常適合虛擬執行緒,作為提高傳統 Web 應用程式可伸縮性的誘人解決方案。另一方面,WebFlux 伺服器提供了一個最佳化的響應式堆疊,非常適合 Netty I/O 設定,透過不同的程式設計模型提供等效的執行時優勢。
當您需要應用程式級併發(例如傳送多個遠端 HTTP 請求,可能流式傳輸,並組合結果)時,Project Loom 結構化併發在未來可能會提供一個有趣的低階構建塊,但這並非開發人員通常在 Spring 應用程式中需要的 API(而且它仍處於預覽階段)。對於此類用例,WebFlux 和 Reactor 等響應式 API 暫時具有無與倫比的附加價值,還有 Kotlin Coroutines 及其 Flow 型別,它提供了命令式和宣告式程式設計模型的有趣組合。RSocket 是響應式互動模型帶來巨大附加價值的另一個例子。
請注意,您不必二選一,因為 Spring MVC 也提供可選的響應式支援。因此,如果您的伺服器應用程式只需要處理幾個用例的併發性,您只需使用帶有虛擬執行緒設定的 Spring MVC 堆疊,並在您的 Web 控制器中無縫地包含例如響應式 WebClient 互動,Spring MVC 會將響應式返回值轉換為 Servlet 非同步響應。Spring MVC 中的這種響應式支援是完全可選的,只有在實際使用響應式端點時才需要堆疊中的 Reactor 和 Reactive Streams,並且 HTTP 堆疊基於 Tomcat 或 Jetty(而不是 Netty)等 Servlet 容器。
對於典型的 Web 場景,我們預計虛擬執行緒將成為 Java 21+ 上 Spring 開發人員使用 Spring MVC 作為精簡 Web 伺服器堆疊的常見選擇。更廣泛的 Java 生態系統仍需完全適應虛擬執行緒,避免任何執行緒固定,例如在常見的 JDBC 驅動程式實現中,但預計這也將很快解決。請務必使用 Spring Boot 3.2 或更高版本,將屬性 spring.threads.virtual.enabled 設定為 true,並使用可用於評估虛擬執行緒的最新庫和驅動程式版本。
我們繼續完善 Spring Boot 3 中引入的 GraalVM 原生支援。主要用例是使用 Buildpacks 構建一個最佳化的容器映象,其中包含一個微型作業系統基礎層和您的應用程式,透過 Spring AOT (Ahead Of Time) 轉換和 GraalVM 原生映象編譯器編譯成本地可執行檔案。無需 JVM 發行版。

這允許部署小型容器,這些容器在幾十毫秒內啟動(通常比常規 JVM 上的啟動時間快 50 倍),具有更低的應用程式基礎設施記憶體消耗,並立即獲得峰值效能。
GraalVM 緊密關注新的 Java 功能,例如已經提供了對虛擬執行緒的一流支援:請參閱 Josh Long 最近的 《All together now》部落格文章。

GraalVM 卓越的執行時特性之所以可能,是因為與 JVM 相比進行了不同的權衡。原生映象編譯需要幾分鐘而不是幾秒鐘。它需要額外的元資料才能正確處理反射、代理和 JVM 的其他動態行為。Spring 推斷出許多此類元資料,但任何真實專案都可能需要一些額外的提示才能正常工作(例如,針對您的組織依賴項)。最後,Spring AOT 轉換和 GraalVM 原生映象的組合要求我們在構建時凍結類路徑和 Spring Boot bean 條件。您通常可以在執行時配置中更改資料庫的 URL 或密碼,但不能更改資料庫型別或執行更改 Spring bean 結構的操作。
從歷史上看,另一個缺點是由於缺乏即時編譯而導致的峰值效能有限,但 Oracle GraalVM 在 GraalVM 免費條款和條件許可下發布(請參閱相關限制)挑戰了這一假設。您可以訂閱此相關的 Buildpacks RFC 以跟蹤其潛在的即將到來的支援,並且您已經可以使用這個簡單的Dockerfile作為起點,將其與您的 Spring Boot 工作負載一起試用。
憑藉即時啟動和立即可用的峰值效能,Spring Boot 原生應用程式可以實現零擴充套件。讓我們探討一下這意味著什麼。
零擴充套件是無伺服器的一種概括。工作負載不僅可以部署到無伺服器雲平臺,還可以部署到任何 Kubernetes 或雲平臺,只要它們在沒有請求要處理時提供零擴充套件的能力。使用 Kubernetes,您可以使用 Knative 或 KEDA 等解決方案來實現零擴充套件。而且您不僅限於函式,您可以將任何型別的應用程式、任何型別的程式設計模型(包括傳統 Web 應用程式)擴充套件到零。無伺服器最重要的特性不是技術性的,而是它所實現的按使用量付費的計費模式。

零擴充套件在各種用例中都很有趣。JVM 在開發高流量網站方面非常出色,但老實說,我們也開發了許多小型後臺應用程式,通常並非一直都在使用。當沒有人使用它們時,我們為什麼要付費呢?還有通常只需要執行一小部分時間的暫存環境,以及快取允許大部分時間關閉其中一些的微服務。別忘了高可用性,它迫使我們為每個服務始終保持兩個例項執行以應對緊急情況,因為我們的應用程式啟動時間太長,無法從危險中恢復。
但是對於那些無法接受 GraalVM 原生映象所要求的權衡的專案,如何實現零擴充套件呢?
CRaC 是一個 OpenJDK 專案,它定義了一個新的 Java API,允許您在 HotSpot JVM 上對應用程式進行檢查點和恢復,由 Azul Systems 開發,目前也受到 AWS Lambda 和 IBM OpenLiberty 的支援。它基於 CRIU,一個在 Linux 上實現檢查點/恢復功能的專案。
原理如下:您幾乎像往常一樣啟動應用程式,但使用啟用了 CRaC 的 JDK 版本。然後,在某個時刻,可能在執行所有常見程式碼路徑使 JVM 變熱的一些工作負載之後,您透過 API 呼叫、jcmd 命令、HTTP 端點或另一種機制觸發檢查點。
執行中的 JVM(包括其“熱度”)的記憶體表示隨後被序列化到磁碟,從而可以在稍後進行非常快速的恢復,可能是在具有類似作業系統和 CPU 架構的另一臺機器上。恢復的程序保留了 HotSpot JVM 的所有功能,包括執行時進一步的 JIT 最佳化。

值得注意的是,“檢查點”和“恢復”與 Spring 應用程式上下文生命週期的停止和啟動階段非常匹配。Spring Framework 6.1 CRaC 支援主要是將 CRaC 和 Spring 生命週期關聯起來,其餘支援與 CRaC 無關,主要是 Spring 生命週期最佳化,旨在正確關閉和重新建立套接字、檔案和池。除了常規的啟動和停止生命週期之外,這裡的目標是支援多次停止和重新啟動迴圈。

與 GraalVM 一樣,Project CRaC 允許應用程式實現零擴充套件,即使在小型伺服器上也能在幾十毫秒內即時啟動。這比常規 JVM 冷啟動快 50 倍,與 GraalVM 本機映象相似。但讓我們探討其中涉及的權衡。

第一個權衡是 CRaC 要求您在投入生產之前提前啟動應用程式。那麼您應該在 CI/CD 平臺上啟動它嗎?是帶上還是不帶上您的生產遠端服務?這提出了一系列非簡單的問題。
第二個權衡是,任何涉及套接字、檔案和池的功能都需要關閉,然後根據 CRaC 生命週期正確重新建立這些資源。對於支援範圍內的功能,Spring Boot 會為您處理。但是有些庫目前還不支援,所以您的整個堆疊可能需要一些時間才能完全支援。
我們認為第三個權衡是最棘手的。建立一個自包含的、隨時可恢復的容器映象可能很有誘惑力。但在檢查點啟動期間載入到記憶體中的任何秘密資訊都將被序列化到快照檔案中,從而可能洩露敏感資訊,例如您的生產資料庫密碼。
一個潛在的解決方案是在沒有生產環境配置的情況下執行檢查點啟動,並在恢復時更新您的應用程式配置。這可以透過使用 Spring Cloud Context 和 @RefreshScope 註解來完成。Spring 團隊未來可能會探討這個主題,看看是否有更多內建支援是有意義的。您還可以採取在 Kubernetes 平臺上直接在加密捲上建立和儲存快照檔案的策略,即使這需要更深入的平臺整合。
最後一個關鍵特性是 CRaC 僅限於 Linux,並且需要一些 Linux 能力微調才能在非特權模式下工作。
請記住,我們正處於 Project CRaC 的早期階段,Spring Boot 3.2 是第一個支援它的版本。隨著檢查點恢復技術與 Spring 的支援一起發展,其中一些限制可能會解除。如果您想自己嘗試這項技術,請檢視 Spring Framework 相關文件 和 https://github.com/sdeleuze/spring-boot-crac-demo。
我們已經看到了兩種透過 GraalVM 和 CRaC 實現 Spring 工作負載零擴充套件的方式,但都涉及非平凡的權衡。如果有一種方法可以在更少約束的情況下改善 Spring Boot 的執行時特性呢?
您可能聽說過 Project Leyden,這是一個新的 OpenJDK 專案,旨在改善 Java 程式的啟動時間、達到峰值效能的時間和佔用空間。如果您想了解更多資訊,我們建議觀看 Brian Goetz 本人的相關演講。
Project Leyden 最近引入了“premain”最佳化(基本上是加強版的類資料共享 + AOT),有趣的是,Java 平臺團隊發現了它與最初為支援 GraalVM 原生映象而建立的 Spring 提前編譯 (AOT) 最佳化之間存在巨大的協同作用,Spring AOT 最佳化已經能夠在 JVM 上提供 15% 更快的啟動時間。

雖然“premain”最佳化仍處於高度實驗階段(目前它只是 Leyden GitHub 倉庫的一個實驗性分支),但 Spring 團隊最近透過結合 JVM 上的 Spring AOT 和 Project Leyden 的這些最佳化,已經能夠將 Spring Petclinic 示例應用程式的啟動時間加快 2 到 4 倍,並且預熱速度也更快,幾乎沒有任何權衡。
以目前的形式,與 GraalVM 和 CRaC 不同,這些最佳化無法實現零擴充套件,因為它們不允許應用程式在生產環境中在幾十毫秒內啟動。但是,如果我們在 JVM 啟動和預熱時間方面獲得顯著改善,並且幾乎沒有任何限制,那麼它就有可能成為主流,並與您可以按需選擇的其他即將推出的 Leyden 功能結合使用。
我們很高興地宣佈,我們已經啟動了 Java 平臺組和 Spring 團隊之間的合作,以探索 Project Leyden 的 premain 方法能夠將可能性推向多遠。結合專門為 JVM 設計的 Spring AOT 改進,我們期望能夠對廣泛的 Spring 應用程式進行進一步最佳化。我們將在未來幾個月分享更多資訊。
如果您想自己嘗試,請檢視 https://github.com/sdeleuze/spring-boot-leyden-demo 倉庫。
傾聽來自世界各地 Spring 社群的反饋已被證明是 Spring 團隊靈感的關鍵來源,與 Oracle、Bellsoft、Azul 等公司務實合作也同樣重要。
我們正在努力支援這些新功能,同時儘量減少對 Spring 應用程式開發的影響,為各種應用程式提供直接的升級路徑。這是我們戰略基礎設施工作中最具挑戰性但也是最有意義的方面。
最後但同樣重要的是,我們正在尋求關於您的組織和專案最感興趣的反饋。您認為零擴充套件和按使用付費的計費模式是否值得 GraalVM 或 CRaC 所要求的權衡?GraalVM 原生映象提供的記憶體消耗降低對您來說是一個關鍵優勢嗎?您認為 JVM 上的 Spring AOT 與 Project Leyden 結合具有巨大潛力嗎?您對虛擬執行緒有什麼看法?請告訴我們!