Skip to content

Commit 43f0ae0

Browse files
committed
CAMEL-20371: Asynchrnous Camel health checks
1 parent 95b661a commit 43f0ae0

File tree

5 files changed

+227
-0
lines changed

5 files changed

+227
-0
lines changed

core/camel-spring-boot/src/main/docs/spring-boot.adoc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,24 @@ or by providing GraalVM JSON hint files that can be generated by the https://doc
417417

418418
For more details about `GraalVM Native Image Support` in Spring Boot please refer to https://docs.spring.io/spring-boot/docs/current/reference/html/native-image.html
419419

420+
== Camel Asynchronous Health Checks
421+
422+
Camel health checks can be executed asynchronously via a Task Scheduler so that the result can be cached and the actual health check is executed in background every few seconds. Asynchronous Camel health checks are disabled by default but can be enabled with the following property:
423+
424+
[source,properties]
425+
----
426+
camel.health.async-camel-health-check=true
427+
----
428+
429+
moreover the Camel health check task scheduler can be customized with the following properties:
430+
431+
[source,properties]
432+
----
433+
camel.health.healthCheckPoolSize=5
434+
camel.health.healthCheckFrequency=10
435+
camel.health.healthCheckThreadNamePrefix=CamelHealthTaskScheduler
436+
----
437+
420438
== Camel Readiness and Liveness State Indicators
421439

422440
Camel specific Readiness and Liveness checks can be added to a Spring Boot 3 application including respectively in the

core/camel-spring-boot/src/main/docs/spring-boot.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,13 @@
513513
"sourceType": "org.apache.camel.spring.boot.debug.CamelDebugConfigurationProperties",
514514
"defaultValue": false
515515
},
516+
{
517+
"name": "camel.health.async-camel-health-check",
518+
"type": "java.lang.Boolean",
519+
"description": "Whether Camel Health Checks are executed asynchronously <p> disabled by default",
520+
"sourceType": "org.apache.camel.spring.boot.actuate.health.CamelHealthCheckConfigurationProperties",
521+
"defaultValue": false
522+
},
516523
{
517524
"name": "camel.health.consumers-enabled",
518525
"type": "java.lang.Boolean",
@@ -538,6 +545,27 @@
538545
"sourceType": "org.apache.camel.spring.boot.actuate.health.CamelHealthCheckConfigurationProperties",
539546
"defaultValue": "default"
540547
},
548+
{
549+
"name": "camel.health.health-check-frequency",
550+
"type": "java.lang.Integer",
551+
"description": "Camel's HealthCheck frequency in seconds",
552+
"sourceType": "org.apache.camel.spring.boot.actuate.health.CamelHealthCheckConfigurationProperties",
553+
"defaultValue": 10
554+
},
555+
{
556+
"name": "camel.health.health-check-pool-size",
557+
"type": "java.lang.Integer",
558+
"description": "Camel HealthCheck pool size",
559+
"sourceType": "org.apache.camel.spring.boot.actuate.health.CamelHealthCheckConfigurationProperties",
560+
"defaultValue": 5
561+
},
562+
{
563+
"name": "camel.health.health-check-thread-name-prefix",
564+
"type": "java.lang.String",
565+
"description": "Camel HealthCheck thread name prefix",
566+
"sourceType": "org.apache.camel.spring.boot.actuate.health.CamelHealthCheckConfigurationProperties",
567+
"defaultValue": "CamelHealthTaskScheduler"
568+
},
541569
{
542570
"name": "camel.health.initial-state",
543571
"type": "java.lang.String",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package org.apache.camel.spring.boot.actuate.health;
2+
3+
import org.apache.camel.spring.boot.actuate.health.liveness.CamelLivenessStateHealthIndicator;
4+
import org.apache.camel.spring.boot.actuate.health.readiness.CamelReadinessStateHealthIndicator;
5+
import org.slf4j.Logger;
6+
import org.slf4j.LoggerFactory;
7+
import org.springframework.beans.factory.InitializingBean;
8+
import org.springframework.boot.actuate.health.Health;
9+
import org.springframework.boot.actuate.health.HealthContributorRegistry;
10+
import org.springframework.boot.actuate.health.HealthIndicator;
11+
import org.springframework.boot.actuate.health.NamedContributor;
12+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
13+
import org.springframework.context.annotation.Configuration;
14+
import org.springframework.scheduling.TaskScheduler;
15+
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
16+
17+
import java.time.Duration;
18+
import java.time.ZonedDateTime;
19+
20+
/**
21+
* Configuration class that replace synchronous Camel Health Checks with asynchronous ones.
22+
*
23+
* This implementation is based on https://github.com/spring-projects/spring-boot/issues/2652 that most probably
24+
* will be added in spring boot 3.2.x as a new feature in the future.
25+
*
26+
* TODO: To be refactored once async health contributors feature will be added in spring boot.
27+
*
28+
*/
29+
@Configuration
30+
@ConditionalOnProperty(name = "camel.health.async-camel-health-check", havingValue = "true")
31+
public class AsyncHealthIndicatorAutoConfiguration implements InitializingBean {
32+
private static final Logger log = LoggerFactory.getLogger(AsyncHealthIndicatorAutoConfiguration.class);
33+
34+
private HealthContributorRegistry healthContributorRegistry;
35+
private TaskScheduler taskScheduler;
36+
private CamelHealthCheckConfigurationProperties config;
37+
38+
public AsyncHealthIndicatorAutoConfiguration(HealthContributorRegistry healthContributorRegistry,
39+
CamelHealthCheckConfigurationProperties config) {
40+
this.healthContributorRegistry = healthContributorRegistry;
41+
this.config = config;
42+
43+
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
44+
threadPoolTaskScheduler.setPoolSize(config.getHealthCheckPoolSize());
45+
threadPoolTaskScheduler.setThreadNamePrefix(config.getHealthCheckThreadNamePrefix());
46+
threadPoolTaskScheduler.initialize();
47+
taskScheduler = threadPoolTaskScheduler;
48+
}
49+
50+
@Override
51+
public void afterPropertiesSet() throws Exception {
52+
for (NamedContributor<?> namedContributor : healthContributorRegistry) {
53+
final String name = namedContributor.getName();
54+
final Object contributor = namedContributor.getContributor();
55+
if (contributor instanceof CamelHealthCheckIndicator
56+
|| contributor instanceof CamelLivenessStateHealthIndicator
57+
|| contributor instanceof CamelReadinessStateHealthIndicator) {
58+
HealthIndicator camelHealthCheckIndicator = (HealthIndicator) contributor;
59+
healthContributorRegistry.unregisterContributor(name);
60+
log.debug(
61+
"Wrapping " + contributor.getClass().getSimpleName() + " for async health scheduling");
62+
WrappedHealthIndicator wrappedHealthIndicator =
63+
new WrappedHealthIndicator(camelHealthCheckIndicator);
64+
healthContributorRegistry.registerContributor(name, wrappedHealthIndicator);
65+
taskScheduler.scheduleWithFixedDelay(
66+
wrappedHealthIndicator, Duration.ofSeconds(config.getHealthCheckFrequency()));
67+
}
68+
}
69+
}
70+
71+
/**
72+
* Health Check Indicator that executes Health Checks within a Task Scheduler
73+
*/
74+
private static class WrappedHealthIndicator implements HealthIndicator, Runnable {
75+
private static final String LAST_CHECKED_KEY = "lastChecked";
76+
private static final String LAST_DURATION_KEY = "lastDuration";
77+
78+
private HealthIndicator wrappedHealthIndicator;
79+
80+
private Health lastHealth;
81+
82+
public WrappedHealthIndicator(HealthIndicator wrappedHealthIndicator) {
83+
this.wrappedHealthIndicator = wrappedHealthIndicator;
84+
}
85+
86+
@Override
87+
public Health health() {
88+
Health lastHealth = getLastHealth();
89+
if (lastHealth == null) {
90+
setLastHealth(getAndWrapHealth());
91+
lastHealth = getLastHealth();
92+
}
93+
94+
return lastHealth;
95+
}
96+
97+
private Health getAndWrapHealth() {
98+
ZonedDateTime startTime = ZonedDateTime.now();
99+
Health baseHealth = getWrappedHealthIndicator().health();
100+
ZonedDateTime endTime = ZonedDateTime.now();
101+
Duration duration = Duration.between(startTime, endTime);
102+
return Health.status(baseHealth.getStatus())
103+
.withDetails(baseHealth.getDetails())
104+
.withDetail(LAST_CHECKED_KEY, startTime)
105+
.withDetail(LAST_DURATION_KEY, duration)
106+
.build();
107+
}
108+
109+
@Override
110+
public void run() {
111+
setLastHealth(getAndWrapHealth());
112+
}
113+
114+
public HealthIndicator getWrappedHealthIndicator() {
115+
return wrappedHealthIndicator;
116+
}
117+
118+
public Health getLastHealth() {
119+
return lastHealth;
120+
}
121+
122+
public void setLastHealth(Health lastHealth) {
123+
this.lastHealth = lastHealth;
124+
}
125+
}
126+
}

core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/actuate/health/CamelHealthCheckConfigurationProperties.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,28 @@ public class CamelHealthCheckConfigurationProperties {
6363
*/
6464
private String excludePattern;
6565

66+
/**
67+
* Camel's HealthCheck frequency in seconds
68+
*/
69+
private int healthCheckFrequency = 10;
70+
71+
/**
72+
* Camel HealthCheck pool size
73+
*/
74+
private int healthCheckPoolSize = 5;
75+
76+
/**
77+
* Camel HealthCheck thread name prefix
78+
*/
79+
private String healthCheckThreadNamePrefix = "CamelHealthTaskScheduler";
80+
81+
/**
82+
* Whether Camel Health Checks are executed asynchronously
83+
* <p>
84+
* disabled by default
85+
*/
86+
private boolean asyncCamelHealthCheck = false;
87+
6688
/**
6789
* Sets the level of details to exposure as result of invoking health checks. There are the following levels: full,
6890
* default, oneline
@@ -158,6 +180,38 @@ public String getInitialState() {
158180
public void setInitialState(String initialState) {
159181
this.initialState = initialState;
160182
}
183+
184+
public int getHealthCheckFrequency() {
185+
return healthCheckFrequency;
186+
}
187+
188+
public void setHealthCheckFrequency(int healthCheckFrequency) {
189+
this.healthCheckFrequency = healthCheckFrequency;
190+
}
191+
192+
public int getHealthCheckPoolSize() {
193+
return healthCheckPoolSize;
194+
}
195+
196+
public void setHealthCheckPoolSize(int healthCheckPoolSize) {
197+
this.healthCheckPoolSize = healthCheckPoolSize;
198+
}
199+
200+
public String getHealthCheckThreadNamePrefix() {
201+
return healthCheckThreadNamePrefix;
202+
}
203+
204+
public void setHealthCheckThreadNamePrefix(String healthCheckThreadNamePrefix) {
205+
this.healthCheckThreadNamePrefix = healthCheckThreadNamePrefix;
206+
}
207+
208+
public boolean isAsyncCamelHealthCheck() {
209+
return asyncCamelHealthCheck;
210+
}
211+
212+
public void setAsyncCamelHealthCheck(boolean asyncCamelHealthCheck) {
213+
this.asyncCamelHealthCheck = asyncCamelHealthCheck;
214+
}
161215
}
162216

163217

core/camel-spring-boot/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ org.apache.camel.spring.boot.CamelAutoConfiguration
1919
org.apache.camel.spring.boot.actuate.console.CamelDevConsoleAutoConfiguration
2020
org.apache.camel.spring.boot.actuate.endpoint.CamelRouteControllerEndpointAutoConfiguration
2121
org.apache.camel.spring.boot.actuate.endpoint.CamelRoutesEndpointAutoConfiguration
22+
org.apache.camel.spring.boot.actuate.health.AsyncHealthIndicatorAutoConfiguration
2223
org.apache.camel.spring.boot.actuate.health.CamelHealthCheckAutoConfiguration
2324
org.apache.camel.spring.boot.actuate.health.CamelAvailabilityCheckAutoConfiguration
2425
org.apache.camel.spring.boot.actuate.info.CamelInfoAutoConfiguration

0 commit comments

Comments
 (0)