OpenJDK的“CRaC檢查點協調恢復” - foojay

banq發表於2022-04-19

Java如何協調程式碼的快速啟動和實時優化兩者之間平衡?
Java虛擬機器(JVM)的一個偉大之處在於,它能夠使Java應用程式的效能適應其使用方式。
它可以找出你的程式碼中哪些部分是經常使用的,然後通過其及時編譯程式碼的能力(JIT)來優化程式碼。
但這也意味著,它必須先弄清楚這些部分,然後才能將這些部分編譯成更快的程式碼。
而這需要時間,也就是說,你不能簡單地執行你的程式碼,並認為JVM會立即優化它,使其執行得儘可能快。
這是因為在你的應用程式能夠以最佳狀態執行之前,它需要時間來預熱JVM。

現代應用兩難
如果你有一個長期執行的應用程式,預熱時間,可能在幾秒鐘到幾分鐘的範圍內,通常沒有問題。
但是,這些天來,Java應用程式經常被用於微服務環境,這意味著你可能有很多小的應用程式,它們只是執行了很短的時間,但會經常被重新啟動。
在這種情況下,JVM的預熱時間不是很有幫助,因為在微服務將被再次關閉之前,JVM可能甚至還沒有預熱好。

解決預熱問題的一個方法是提前編譯你的應用程式,並從中建立一個可以快速啟動的本地映象。
但原生映象的缺點是,一旦你的程式碼被靜態編譯為原生程式碼,你就會失去JVM可以進行的執行時優化的能力。

檢查點的協調恢復
那麼問題來了,是否有辦法保留JVM但減少其啟動時間?

答案是,是的,有:使用CRaC,即檢查點的協調恢復。
Azul公司的高階軟體工程師Anton Kozlov是圍繞這個主題的OpenJDK提案的幕後推手,你可以在相關的OpenJDK頁面上找到更多關於這個專案的資訊。

CRaC專案的重點是開發一個Java API,使得儲存和恢復JVM的狀態成為可能,包括當前執行的應用程式。
這個CRaC API是為了協調,加強檢查點/恢復,儘管在技術上檢查點/恢復在某些情況下不需要協調也可以實現。
使用這種方法可以使啟動時間從幾百秒大幅減少到幾十毫秒。
該建議依賴於Linux CRIU(Coordinated Resume In Userspace)專案,加上其他額外的方法。

檢查點的建立
這個想法是用你的應用程式啟動JVM,並對其進行預熱,直到它達到最佳效能。
一旦達到這個狀態,你就建立一個JVM的快照,即所謂的檢查點。
檢查點的建立意味著JVM的當前狀態將被儲存到檔案系統的一組檔案中。
現在你可以將JVM從這組檔案中恢復到一個正在執行的例項,但不需要對它進行預熱。
如果你考慮到部署在容器化環境中的微服務,你可以考慮啟動一個容器,在容器內預熱JVM,並通過停止容器建立一個檢查點。
下次你啟動這個容器時,它就可以從儲存的檢查點恢復JVM。

CRaC API
建立檢查點需要你的應用程式釋放其資源,如資料庫連線、HTTP連線和開啟的檔案,否則檢查點影像可能因依賴可能消失的資源而過時。
擬議的CRaC API提供了一些方法來幫助你在建立檢查點之前釋放資源,並在恢復檢查點的JVM之後連線你的資源。
首先,你需要實現 "jdk.crac "包中的資源介面。
這個介面提供了兩個方法,"beforeCheckpoint() "和 "afterRestore()"。
為了使其工作,你還需要通過呼叫 "Core.getGlobalContext().register() "將你的資源註冊到一個全域性上下文:

public class Main implements Resource {

  public Main() {
    Core.getGlobalContext().register(Main.this);
  }

  @Override
  public void beforeCheckpoint(Context<? extends Resource> context) throws Exception {
    // Free your resources here
  }

  @Override
  public void afterRestore(Context<? extends Restore> context) throws Exception {
    // Load your resources here
  }

}


為了能夠檢查一切是否按預期工作,目前的實現會在你有開放的資源時丟擲異常,例如開放的套接字。
當你觸發檢查點時,JVM的堆將被清理和壓縮,這樣JVM就處於安全狀態。

CRaC專案還處理由JVM產生的檔案。
因為它依賴於CRIU,CRaC專案與CRIU捆綁在一起,這意味著你不需要手動安裝它。

檢查點可以通過shell中的jcmd工具來建立,也可以從程式碼本身呼叫Core.checkpointRestore()。
這將建立檢查點並退出應用程式。
註冊資源是通過在檢查點建立前和檢查點恢復後通知一個全域性上下文來完成的。

入門

  1. 獲取已經包含 CRaC 功能的OpenJDK 構建。
  2. 在 GitHub 上獲取我建立的基本示例,讓您瞭解 CRaC 的工作原理。

簡而言之,該示例將每 5 秒呼叫一次迴圈。在該迴圈中,它將檢查 100000 次,如果 1 - 100000 之間的隨機數是素數。
在進行實際計算之前,它會檢查結果是否已經在快取中。
如果找到快取中的數字,則直接返回結果,如果沒有,則計算結果,將其放入快取中,然後返回。
在檢視應用程式效能時,這將導致與普通應用程式類似的行為。
一開始,快取是空的,這導致每個數字至少計算一次。隨著時間的推移,效能會提高,因為快取會越來越滿。
有關如何設定 CRaC 和執行示例的資訊可以在自述檔案中找到。
關於 CRaC 的更多資訊可以在這裡找到:https://openjdk.java.net/projects/crac/https://wiki.openjdk.java.net/display/crac
 

相關文章