Spring Boot Actuator 配置和应用

一、Spring Boot Actuator 简介

官方文档:https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html

Spring Boot Actuator(以下简称 SBA)主要用于 Spring Boot 应用的健康检查,配合 K8S 可用于服务部署,切换流量,监控报警等场景。

1. 举个例子:

(1) 账户服务,线上运行版本为 1.0.0,准备上线 1.1.0,要求上线过程不可中断客户请求;为此,我们需要在保持 1.0.0 运行的同时部署 1.1.0,在部署完 1.1.0 并且保证其已经 Ready 之后,我们让网关把部分 / 全部流量切换进来(前者属于灰度发布,后者是蓝绿发布)。如果这个过程是自动化的,我们如何让程序感知 1.1.0 版本已经 Ready 呢?注意,Ready 这个状态未必仅指应用启动,可能启动之后还需要加载数据,或者有其他初始化工作,只有这些工作全部完成才算 Ready。

Spring Boot Actuator 的 readiness 接口便是为此而设计的。

(2) 用户服务,它强依赖于账户服务,我们希望当账户服务无法访问的情况下,用户服务可以感知,并且主动拒绝外部请求。针对这种场景,可通过自定义 HealthIndicator 的方式来实现。

2. 本文的应用对象为 Kubernetes 环境下的健康检查。

二、liveness 和 readiness

Kubernetes 有两个 Probes(探针, 请求者),分别是 liveness 和 readiness。相应的 SBA 也提供了两个接口,分别是 liveness 和 readiness。

1. liveness,表示 Spring Boot 应用的存活状态。SBA 认为,liveness 的状态不应依赖外部系统的状态,外部系统包括数据库、中间件、外部接口等;它仅仅表示应用本身是否活着。取值如下:

2. readiness,表示 Spring Boot 应用是否做好准备接受外部请求。SBA 认为,readiness 一定要考虑应用自身依赖的外部系统状态,例子 2 很好的说明了这一点。取值如下:

3. Spring Boot 的生命周期

(1) 启动阶段

阶段 Liveness 状态 Readiness 状态 备注
Starting BROKEN REFUSING_TRAFFIC K8S 检查 "liveness" 探针状态,如果长期没有影响,则重启服务。
Started CORRECT REFUSING_TRAFFIC 刷新 Spring Boot 应用上下文,执行其他启动任务,此时仍处于拒绝接受流量的状态。
Ready CORRECT ACCEPTING_TRAFFIC 启动完成,应用开始接受请求。

(2) 关闭阶段 

阶段 Liveness 状态 Readiness 状态 备注
Running live ready Shutdown 开始执行。
Graceful shutdown live unready

所谓优雅的关闭,是指让已经进入应用的请求处理完毕,且在此阶段拒绝接受新的请求进入。

Shutdown complete broken unready 完成关闭。

三、监控什么?

我们需要监控的,一定是影响应用运行的因素。根据空间的划分,我们可以把监控目标分为本地依赖和远程依赖。

1. 本地依赖主要包括:

(1) 内存空间

(2) 硬盘空间

(3) 环境变量

(4) 配置文件

2. 远程依赖又可分为:

(1) 远程软件依赖,比如数据库、中间件、FTP 或搜索引擎等;

(2) 远程接口依赖。

四、怎样监控?

1. 本地依赖

(1) 内存空间,假设应用实例出现内存溢出,我们应该设置应用的 liveness 状态为 down,使调度器感知,发出报警,切换流量到其他实例,并重启该实例,这样可以避免线上出现宕机;

(2) 磁盘空间,如果应用需要保存大量文件到本地,就有必要监控磁盘空间占有量,如果空置率低到一定水平,应该设置应用的 readiness 状态为 down,使调度器将请求切换到其他实例;

2. 远程依赖

(1) 软件依赖,至少需要从连通性上进行监控,如果发现无法连同,则需要设置应用的 readiness 状态为 down,使调度器将请求切换到其他实例,或拒绝请求;

(2) 接口依赖,主要围绕着接口的连通性和可用性进行监控,如果接口服务可以提供 readiness 接口,可直接访问该接口来实现。

 五、Spring Boot Actuator 重要概念

1. Endpoints

可以把 Endpoints 理解为一个功能模块,功能模块可以监控 Spring Boot 应用,甚至可以与 Spring Boot 进行交互(比如读取信息,关闭应用等操作)。Spring Boot 内置了很多 Endpoints,对我们来讲最重要的 Endpoints 是 health,即健康检查模块。

除 health 外,常用的 Endpoints 还包括:

(1) env,用于输出环境变量;

(2) beans,用于显示应用中所有的 bean 对象;

(3) info,可以配置一些自定义信息。

a. 默认情况下,除 shutdown 以外,所有的 Endpoints 均是 enable 状态。如果希望 disable 所有 Endpoints,可在 application.properties 中配置:

management.endpoints.enabled-by-default=false

b. 如果希望 enable 指定 Endpoints,可配置:

management.endpoint.<name>.enabled=true

例如:

management.endpoint.info.enabled=true

c. 默认情况下,只有 health 和 info 两个 Endpoints 暴露在 web 环境下,可通过 url 访问,如果想暴露其他 Endpoints,可配置:

management.endpoints.web.exposure.include=health,info,env

全部暴露可配置成 *

management.endpoints.web.exposure.include=*

2. HealthIndicator(健康检查器)

从概念上讲,HealthIndicator 从属于 health 这个 Endpoints。

从代码上讲,它是一个接口,继承该接口并重写 health 方法便可自定义一个健康检查器。

根据依赖目标的不同,检查器也不相同,围绕着常见的依赖,Spring Boot 内置了许多 Indicator:

上面这些已经默认启动,下面这俩默认禁用。

 

首先,让我们看看这些自带 HealthIndicator 的继承关系:

以 MongoHealthIndicator 为例,让我们透过源码,看看这些 HealthIndicator 如何进行健康检查:

事实上,MongoHealthIndicator 执行了一段 Mongo 命令 "{buildInfo: 1}",以此来判断 Mongo 的连通性。

3. Health 信息

(1) Spring Boot 启动后,访问

http://<Spring Boot Domain>/actuator

即可查看该实例所有已经暴露到 Web 的 Endpoints。

(2) 查看健康信息

/actuator/health

如果希望得到详细的健康信息,则添加配置:

management.endpoint.health.show-details=always

(3) Health 状态

如果一切 OK,则是 UP 状态;如果有依赖挂掉,则是 DOWN 状态。

(4) Health 数据是如何产生的?

在 (2) 中我们看到,访问 /actuator/health 得到的是系统整体的健康信息。默认情况下,SBA 自动 enable 了所有 HealthIndicator。

如果想关掉所有默认打开的 HealthIndicator,配置:

management.health.defaults.enabled=false

如果想 enable 指定的 HealthIndicator,配置

management.health.<key>.enabled=true

key 的取值参考五.2 中的表格。 

4. 自定义 HealthIndicator

(1) 官网例子

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;

@Component
public class MyHealthIndicator implements HealthIndicator {
@Override
public Health health() {
int errorCode = check(); //执行健康检查

    <span style="color: rgba(0, 0, 255, 1)">if</span> (errorCode != <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">) {
        return Health.down().withDetail(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Error Code</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, errorCode).build();
    }

    return Health.up().build();
}

}

(2) 再来个栗子

假设我们的项目依赖 https://api.github.com/

@Component
public class GitHubAPIHealthIndicator implements HealthIndicator {
    private final RestTemplate restTemplate;
@Override
public Health health() {
    try {
        ParameterizedTypeReference</span>&lt;Map&lt;String, String&gt;&gt; reference = new ParameterizedTypeReference&lt;Map&lt;String, String&gt;&gt;<span style="color: rgba(0, 0, 0, 1)">() {};
        ResponseEntity</span>&lt;Map&lt;String, String&gt;&gt; result = restTemplate.exchange(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">https://api.github.com/</span><span style="color: rgba(128, 0, 0, 1)">"</span>, HttpMethod.GET, <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">, reference);<br>
        </span><span style="color: rgba(0, 0, 255, 1)">if</span> (result.getStatusCode().is2xxSuccessful() &amp;&amp; result.getBody() != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
            return Health.up().withDetails(result.getBody()).build();
        } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
            return Health.down().withDetail(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">status</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, result.getStatusCode()).build();
        }
    } catch (RestClientException ex) {
        return Health.down().withException(ex).build();
    }
}

}

访问 /actuator/health

 

5. Health Groups

(1) 如果你想将若干指定的 HealthIndicator 捆绑起来构成一组健康检查,那么可配置:

management.endpoint.health.group.custom.include=db

访问

/actuator/health/custom

即可获得 custom 这个组的健康状态。

(2) SBA 创建了两个默认的组,liveness 和readiness。

默认情况下,只有当 Spring Boot 应用部署到 K8S 内,才可以访问 liveness 和 readiness 接口,如果你希望在本地访问这两个接口,需要配置:

management.endpoint.health.probes.enabled=true

接口访问方式为:

/actuator/health/liveness/actuator/health/readiness

(3) 既然 liveness 和 readiness 也是 Health Groups,那么我们就可以自定义它们包含的 HealthIndicator 对象:

management.endpoint.health.group.liveness.include=livenessState,customCheck
management.endpoint.health.group.readiness.include=readinessState,db

此时 liveness 会执行 LivenessStateHealthIndicator 和 CustomCheckHealthIndicator 两个检查器,readiness 会执行 ReadinessStateHealthIndicator 和数据库检查。

三、基本配置和应用

1. pom 引用

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

作为 spring-boot-starter-parent 自带的组件,我们无需指定它的版本号。

2. 在 application.properties 中添加配置

management.endpoints.web.exposure.include=info, health, env
management.endpoint.health.show-details=always
management.endpoint.health.probes.enabled=true