SpringBoot 3.3.5 試用CRaC,啟動(dòng)速度提升3到10倍
今天和小伙伴們來聊一個(gè)稍微新一點(diǎn)的技術(shù)話題---CRaC。
CRaC(Coordinated Restore at Checkpoint,檢查點(diǎn)協(xié)調(diào)恢復(fù))是一個(gè) OpenJDK 項(xiàng)目,旨在解決 Java 應(yīng)用程序啟動(dòng)和預(yù)熱時(shí)間過長(zhǎng)的問題。
Java 應(yīng)用程序啟動(dòng)和預(yù)熱時(shí)間過長(zhǎng)是一個(gè)老大難的問題,目前來看各方也都提出了一些不同的解決思路,之前松哥和大家聊過的 AOT 也能從一定程度上解決啟動(dòng)慢的問題,今天的 CRaC 算是另外一種解決思路。
一、CRaC是什么
CRaC 允許對(duì)運(yùn)行中的 JVM 進(jìn)行“快照”,并將其狀態(tài)(包括應(yīng)用)存儲(chǔ)到磁盤中。
之后,在另一個(gè)時(shí)間點(diǎn),可以將 JVM 從保存的檢查點(diǎn)恢復(fù)到內(nèi)存中。
這個(gè)功能意味著你可以啟動(dòng)應(yīng)用程序、預(yù)熱并創(chuàng)建檢查點(diǎn),然后從這個(gè)檢查點(diǎn)快速恢復(fù),從而顯著減少啟動(dòng)時(shí)間。
二、CRaC的原理
CRaC 的工作原理基于用戶空間檢查點(diǎn)和恢復(fù)(CRIU),這是一個(gè)為 Linux 實(shí)現(xiàn)檢查點(diǎn)和恢復(fù)功能的項(xiàng)目。
CRIU 允許凍結(jié)容器或單個(gè)應(yīng)用程序并從保存的檢查點(diǎn)文件中恢復(fù)它。
CRaC 采用了 CRIU 的通用方法,并增加了一些增強(qiáng)和調(diào)整,使其適用于 Java 應(yīng)用程序。
一般來說,CRaC 的執(zhí)行步驟如下:
- 創(chuàng)建檢查點(diǎn):在應(yīng)用程序運(yùn)行并達(dá)到穩(wěn)定狀態(tài)后,可以創(chuàng)建一個(gè)檢查點(diǎn),這個(gè)檢查點(diǎn)包含了 JVM 的狀態(tài)和應(yīng)用程序的數(shù)據(jù)。
- 存儲(chǔ)檢查點(diǎn):檢查點(diǎn)數(shù)據(jù)被存儲(chǔ)到磁盤上,以便之后可以從中恢復(fù)。
- 恢復(fù)檢查點(diǎn):當(dāng)需要啟動(dòng)應(yīng)用程序時(shí),可以直接從檢查點(diǎn)恢復(fù),而不是從頭開始啟動(dòng)和預(yù)熱 JVM。
這個(gè)感覺就有點(diǎn)類似于大伙平時(shí)使用的 VMWare 的快照功能,在某個(gè)時(shí)間點(diǎn)為系統(tǒng)拍攝一個(gè)快照,下次可以直接從快照啟動(dòng),就比從頭開始啟動(dòng)要快很多。CRaC 所拍快照中不僅包含 JVM,也可以包含你的應(yīng)用信息。
三、CRaC 的應(yīng)用場(chǎng)景
CRaC 特別適用于需要快速啟動(dòng)和恢復(fù)的場(chǎng)景,比如:
- 云原生環(huán)境:在微服務(wù)和無服務(wù)器架構(gòu)中,服務(wù)可能需要頻繁地啟動(dòng)和停止,CRaC 可以顯著減少服務(wù)的啟動(dòng)時(shí)間。
- 開發(fā)和測(cè)試環(huán)境:開發(fā)者可以在開發(fā)和測(cè)試過程中快速恢復(fù)應(yīng)用程序到某個(gè)已知狀態(tài),提高開發(fā)效率。
- 災(zāi)難恢復(fù):在系統(tǒng)發(fā)生故障時(shí),可以快速?gòu)淖罱臋z查點(diǎn)恢復(fù)服務(wù),減少系統(tǒng)停機(jī)時(shí)間。
四、支持版本
從 Spring Boot3.2/Spring6.1 開始對(duì) CRaC 的提供支持,所以如果大伙想體驗(yàn) CRaC,需要選擇合適的 SpringBoot 版本。
同時(shí),由于前文提到的 CRaC 依賴于 Linux 特有的 CRIU,因此 CRaC 目前僅在Linux操作系統(tǒng)上支持。Windows 和 Mac 則不支持。
五、實(shí)踐
首先我們需要安裝支持 CRaC 的 JDK,目前主要有以下兩種 JDK 支持 CRaC:
- Azul Zulu 21.0.1 + CRaC 版本支持 CRaC,適用于 x64 和 aarch64 CPU 架構(gòu),包括 JDK17 和 JDK21。
選擇支持 CRaC 的 JDK
- Liberica JDK 17 和 Liberica JDK 21 提供了對(duì) CRaC 的支持。
接下來在項(xiàng)目中添加 CRaC 依賴:
<dependency>
<groupId>org.crac</groupId>
<artifactId>crac</artifactId>
<version>1.5.0</version>
</dependency>
OK,如此之后,我們的準(zhǔn)備工作就算完成了。
接下來我們需要在項(xiàng)目啟動(dòng)的時(shí)候,指定檢查點(diǎn)的位置,并給出生成檢查點(diǎn)的時(shí)機(jī):
java -Dspring.context.checkpoint=onRefresh -XX:CRaCCheckpointTo=./tmp_checkpoint -jar javaboy-crac-3.3.5.jar
在上面的啟動(dòng)腳本中,我們通過設(shè)置 JVM 系統(tǒng)屬性 -Dspring.context.checkpoint=onRefresh 來啟用自動(dòng)檢查點(diǎn)。這個(gè)屬性會(huì)在 Spring 的 LifecycleProcessor.onRefresh 階段自動(dòng)創(chuàng)建檢查點(diǎn),這個(gè)階段在所有非延遲初始化的 Singleton 實(shí)例化和 InitializingBean#afterPropertiesSet 回調(diào)調(diào)用之后,但在生命周期啟動(dòng)和 ContextRefreshedEvent 發(fā)布之前。也就是說在這個(gè)時(shí)機(jī)創(chuàng)建檢查點(diǎn)(拍攝快照)。
當(dāng)然,如果你想等應(yīng)用程序完全啟動(dòng)之后再拍攝快照,也是可以的。
先用如下命令啟動(dòng)應(yīng)用程序:
java -XX:CRaCCheckpointTo=./tmp_checkpoint -jar javaboy-crac-3.3.5.jar
等待應(yīng)用程序完全啟動(dòng)后,在另一個(gè)終端執(zhí)行以下命令來手動(dòng)觸發(fā)檢查點(diǎn):
jcmd <pid> JDK.checkpoint
其中 <pid> 是應(yīng)用程序的進(jìn)程ID,這將創(chuàng)建檢查點(diǎn)并關(guān)閉應(yīng)用程序。檢查點(diǎn)文件將存儲(chǔ)在指定的文件夾中。
手動(dòng)執(zhí)行檢查點(diǎn)生成的好處是,這個(gè)檢查點(diǎn)包含了框架代碼和應(yīng)用程序代碼,因此啟動(dòng)速度會(huì)更快,因?yàn)榭蚣芤呀?jīng)加載并啟動(dòng)了應(yīng)用程序。
無論哪種方式生成檢查點(diǎn),只要有了檢查點(diǎn),最后一步就是使用這個(gè)檢查點(diǎn)了。
我們可以利用檢查點(diǎn)生成的文件來快速拉起應(yīng)用程序,相關(guān)命令如下:
java -XX:CRaCRestoreFrom=./tmp_checkpoint
總結(jié)下就是,自動(dòng)檢查點(diǎn)適合快速實(shí)現(xiàn)和無需代碼更改的場(chǎng)景,而手動(dòng)檢查點(diǎn)提供了更大的靈活性,允許在應(yīng)用程序完全預(yù)熱后創(chuàng)建檢查點(diǎn),從而可能實(shí)現(xiàn)更快的啟動(dòng)時(shí)間。
好啦,感興趣的小伙伴可以去嘗試下,記得選擇合適的操作系統(tǒng)、JDK 版本以及 Spring Boot 版本哦~