implementation("io.micronaut.crac:micronaut-crac")
Micronaut CRaC
Adds support for CRaC (Coordinated Restore at Checkpoint) to the Micronaut Framework.
Version:
1 Introduction
This Micronaut module adds support for CRaC (Coordinated Restore at Checkpoint) within the framework.
2 Release History
For this project, you can find a list of releases (with release notes) here:
3 Installation
Add the following the dependency to your build:
<dependency>
<groupId>io.micronaut.crac</groupId>
<artifactId>micronaut-crac</artifactId>
</dependency>
4 CRaC Resources
4.1 DataSources
We currently support Hikari DataSources that are configured to allow suspension with:
datasources.default.allow-pool-suspension=true
datasources:
default:
allow-pool-suspension: true
[datasources]
[datasources.default]
allow-pool-suspension=true
datasources {
'default' {
allowPoolSuspension = true
}
}
{
datasources {
default {
allow-pool-suspension = true
}
}
}
{
"datasources": {
"default": {
"allow-pool-suspension": true
}
}
}
When a checkpoint is taken, the pool is suspended and the connections are closed. When the checkpoint is restored, the pool is resumed and the connections are re-established.
4.2 Redis
Since v1.2.1 of this library, we also support Redis connections.
When the checkpoint is taken, we destroy RedisClient
, RedisCache
and StatefulRedisConnection
beans that exist in the application.
These will be re-created once the checkpoint is restored, however you will need to add your beans to the refresh scope so that these new beans are used.
The resources for each of the above can be disabled via configuration:
crac.redis.enabled=false
crac.redis.client-enabled=false
crac.redis.cache-enabled=false
crac.redis.connection-enabled=false
crac:
redis:
enabled: false # disable all redis support
client-enabled: false # disable RedisClient support
cache-enabled: false # disable RedisCache support
connection-enabled: false # disable StatefulRedisConnection support
[crac]
[crac.redis]
enabled=false
client-enabled=false
cache-enabled=false
connection-enabled=false
crac {
redis {
enabled = false
clientEnabled = false
cacheEnabled = false
connectionEnabled = false
}
}
{
crac {
redis {
enabled = false
client-enabled = false
cache-enabled = false
connection-enabled = false
}
}
}
{
"crac": {
"redis": {
"enabled": false,
"client-enabled": false,
"cache-enabled": false,
"connection-enabled": false
}
}
}
4.3 Custom CRaC Resources
To provide custom CRaC resources, create beans of type OrderedResource.
Micronaut CRaC registers resources for you into the CRaC Context. You just focus on providing implementations for OrderedResource::beforeCheckpoint
and OrderedResource::afterRestore
in your resources.
Micronaut CRaC registers resources in order. You can control the order by overriding OrderedResource::getOrder
.
5 Refresh scope
Prior to a checkpoint being taken, a RefreshEvent will be published to invalidate all beans in the @Refreshable scope.
This behaviour can be disabled by setting the crac.refresh-beans
property to false
in the application config.
6 Events
To notify external components when the default Resource handlers execute, there are two events; BeforeCheckpointEvent and AfterRestoreEvent.
These events contain the java.time.Instant
that the action completed, the length of time it took to execute the action in nanoseconds, and the Resource that was acted upon.
Please see the Micronaut Framework guide for information on how to listen for these events.
7 Context Provider
GlobalCracContextProvider, Micronaut CRaC’s default implementation of CracContextProvider, returns the global context. You can provide a replacement for CracContextProvider and provide your custom Context.
8 Docker Support
Support for building CRaC-enabled Docker images is provided by the Micronaut Gradle or Maven plugin. Either one is capable of generating a docker image containing a CRaC enabled JDK and a pre-warmed, checkpointed application.
9 Testing
When you create a Micronaut Framework application (either via https://launch.micronaut.io or the command line application), it creates a ContextConfigurer with enables eager singleton initialization.
As tests annotated with @MicronautTest
are implicitly in the Singleton
scope, this can cause problems injecting some beans (for example an HttpClient
) into your test class.
To avoid this, you can either disable eager singleton initialization for your tests, or you will need to manually get an instance of the bean you would normally inject. As an example, to get an HttpClient
you could do:
@Inject
EmbeddedServer server; // (1)
Supplier<HttpClient> clientSupplier = SupplierUtil.memoizedNonEmpty(() -> // (2)
server.getApplicationContext().createBean(HttpClient.class, server.getURL())
);
@Test
void testClient() {
assertEquals("ok", clientSupplier.get().toBlocking().retrieve("/eager")); // (3)
}
@Inject
EmbeddedServer server // (1)
@Memoized // (2)
HttpClient clientSupplier() {
server.applicationContext.createBean(HttpClient, server.URL)
}
void 'test client'() {
expect:
clientSupplier().toBlocking().retrieve("/eager") == "ok" // (3)
}
@field:Inject
lateinit var server: EmbeddedServer // (1)
val client by lazy {
server.applicationContext.createBean(HttpClient::class.java, server.url) // (2)
}
@Test
fun testClient() {
assertEquals("ok", client.toBlocking().retrieve("/eager")) // (3)
}
1 | Inject the EmbeddedServer as normal |
2 | Lazily create a HttpClient when it is first called |
3 | Get the HttpClient and make the request |
10 Repository
You can find the source code of this project in this repository: