背景说明
在生产环境中,需要实时或定期监控服务的可用性。Spring Boot的actuator(健康监控)功能提供了很多监控所需的接口,可以对应用系统进行配置查看、相关功能统计等。
这里针对Actuator组件对数据源[DataSource]判活进行源码阅读简要过程进行记录。
解决方案
组件引入
org.springframework.boot
spring-boot-starter-actuator
主类入口
1.X
请阅读org.springframework.boot.actuate.health.DataSourceHealthIndicator
2.X
请阅读org.springframework.boot.actuate.jdbc.DataSourceHealthIndicator
核心流程
由于1.X
和2.X
在核心逻辑上并没有变化,只是对包名做了调整,这里针对2.X
进行阅读分析,当我们需要对我们新建的一个模块进行健康监控的话,可以把这些监控信息统一归纳到health节点下,只需要实现HealthIndicator接口即可,当我们调用health端口获取监控状态时health方法会自动执行。
@FunctionalInterface
public interface HealthIndicator {
/**
* Return an indication of health.
* @return the health for
*/
Health health();
}
HealthIndicator
-健康检查指示器
查看源码AbstractHealthIndicator
的核心实现
public abstract class AbstractHealthIndicator implements HealthIndicator {
@Override
public final Health health() {
Health.Builder builder = new Health.Builder();
try {
doHealthCheck(builder);
}
catch (Exception ex) {
if (this.logger.isWarnEnabled()) {
String message = this.healthCheckFailedMessage.apply(ex);
this.logger.warn(StringUtils.hasText(message) ? message : DEFAULT_MESSAGE,
ex);
}
builder.down(ex);
}
return builder.build();
}
protected abstract void doHealthCheck(Health.Builder builder) throws Exception;
}
查看DataSourceHealthIndicator
的核心实现
public class DataSourceHealthIndicator extends AbstractHealthIndicator implements InitializingBean {
private static final String DEFAULT_QUERY = "SELECT 1";
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
if (this.dataSource == null) {
builder.up().withDetail("database", "unknown");
}
else {
doDataSourceHealthCheck(builder);
}
}
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
这里
DEFAULT_QUERY
是默认判活SQL
当通过数据库厂商无法获取到对应的SQL
时会使用此默认的SQL
,当数据库不支持此默认SQL
时会出现判活失败即报错的情况。
由此可以看出核心流程如下:
获取数据源厂商=>获取判活SQL
=>执行判活SQL
=>构建返回内容
分支流程
构建JdbcTemplate
通过数据源新建一个JdbcTemplate
用于数据查询
public DataSourceHealthIndicator(DataSource dataSource, String query) {
super("DataSource health check failed");
this.dataSource = dataSource;
this.query = query;
this.jdbcTemplate = (dataSource != null) ? new JdbcTemplate(dataSource) : null;
}
获取数据源厂商
private String getProduct() {
return this.jdbcTemplate.execute((ConnectionCallback) this::getProduct);
}
private String getProduct(Connection connection) throws SQLException {
return connection.getMetaData().getDatabaseProductName();
}
这里返回的数据厂商可能为MySQL
,PostgreSQL
,Oracle
等
获取数据源判活SQL
protected String getValidationQuery(String product) {
String query = this.query;
if (!StringUtils.hasText(query)) {
DatabaseDriver specific = DatabaseDriver.fromProductName(product);
query = specific.getValidationQuery();
}
if (!StringUtils.hasText(query)) {
query = DEFAULT_QUERY;
}
return query;
}
这里关注类org.springframework.boot.jdbc.DatabaseDriver
,源码摘录部分如下
public enum DatabaseDriver {
UNKNOWN((String)null, (String)null),
MYSQL("MySQL", "com.mysql.jdbc.Driver", "com.mysql.jdbc.jdbc2.optional.MysqlXADataSource", "/* ping */ SELECT 1"),
MARIADB("MySQL", "org.mariadb.jdbc.Driver", "org.mariadb.jdbc.MariaDbDataSource", "SELECT 1") {
public String getId() {
return "mysql";
}
},
ORACLE("Oracle", "oracle.jdbc.OracleDriver", "oracle.jdbc.xa.client.OracleXADataSource", "SELECT 'Hello' from DUAL"),
POSTGRESQL("PostgreSQL", "org.postgresql.Driver", "org.postgresql.xa.PGXADataSource", "SELECT 1"),
}
这里维护了数据库厂商和判活SQL
的映射,当通过数据库厂商名称没有找到时返回UNKNOWN
public static DatabaseDriver fromProductName(String productName) {
if (StringUtils.hasLength(productName)) {
DatabaseDriver[] var1 = values();
int var2 = var1.length;
for(int var3 = 0; var3 < var2; ++var3) {
DatabaseDriver candidate = var1[var3];
if (candidate.matchProductName(productName)) {
return candidate;
}
}
}
return UNKNOWN;
}
构建返回结果
Object result = DataAccessUtils.requiredSingleResult(results);
builder.withDetail("hello", result);
暴露端点
1.X
如果仅仅想关闭数据源安全检查可以使用配置managent.health.db.enable=false
关闭
management.port=8088
默认情况下健康检查和应用端口一致,可以使用配置项
management.port=8088
更改端口
访问接口:http://localhost:8088/actuator/health
2.X
如果仅仅想关闭数据源安全检查可以使用配置management.health.db.enabled=false
关闭
management.server.port=8088
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
默认情况下健康检查和应用端口一致,可以使用配置项
management.server.port=8088
更改端口
默认情况下端点服务暴露,需要通过management.endpoints.web.exposure.include
进行开启,如果需要排除可以使用management.endpoints.web.exposure.exclude
进行排除即可
默认情况下无法暴露明细数据,可以通过配置项management.endpoint.health.show-details
暴露
健康检查
86183@LAPTOP-CRFFK470 MINGW64 ~
$ curl -X GET http://localhost:8088/actuator/health
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 189 0 189 0 0 12600 0 --:--:-- --:--:-- --:--:-- 12600{"status":"UP","details":{"db":{"status":"UP","details":{"database":"MySQL","hello":1}},"diskSpace":{"status":"UP","details":{"total":808121790464,"free":9833570304,"threshold":10485760}}}}
86183@LAPTOP-CRFFK470 MINGW64 ~
$
例外情况
Idea旗舰版本内置了健康检查功能,打开控制台后,右侧有一个端点包含Bean、运行状况、映射,当数据源检查不通过时,Idea控制会出现异常日志,例如当数据库厂商为Calcite
at org.springframework.boot.actuate.health.DataSourceHealthIndicator.doDataSourceHealthCheck(DataSourceHealthIndicator.java:109)
at org.springframework.boot.actuate.health.DataSourceHealthIndicator.doceHealthCheck(DataSourceHealthIndicator.java:98)
at org.springframework.boot.actuate.health.AbstractHealthIndicator.health(AbstractHealthIndicator.java:43)
............................................................
............................................................
............................................................
at org.springframework.boot.actuate.health.DataSourceHealthIndicator.doDataSourceHealthCheck(DataSourceHealthIndicator.java:109)
at org.springframework.boot.actuate.health.DataSourceHealthIndicator.doceHealthCheck(DataSourceHealthIndicator.java:98)
at org.springframework.boot.actuate.health.AbstractHealthIndicator.health(AbstractHealthIndicator.java:43)
............................................................
............................................................
............................................................