Wie wird CRaC mit Java verwendet

Wie wird CRaC mit Java verwendet

Was ist CRaC

CRaC steht für Coordinated Restore at Checkpoint. Damit ist es möglich Javaprogramme zu einem beliebigen Zeitpunkt anzuhalten, den aktuellen Zustand festzuhalten und zu einem späteren Zeitpunkt wieder fortzusetzen. Dabei ist es nicht zwingend erforderlich wieder auf dem gleichen System fortzufahren. Zum jetzigen Zeitpunkt prüft die CRaC Implementierung auf offene Dateien und Sockets. Die Erstellung eines Checkpoints wird mit abgebrochen und eine Exception geworfen, wenn das der Fall ist. Derzeit wird CRaC nur unter Linux unterstützt.

Voraussetzungen

  • JDK 21 mit CRaC Unterstützung
  • Eine beliebige IDE

Bitte beachten das mit deb und rpm Paketen CRaC automatisch funktioniert da diese Pakete die notwendigen Rechte für die Datei criu automatisch setzen. Für den Fall einer Installation aus einem tar.gz Paket müssen die Rechte selber nachträglich gesetzt werden. Dies ist notwendig da dies als Wrapper um den eigentlichen JVM-Prozess verwendet wird.

sudo chown root:root jdk-21.0.1-crac/lib/criu sudo chmod u+s jdk-21.0.1-crac/lib/criu

Ein einfaches Beispiel

Zunächst erstellen wir ein einfaches Javaprogramm

public class CracTest { public class CracTest { public static void main(String[] args) throws InterruptedException { long startTime = System.currentTimeMillis(); for (int counter = 0; counter <100; counter++) { Thread.sleep(1000); final long currentTime = System.currentTimeMillis(); System.out.println("Schritt: "+counter+" (Zeit "+ (currentTime-startTime)+")"); startTime=currentTime; } } } }

Bitte beachten Sie das CRaC den exakten Zustand der ausgeführten Anwendung speichert. Dieses Abbild kann Passwörter oder andere sensitiven Informationen enthalten.

Kompilieren und Starten des Beispiels

javac CracTest.java java -XX:CRaCCheckpointTo=checkpoint-verzeichnis CracTest

Die Option -XX:CRaCCheckpointTo=checkpoint-verzeichnis zeigt auf das Verzeichnis in dem die Daten der JVM zum Zeitpunkt der Erstellung des Checkpoints gespeichert werden. Ein Checkpoint wird mit dem Kommando jcmd ertstellt werden

jcmd CracTest JDK.checkpoint 442512: CR: Checkpoint ...

Die Ausgabe des Programms sieht folgendermaßen aus

Schritt: 0 (Zeit 1000) Schritt: 1 (Zeit 1002) Schritt: 2 (Zeit 1000) Schritt: 3 (Zeit 1001) Dec 29, 2023 3:16:47 PM jdk.internal.crac.LoggerContainer info INFO: Starting checkpoint

Die Anwendung wurde durch das jcmd-Kommando beendet und der aktuelle Zustand in das angegebene Verzeichnis geschrieben. Und jetzt kommen wir zum interessanten Teil dieser Übung. Wir starten die Anwendung wieder und setzen auf dem abgebrochenen Zustandt wieder auf.

java -XX:CRaCRestoreFrom=checkpoint-verzeichnis

Die Ausgabe sieht folgendermaßen aus:

java -XX:CRaCRestoreFrom=checkpoint-verzeichnis Schritt: 4 (Zeit 45209) Schritt: 5 (Zeit 1033) Schritt: 6 (Zeit 1000) Schritt: 7 (Zeit 1002)

Die vergangene Zeit in Millisekunden in der Zeile Schritt: 4 (Zeit 45209) zeigt, dass die Anwendung gestoppt und wieder gestartet wurde.

Mögliche Probleme und deren Lösung

Um die möglichen Probleme zu verdeutlichen, schreiben wir eine weitere Anwendung:

import org.crac.Context; import org.crac.Core; import org.crac.Resource; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class CRaCTestMitThread { private ScheduledExecutorService executor; private long startTime = System.currentTimeMillis(); private int counter = 0; public static void main(String args[]) throws InterruptedException { CRaCTestMitThread exampleWithCRaC = new CRaCTestMitThread(); exampleWithCRaC.startTask(); } private void startTask() throws InterruptedException { executor = Executors.newScheduledThreadPool(1); executor.scheduleAtFixedRate(() -> { long currentTimeMillis = System.currentTimeMillis(); System.out.println("Anzahl: " + counter + " (Zeit " + (currentTimeMillis-startTime) + ")"); startTime = currentTimeMillis; counter++; }, 1, 1, TimeUnit.SECONDS); Thread.sleep(1000*30); executor.shutdown(); } }

Wir starten die Beispielanwendung und erstellen einen Checkpoint

java -XX:CRaCCheckpointTo=checkpoint-verzeichnis CRaCTestMitThread jcmd CRaCTestMitThread JDK.checkpoint 442948: CR: Checkpoint ...

Die Ausgabe sieht folgendermaßen aus:

Anzahl: 0 (Zeit 1019) Anzahl: 1 (Zeit 999) Anzahl: 2 (Zeit 1000) Anzahl: 3 (Zeit 1000) Dec 29, 2023 3:34:27 PM jdk.internal.crac.LoggerContainer info INFO: Starting checkpoint Killed

Jetzt starten wir die Anwendung wieder:

java -XX:CRaCRestoreFrom=checkpoint-verzeichnis

Die Anwendung beendet sich sofort wieder. Wir haben ein paar Iterationen erwartet stattdessen sind 30 Sekunden sind und die Anwendung hat sich beendet. So die Anwendung sollte den Checkpoint-Event behandeln und entsprechend reagieren. Das kann mithilfe von Klassen aus dem Package jdk.crac erreicht werden.

Wir können nun unsere Anwendung entsprechend modifizieren. Zunächst fügen wir eine neue Dependency zu unserem Projekt hinzu

<dependency> <groupId>org.crac</groupId> <artifactId>crac</artifactId> <version>1.4.0</version> </dependency>

Damit stehen uns die Klasse aus `org.crac zur Verfügung um unsere Klasse anzupassen

import org.crac.Context; import org.crac.Core; import org.crac.Resource; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class CRaCTestMitThread { private ScheduledExecutorService executor; private long startTime = System.currentTimeMillis(); private int counter = 0; class ExampleWithCRaCRestoreResource implements Resource { @Override public void beforeCheckpoint(Context<? extends Resource> context) throws Exception { executor.shutdown(); System.out.println("Behandle Checkpoint"); } @Override public void afterRestore(Context<? extends Resource> context) throws Exception { System.out.println(this.getClass().getName() + " restore."); CRaCTestMitThread.this.startTask(); } } public static void main(String args[]) throws InterruptedException { CRaCTestMitThread exampleWithCRaC = new CRaCTestMitThread(); Core.getGlobalContext().register(exampleWithCRaC.new ExampleWithCRaCRestoreResource()); exampleWithCRaC.startTask(); } private void startTask() throws InterruptedException { executor = Executors.newScheduledThreadPool(1); executor.scheduleAtFixedRate(() -> { long currentTimeMillis = System.currentTimeMillis(); System.out.println("Anzahl: " + counter + " (Zeit " + (currentTimeMillis-startTime) + ")"); startTime = currentTimeMillis; counter++; }, 1, 1, TimeUnit.SECONDS); Thread.sleep(1000*30); executor.shutdown(); } }

Die neue Version probieren wir gleich mal aus

java -XX:CRaCCheckpointTo=checkpoint-dir -cp crac-1.4.0.jar:. CRaCTestMitThread Anzahl: 0 (Zeit 1057) Anzahl: 1 (Zeit 999) Anzahl: 2 (Zeit 1000) Anzahl: 3 (Zeit 1000) Dec 29, 2023 3:52:35 PM jdk.internal.crac.LoggerContainer info INFO: Starting checkpoint Behandle Checkpoint Dec 29, 2023 3:52:35 PM jdk.internal.crac.LoggerContainer info INFO: /home/wkl/javatest/crac-1.4.0.jar is recorded as always available on restore Killed

Anhalten der Anwendung

cmd CRaCTestMitThread JDK.checkpoint 445088: CR: Checkpoint ...

Und jetzt versuchen wir die Anwendung wieder zu starten

java -XX:CRaCRestoreFrom=checkpoint-dir CRaCTestMitThread$ExampleWithCRaCRestoreResource restore. Anzahl: 4 (Zeit 68228) Anzahl: 5 (Zeit 1000) Anzahl: 6 (Zeit 1000) Anzahl: 7 (Zeit 1000) Anzahl: 8 (Zeit 1000)

Das Problem ist gelöst. Wir können programmatisch auf die Erstellung und Wiederherstellung eines Checkpoints reagieren.

Fazit

Ich hoffe, mit dieser kurzen Einführung konnte ich einen Überblick geben, was es mit dem neuen Feature auf sich hat und wie man es benutzt. In einem folgenden Artikel werde ich zeigen wie eine Spring Boot Anwendung angehalten und wieder gestartet werden kann. Spring Boot bietet seit der Version 3.2 eine entsprechende Unterstützung an.

← Zurück