spring cloud框架之健康检查
引言:由于业务需要,我们框架中引入了多数据源,我们的持久层框架是spring data jpa,第一个数据源是:mysql,也是我们的主数据源,第二个数据源是:oracle,第三个数据源是sap那边的hana数据库。当引入hana数据库时,发现实例起不来了,报错误的sql语法,经过一番研究,发现是健康机制执行时,会检测每个数据源的健康程度,如果健康,则状态为:UP,如果不健康,状态为:DOWN。伴随着好奇心,我探究了下springcloud中的健康检查机制。
springcloud搭配k8s可以搭建健康检测机制,可以发现不健康的实例,并将其重启。
涉及到数据库相关你的健康检查是个配置累:DataSourceHealthIndicatorAutoConfiguration.class
@Configuration
@ConditionalOnClass({ JdbcTemplate.class, AbstractRoutingDataSource.class })
@ConditionalOnBean(DataSource.class)
@ConditionalOnEnabledHealthIndicator("db")
@AutoConfigureBefore(HealthIndicatorAutoConfiguration.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class DataSourceHealthIndicatorAutoConfiguration extends
CompositeHealthIndicatorConfiguration<DataSourceHealthIndicator, DataSource>
implements InitializingBean {
private final Map<String, DataSource> dataSources;
private final Collection<DataSourcePoolMetadataProvider> metadataProviders;
private DataSourcePoolMetadataProvider poolMetadataProvider;
public DataSourceHealthIndicatorAutoConfiguration(
ObjectProvider<Map<String, DataSource>> dataSources,
ObjectProvider<Collection<DataSourcePoolMetadataProvider>> metadataProviders) {
this.dataSources = filterDataSources(dataSources.getIfAvailable());
this.metadataProviders = metadataProviders.getIfAvailable();
}
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;
}
@Override
public void afterPropertiesSet() throws Exception {
this.poolMetadataProvider = new CompositeDataSourcePoolMetadataProvider(
this.metadataProviders);
}
@Bean
@ConditionalOnMissingBean(name = "dbHealthIndicator")
public HealthIndicator dbHealthIndicator() {
return createHealthIndicator(this.dataSources);
}
@Override
protected DataSourceHealthIndicator createHealthIndicator(DataSource source) {
return new DataSourceHealthIndicator(source, getValidationQuery(source));
}
private String getValidationQuery(DataSource source) {
DataSourcePoolMetadata poolMetadata = this.poolMetadataProvider
.getDataSourcePoolMetadata(source);
return (poolMetadata != null) ? poolMetadata.getValidationQuery() : null;
}
}
这个配置类会为所有数据源初始化一个健康检查类:DataSourceHealthIndicator,未来健康检查时,会执行里面的健康检测方法,并修改健康检查的状态。 每初始化一个DataSourceHealthIndicator,都会放入上下文对象中(是一个linkedHashMap)(CompositeHealthIndicator):
private final Map<String, HealthIndicator> indicators;
public void addHealthIndicator(String name, HealthIndicator indicator) {
this.indicators.put(name, indicator);
}
外面一层包装(CompositeHealthIndicatorConfiguration):
protected HealthIndicator createHealthIndicator(Map<String, S> beans) {
if (beans.size() == 1) {
return createHealthIndicator(beans.values().iterator().next());
}
CompositeHealthIndicator composite = new CompositeHealthIndicator(
this.healthAggregator);
beans.forEach((name, source) -> composite.addHealthIndicator(name,
createHealthIndicator(source)));
return composite;
}
真正在健康检查时执行的是(DataSourceHealthIndicator):
private void doDataSourceHealthCheck(Health.Builder builder) throws Exception {
String product = getProduct();
builder.up().withDetail("database", product);
String validationQuery = getValidationQuery(product);
if (StringUtils.hasText(validationQuery)) {
// Avoid calling getObject as it breaks MySQL on Java 7
List<Object> results = this.jdbcTemplate.query(validationQuery,
new SingleColumnRowMapper());
Object result = DataAccessUtils.requiredSingleResult(results);
builder.withDetail("hello", result);
}
}
可以看到,一开始默认将状态设置成了UP,后面异常后会改为:DOWN。getValidationQuery(product)方法会获取要执行的sql,最后用jdbcTemplate来验证数据库的连通性。
看到这里,其实一开始遇到的问题就有答案了,这里获取到的验证sql为:select 1,而hana数据库是根本不支持这种语法的,另外,如果验证sql为空的话,也会默认执行:/ping/ select 1。
这个验证sql加载的地方是在(DataSourceHealthIndicatorAutoConfiguration):
private String getValidationQuery(DataSource source) {
DataSourcePoolMetadata poolMetadata = this.poolMetadataProvider
.getDataSourcePoolMetadata(source);
return (poolMetadata != null) ? poolMetadata.getValidationQuery() : null;
}
这里会取配置中的connectionTestQuery。一般在mysql中测试sql为:select 1;在oracle中测试sql为:select Hello from dual。而在hana数据库中,可以将一张空表作为测试表来构造测试sql。
这里是设置connectionTestQuery的其中一个方法:
public HikariDataSource dataSource() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setMinimumIdle(0);
dataSource.setIdleTimeout(10000);
dataSource.setConnectionTestQuery("select count(1) from test.\"student\"");
return dataSource;
}
@Bean
public DataSourcePoolMetadataProvider hikariPoolDataSourceMetadataProvider(HikariDataSource hikariDataSource){
return dataSource -> new HikariDataSourcePoolMetadata((HikariDataSource) dataSource);
}
这样的话,实例就可以起起来了。
看起来好像没什么问题了,但是当其他数据源挂掉后,我们的实例由于健康检查不通过,导致实例异常,表现是:本系统与异常数据源无关的功能也用不了。于是,应该健康检查时,只检查主数据源即可,其他数据源在用的时候知道是否健康即可。
这里有一种方案,可以在初始化DataSourceHealthIndicator的时候,只初始化主数据源相关的Indicator:
@Bean
public HealthIndicator dbHealthIndicator(HikariDataSource dataSource, HealthAggregator healthAggregator) {
CompositeHealthIndicator composite = new CompositeHealthIndicator(
healthAggregator);
composite.addHealthIndicator("dataSource", new DataSourceHealthIndicator(dataSource, null));
return composite;
}
这样的话,当健康检查数据源时,只会检查主数据源mysql,这里将测试sql设置成了null,意在用默认的select 1。
修改前:当其他数据库挂了的时候,我们正在运行的服务器实例也变得异常了,原有的功能也用不了了。
修改后:当其他数据库挂了的时候,我们的服务器实例正常运行,原油的功能可以用。