Spring Boot 自带的 Health Indicator
目的
- 检查应用程序的运行状态
状态
- DOWN - 503
系统有错误 - OUT_OF_SERVICE - 503
系统有错误 - UP - 200
系统正常运行 - UNKNOWN - 200
系统处于未知状态
机制
- 通过 HealthIndicatorRegistry 收集信息
- 通过这个里面每个HealthIndicator 实现具体检查逻辑
配置项
- management.health.defaults.enabled=true|false
默认打开所有的Health Indicator - management.health.<id>.enabled=true
单独打开名为id的Health Indicator - management.endpoint.health.show-details=never|whenauthorized|always
never:永远不显示health信息
whenauthorized:登录之后 显示
always :一直显示
以DataSourceHealthIndicator为例(检测数据库类型及其连接状态)
public class DataSourceHealthIndicator extends AbstractHealthIndicator implements InitializingBean {
private static final String DEFAULT_QUERY = "SELECT 1";
private DataSource dataSource;
private String query;
private JdbcTemplate jdbcTemplate;
public DataSourceHealthIndicator() {
this((DataSource)null, (String)null);
}
public DataSourceHealthIndicator(DataSource dataSource) { //在传入的时候 构造一个DataSource 并保存在内部
this(dataSource, (String)null);
}
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;
}
public void afterPropertiesSet() throws Exception {
Assert.state(this.dataSource != null, "DataSource for DataSourceHealthIndicator must be specified");
}
protected void doHealthCheck(Builder builder) throws Exception {
if (this.dataSource == null) { //健康检查 如果没有DataSource 就返回一个"database", "unknown" 状况为up的 状态为健康的
builder.up().withDetail("database", "unknown");
} else {
this.doDataSourceHealthCheck(builder);
}
}
private void doDataSourceHealthCheck(Builder builder) throws Exception {
String product = this.getProduct(); //如果有database在这里 就做一个尝试 将它是什么数据库取出来
builder.up().withDetail("database", product); //将它赋给database
String validationQuery = this.getValidationQuery(product);//根据数据库 取出校验语句
if (StringUtils.hasText(validationQuery)) {
List<Object> results = this.jdbcTemplate.query(validationQuery, new DataSourceHealthIndicator.SingleColumnRowMapper());//根据校验语句取出结果
Object result = DataAccessUtils.requiredSingleResult(results);//如果结果正确
builder.withDetail("hello", result);//将结果放到hello中
}
}
private String getProduct() {
return (String)this.jdbcTemplate.execute(this::getProduct);
}
private String getProduct(Connection connection) throws SQLException {
return connection.getMetaData().getDatabaseProductName();
}
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 = "SELECT 1";
}
return query;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public void setQuery(String query) {
this.query = query;
}
public String getQuery() {
return this.query;
}
private static class SingleColumnRowMapper implements RowMapper<Object> {
private SingleColumnRowMapper() {
}
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
ResultSetMetaData metaData = rs.getMetaData();
int columns = metaData.getColumnCount();
if (columns != 1) {
throw new IncorrectResultSetColumnCountException(1, columns);
} else {
return JdbcUtils.getResultSetValue(rs, 1);
}
}
}
}
以DiskSpaceHealthIndicator为例(检测磁盘空间)
public class DiskSpaceHealthIndicator extends AbstractHealthIndicator {
private static final Log logger = LogFactory.getLog(DiskSpaceHealthIndicator.class);
private final File path;
private final DataSize threshold;
public DiskSpaceHealthIndicator(File path, DataSize threshold) {
super("DiskSpace health check failed");
this.path = path;
this.threshold = threshold;
}
/** @deprecated */
@Deprecated
public DiskSpaceHealthIndicator(File path, long threshold) {
this(path, DataSize.ofBytes(threshold));
}
protected void doHealthCheck(Builder builder) throws Exception {
long diskFreeInBytes = this.path.getUsableSpace();//将这个路径下可用的磁盘空间取出
if (diskFreeInBytes >= this.threshold.toBytes()) {//如果可用空间大于预值
builder.up(); //健康状况就是好的
} else {
logger.warn(String.format("Free disk space below threshold. Available: %d bytes (threshold: %s)", diskFreeInBytes, this.threshold));
builder.down();//反之 健康状况就是不好的
}
builder.withDetail("total", this.path.getTotalSpace()).withDetail("free", diskFreeInBytes).withDetail("threshold", this.threshold.toBytes());
}
}
自定义 Health Indicator
方法
- 实现 HealthIndicator 接口
- 根据自定义检查逻辑返回对应 Health 状态
• Health 中包含状态和详细描述信息
例子
CoffeeIndicator
@Component
public class CoffeeIndicator implements HealthIndicator {
@Autowired
private CoffeeService coffeeService;
@Override
public Health health() {
long count = coffeeService.getCoffeeCount();//每次检查的时候 取出Count
Health health;
if (count > 0) {
health = Health.up() //如果coffee数量大于0 表明 系统的健康状况是好的
.withDetail("count", count)
.withDetail("message", "We have enough coffee.")
.build();
} else {
health = Health.down()
.withDetail("count", 0)
.withDetail("message", "We are out of coffee.")
.build();
}
return health;
}
}
application.properties
spring.jpa.hibernate.ddl-auto=none
#ddl-auto 的几个常用属性值:
#none:默认值,什么都不做,每次启动项目,不会对数据库进行任何验证和操作
#create:每次运行项目,没有表会新建表,如果表内有数据会被清空
#create-drop:每次程序结束的时候会清空表
#update:每次运行程序,没有表会新建表,但是表内有数据不会被清空,只会更新表结构。
#validate:运行程序会校验数据与数据库的字段类型是否相同,不同会报错
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true
# 格式化sql语句
pring.jpa.properties.hibernate.use_sql_comments=true
#指出是什么操作生成了该语句
management.endpoints.web.exposure.include=*
#在web接口开启所有的endpoint
management.endpoint.health.show-details=always
#永远显示健康状况
info..app.name=coffee
info.app.author=Long
#info.app.encoding=@project.build.sourceEncoding@
info.app.encoding=UTF-8
#通过设置Spring属性info.*,你可以定义info端点暴露的数据。所有在info关键字下的Environment属性都将被自动暴露
pom
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 增加actuator开启依赖 -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<!-- 增加 prometheus 依赖 否则 运行prometheus无效 -->
<dependency>
<groupId>org.joda</groupId>
<artifactId>joda-money</artifactId>
<version>1.0.1</version>
</dependency>
<dependency>
<groupId>org.jadira.usertype</groupId>
<artifactId>usertype.core</artifactId>
<version>6.0.1.GA</version>
</dependency>
<!-- 增加Jackson的Hibernate类型支持 -->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-hibernate5</artifactId>
<version>2.9.8</version>
</dependency>
<!-- 增加Jackson XML支持 -->
<!--<dependency>-->
<!--<groupId>com.fasterxml.jackson.dataformat</groupId>-->
<!--<artifactId>jackson-dataformat-xml</artifactId>-->
<!--<version>2.9.0</version>-->
<!--</dependency>-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
结果
打开 http://localhost:8080/actuator/
可以看到 我们能检测到的所有可以检测到的数据
其中,使用http://localhost:8080/actuator/info查看我们在application.properties中设置的info信息
使用http://localhost:8080/actuator/health,查看程序健康状况
第一个是咖啡的数量及其状态
第二个是使用的数据库及其状态
第三个是磁盘的使用空间及其状态
三个状态都为up时 这个系统的状态就为 否则 为down