在使用無伺服器程式碼時,一種相當常見的方法是將其編寫為 Python、Node 或 Go 應用程序,因為它們以非常快速的冷啟動而聞名。
但是,如果我們面臨現有針對 AWS Lambda 等無伺服器環境的 Java 應用程式該怎麼辦?也許我們的大部分程式碼庫都託管 Java,並且我們已經開發了一個豐富的工具和庫生態系統,我們希望重複使用它們。將整個此類應用程式重寫為不同的語言是昂貴的,更不用說,我們正在放棄靜態類型安全性和編譯時最佳化等功能。
不久前,我遇到過這樣的場景:9 個用 Java 編寫的 AWS Lambda 應用程式在冷啟動時會非常慢,甚至其中一些應用程式偶爾會超時。
相關 Lambda 被放置在 API Gateway 後面,並透過呼叫對應的 REST API 用於管理任務。此功能並未被廣泛使用,因此遇到冷啟動是不可避免的;然而,因為這不是一項關鍵服務,所以它是一個絕佳的實驗機會:弄清楚這些 Lambda 是否可以被挽救。
不久之後,我遇到了其他幾篇關於開發人員成功使用 GraalVM 和 Quarkus 等框架來解決這個問題的部落格文章。所以我決定親自嘗試看看。
但是這些工具到底是什麼?
簡而言之,GraalVM 是一個 Java 虛擬機,它附帶了一個工具集,能夠將 Java 編譯為本機映像並使用 Graal JVM 執行它。
通常 Java 使用「即時」(JIT) 編譯器,顧名思義,它在程式碼執行期間執行最佳化和編譯。鑑於 JVM 優化器不斷監視程式的執行並執行微調,隨著時間的推移,可以轉化為更好的效能,因此長時間運行的應用程式可以從中受益。
如果應用程式實例化一次,並且預計運行幾個小時或更長時間,那麼這很好,但如果我們正在處理Kubernetes、AWS Lambda 和希望快速啟動Java 應用程式的批次作業,執行對時間敏感的操作和規模取決於需求- 對於汽車愛好者來說就是渦輪遲滯。
這就是 GraalVM 的 Native Image 功能可以提供幫助的地方。它沒有使用 JIT 編譯器,而是選擇了一種非常不同的提前編譯程式碼的方法 (AOT)。它使用靜態程式碼分析來預先烘焙我們的餡餅,甚至在建置期間預先初始化某些類,以便它們在我們的應用程式程式碼執行時隨時準備好觸發。
結果呢?非常快速的冷啟動,這使得 Native Images 在應用程式生命週期短暫且必須快速啟動的無伺服器領域中非常有能力。
需要注意的一點是,儘管 GraalVM 具有 AOT 能力,但鑑於 GraalVM 用 Java 編寫的新 JIT 編譯器,它也可以作為現有 JVM 的直接替代品,提供更好的性能。
但是等等,還有更多!由於 Native Image 僅包含已知執行路徑上的程式碼,因此我們會修剪多餘的程式碼,並且所有未明確聲明保留的 Java 類別將不可用。因為我們只保留預期執行的位,所以我們提高了應用程式的安全性。
以臭名昭著的 Log4J 漏洞為例,該漏洞使用遠端程式碼執行作為危害主機的手段。對於本機映像,小工具連結不太可能成功,因為傳達攻擊所需的庫程式碼片段甚至無法存取。
Quarkus 另一方面,是一個針對無伺服器應用程式進行最佳化的Java 框架,它附帶一個工具箱,透過提供專門配置和建構AWS Lambda 作為本機可執行檔的擴展,使建置本機映像變得更加容易。
在 Lambda 最佳化之旅中,我還遇到了替代最佳化技術。其中一項最佳化是建議在 Lambda 執行期間獨佔使用 C1 編譯器,這有望提供更快的冷啟動。通常,在 JVM 內執行的 Java 應用程式會使用分層編譯,其中包括速度更快但不太優化的 C1,然後是速度較慢的 C2,但為長時間執行的 Java 應用程式提供更優化的效能。鑑於 Lambda 的壽命很短,C2 編譯的好處可以忽略不計。
此處提供了逐步完成為 AWS Lambda 配置 C1 編譯過程的指南。
當然,我想知道與我現有的 GraalVM 總體規劃相比,該技術可以提供多少改進,因此我也將其包含在下面的研究結果中。
有關 JVM 分層編譯以及 GraalVM 全新 JIT 編譯器的更多詳細信息,請參閱這篇 Baeldung 文章。
諷刺的是,在我將更改交付到生產環境幾個月後,AWS 推出了他們最新的SnapStart 功能,該功能可以拍攝正在運行的Lambda 的快照,而不是重新初始化它,而是使用快照影像作為一個承諾更快冷啟動的恢復點。我必須嘗試一下,看看使用 GraalVM 是否是浪費精力,並將其納入我的發現中。
值得注意的是,為了充分利用 SnapStart,需要進行程式碼重構才能利用 beforeCheckpoint 和 afterRestore 掛鉤(更多詳細資訊請參閱此處)。鑑於我想盡可能避免任何重大程式碼更改,我「按原樣」使用此功能,沒有實現這些方法並重新排列任何程式碼。
現在回到 GraalVM!令我驚訝的是,合併此解決方案後,除了添加和調整建置設定檔和一些必需的元資料之外,絕對不需要更改 Java 程式碼。
聽起來好得令人難以置信?
也許有一點。鑑於我們使用的是 AOT 編譯,在 Java 世界中,如果涉及到使用許多函式庫所依賴的反射、代理、介面和服務註冊表等語言功能,這會帶來一定的挑戰。這就是為什麼 GraalVM 編譯器需要聲明額外的配置元資料來明確註冊某些類別和服務,以便它們可以包含在最終的工件中。 GraalVM 提供了一個所謂的代理,可用於與可執行檔一起運行,以自動識別所需的配置,從而使此過程變得更容易。
Quarkus 為知名庫提供了幾個擴展,使它們“原生圖像友好”,但考慮到我正在使用現有的程式碼庫,我的目標是避免任何重大重構(或與此相關的任何程式碼更改) ),我決定創建現有庫所需的配置文件,以便成功生成原生映像。
請注意,編譯 Native Images 是資源密集的,與針對標準 JVM 執行時期的字節碼編譯相比,它需要更長的時間。您可能會發現自己必須為建立節點分配更多 RAM 以避免記憶體不足問題,這不應該是一個大問題,但絕對是需要記住的事情。
現在我已經編譯並打包了 Native Image Lambda,是時候將它們部署到測試環境中了。通常,Java Lambda 使用AWS 的Java 運行時來執行;然而,考慮到我們正在嘗試使用原生映像,它是一個二進位工件,其中包含封裝在Graal JVM 中的應用程式程式碼,因此我們必須選擇AWS 提供的“自訂”Amazon Linux 環境之一。
我使用 Postman API 集合向所有 9 個 Lambda 發送請求,並測量上述每種技術的冷啟動回應時間。為了確保我總是遇到冷啟動,我重新載入了目標 Lambda 的配置,以確保下一次呼叫不會使用可能已經加熱的實例。所有 Lambda 均配置 1GB RAM。我還測量了每個配置的單次調用,因為該過程非常耗時;然而,觀察到的響應時間描繪了一幅非常清晰的圖景。
所以有效果嗎?絕對地!結果如下:
明顯的贏家是:GraalVM Native Images - 平均而言,與未更改的Java Lambda 相比,它的速度提高了3 倍- 不再有超時,並且響應時間更好,這正是我想要的實現。
如果不更改任何程式碼,SnapStart 的效能並不如我想像的那麼好。當除了 SnapStart 功能之外還使用 C1 編譯器時,它進一步降低了冷啟動時間,但仍然沒有擊敗 GraalVM 的 Native Image。這並不是說它不是一個可行的選擇,因為它是一種快速且易於實施的改進。然而,如果我們想盡可能地優化 Lambda,並且我們有一些時間和資源來調整配置和建置流程,那麼 GraalVM 在效能和安全性方面絕對更勝一籌。
正如 GraalVM 所聲稱的那樣,與常規 JVM 對應物相比,本機映像需要更少的資源才能有效運作。我想看看如果我要減少這些 Lambda 必須使用的 RAM 量,冷啟動和熱啟動效能將如何維持。這次我只選擇了一個 Lambda 應用程式來執行此測試。結果如下:
他們兌現了他們的承諾!常規 JVM Lambda 在嘗試配置 256 MB 或更低時會耗盡內存,而本機映像似乎未分階段並繼續執行。如果不是 128 MB 是最低的可用記憶體選項,我想知道我們還能低多少。原生映像不僅在冷啟動時速度更快,而且在使用有限資源時提供一致的效能,這意味著更低的營運成本。
Java 的生態系統豐富且龐大,每天都會出現許多新技術和增強功能,使 Java 在無伺服器應用程式方面保持領先地位。 GraalVM 就是這樣一種新興技術。最初是一個研究項目,現在慢慢被採用,並成為標準 JVM(例如 HotSpot)的可行替代方案。在這篇文章中,我僅僅觸及了 GraalVM 所提供功能的表面,我鼓勵讀者進一步探索它。 Adyen(文章連結)或 Facebook(文章連結)等公司有多個成功案例,他們能夠利用 GraalVM 來節省時間和金錢。
因此,下次當您打算將 Java 作為選項打折時,請嘗試 GraalVM。現在,Spring Boot 3 開箱即用地支援 GraalVM 本機映像,因此比以往任何時候都更容易將它們用於無伺服器工作負載,以充分利用 GraalVM 提供的效能、低資源消耗和更高的安全性。
以上是Java 也可以無伺服器:使用 GraalVM 實作快速冷啟動的詳細內容。更多資訊請關注PHP中文網其他相關文章!