領先一步
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 等 Servlet 容器(而非 Netty)。
對於典型的 Web 場景,我們預計虛擬執行緒將成為 Spring 開發者在 Java 21+ 上使用 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 native image 編譯器將您的應用編譯成原生可執行檔案。不需要 JVM 分發包。
這使得可以部署微型容器,它們可以在幾十毫秒內啟動(通常比普通 JVM 上的啟動時間快 50 倍),應用基礎設施的記憶體消耗更低,並且峰值效能立即可用。
GraalVM 非常緊密地跟進新的 Java 特性,例如已經提供了對虛擬執行緒的一等公民支援:請參閱 Josh Long 最近的博文 All together now。
與 JVM 相比,GraalVM 卓越的執行時特性得益於不同的權衡。原生映象編譯需要幾分鐘而不是幾秒鐘。為了正確處理反射、代理和 JVM 的其他動態行為,它需要額外的元資料。Spring 推斷了大部分這些元資料,但任何實際專案可能都需要一些額外的提示才能正常工作(例如針對您組織的依賴項)。最後,Spring AOT 轉換和 GraalVM 原生映象的組合要求我們在構建時凍結類路徑和 Spring Boot Bean 條件。通常您可以在執行時配置中更改資料庫的 URL 或密碼,但不能更改資料庫型別或執行會改變 Spring Bean 結構的操作。
歷史上,另一個缺點是由於缺乏即時編譯而導致的有限的峰值效能,但在 GraalVM Free Terms and Conditions 許可下發布的 Oracle GraalVM(請參閱相關限制)挑戰了這一假設。您可以訂閱此相關的 Buildpacks RFC 以關注其潛在的未來支援,並且您可以使用這個簡單的 Dockerfile
作為起點,將其用於您的 Spring Boot 工作負載進行嘗試。
憑藉即時啟動和立即可用的峰值效能,Spring Boot 原生應用可以實現零伸縮。讓我們探究這意味著什麼。
零伸縮是無伺服器的一種泛化。工作負載不僅可以部署到無伺服器雲平臺,還可以部署到任何提供在沒有請求處理時能夠伸縮到零的 Kubernetes 或雲平臺。在 Kubernetes 中,您可以使用 Knative 或 KEDA 等解決方案來實現零伸縮。您不限於函式,您可以對任何型別的應用、任何程式設計模型(包括傳統 Web 應用)進行零伸縮。無伺服器最重要的特徵並非技術上的,而是它實現的按使用付費的計費模式。
零伸縮在各種用例中都很有吸引力。JVM 在開發高流量網站方面表現出色,但老實說,我們也開發了許多小型後臺應用,通常不會一直使用。當沒人使用它們時,我們為什麼要為此付費?還有一些通常只需要在很短的時間內執行的預生產環境,以及可以透過快取機制在大部分時間關閉部分例項的微服務。另外,不要忘了高可用性,它迫使我們為每個服務保持兩個例項始終執行,以備緊急情況,因為我們的應用啟動時間太長,無法從故障中快速恢復。
但是,對於無法接受 GraalVM native image 所需權衡的專案,如何實現零伸縮呢?
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 生命週期的一些改進,旨在正確關閉和重新建立 socket、檔案和連線池。這裡的目標是,除了常規的啟動和停止生命週期之外,還支援多次停止和重新啟動迴圈。
與 GraalVM 一樣,Project CRaC 允許應用實現零伸縮,即使在小型伺服器上也能在幾十毫秒內即時啟動。這比常規 JVM 冷啟動快 50 倍,與 GraalVM native image 類似。但讓我們探討一下其中涉及的權衡。
第一個權衡是 CRaC 要求您在投入生產之前預先啟動您的應用。那麼您應該在 CI/CD 平臺上啟動它嗎?是包含還是不包含您的生產遠端服務?這引發了一系列不容忽視的問題。
第二個權衡是任何涉及 socket、檔案和連線池的功能都需要關閉,然後根據 CRaC 生命週期正確重新建立這些資源。Spring Boot 會為您處理受支援的範圍。但有些庫尚不支援此功能,因此可能需要一段時間才能完全支援您的整個技術棧。
在我們看來,第三個權衡是最棘手的。建立一個包含所有內容、可直接恢復的容器映象可能很誘人。但檢查點啟動期間載入到記憶體中的任何秘密資訊都將被序列化到快照檔案中,從而可能洩露生產資料庫密碼等敏感資訊。
一個潛在的解決方案是在沒有生產環境配置的情況下執行檢查點啟動,並在恢復時更新應用配置。這可以透過使用 Spring Cloud Context 和 @RefreshScope 註解來實現。Spring 團隊未來可能會探索這個主題,看看是否有必要提供更多內建支援。您還可以採取策略,直接在 Kubernetes 平臺上加密捲上建立和儲存快照檔案,即使這需要更深入的平臺整合。
最後一個關鍵特徵是 CRaC 是 Linux 特定的,並且需要進行一些Linux capability 微調才能在非特權模式下工作。
請記住,我們正處於 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 平臺團隊發現它與 Spring 預編譯(Ahead-Of-Time,AOT)最佳化具有極好的協同效應,Spring AOT 最初是為了支援 GraalVM native image 而建立的,但已能夠使 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 native image 提供的記憶體消耗降低對您來說是一個關鍵優勢嗎?您認為 JVM 上的 Spring AOT 與 Project Leyden 結合具有很高潛力嗎?您對虛擬執行緒有何看法?請告訴我們!