0%

Health Indicator for Routing Data Source

csongyu/health-indicator-routing-data-source

DB Unknown Status

javax.sql.DataSource 接口的实现类为 org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource 的子类时,调用 /actuator/health 接口返回 DB 的健康状态为 UNKNOWN。

1
{"status":"UP","details":{"db":{"status":"UNKNOWN"},"diskSpace":{"status":"UP","details":{"total":494384795648,"free":391930544128,"threshold":10485760}}}}

原因在于:

org.springframework.boot.actuate.autoconfigure.jdbc.DataSourceHealthIndicatorAutoConfiguration

1
2
3
4
5
6
7
8
9
10
11
12
private Map<String, DataSource> filterDataSources(Map<String, DataSource> candidates) {
if (candidates == null) {
return null;
}
Map<String, DataSource> dataSources = new LinkedHashMap<>();
candidates.forEach((name, dataSource) -> {
if (!(dataSource instanceof AbstractRoutingDataSource)) {
dataSources.put(name, dataSource);
}
});
return dataSources;
}

org.springframework.boot.actuate.jdbc.DataSourceHealthIndicator

1
2
3
4
5
6
7
8
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
if (this.dataSource == null) {
builder.up().withDetail("database", "unknown");
} else {
doDataSourceHealthCheck(builder);
}
}

自定义 Health Indicator

通过 org.springframework.jdbc.core.JdbcTemplate 执行 SELECT 1 FROM DUAL 语句检查 DB 的健康状态。

xyz.csongyu.healthindicator.RoutingDataSourceHealthIndicator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@PostConstruct
@SuppressWarnings("unchecked")
public void init() throws NoSuchFieldException, IllegalAccessException {
final Field field = AbstractRoutingDataSource.class.getDeclaredField("resolvedDataSources");
field.setAccessible(true);
final Map<Object, DataSource> resolvedDataSources = (Map<Object, DataSource>)field.get(this.dataSource);
this.jdbcTemplates = resolvedDataSources.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, entry -> new JdbcTemplate(entry.getValue())));
}

@Override
public Health health() {
final List<Health> results = this.jdbcTemplates.entrySet().stream().map(entry -> {
final JdbcTemplate jdbcTemplate = entry.getValue();
try {
jdbcTemplate.queryForObject("SELECT 1 FROM DUAL", String.class);
return Health.up().withDetail("dataSource", entry.getKey()).build();
} catch (final DataAccessException e) {
return Health.down().withDetail("dataSource", entry.getKey()).withException(e).build();
}
}).collect(Collectors.toList());

...
}
1
{"status":"UP","details":{"routingDataSource":{"status":"UP","details":{"DATA_SOURCE_B":"UP","DATA_SOURCE_A":"UP"}},"diskSpace":{"status":"UP","details":{"total":494384795648,"free":391919927296,"threshold":10485760}}}}